工具介绍

Chef 和 Chef Solo

Chef 是一个自动化工具,用 Opscode 开发,使用 Ruby DSL 以一种可重复使用的格式表示配置服务器所需的命令
Chef 经常运行在服务器集群的枢纽,统筹安排其他服务器的配置

可以在单机模式下使用 Chef (Chef Solo)
在这种模式下,我们在本地开发平台上定义服务器的各种角色和设置,然后根据需要手动应用这些设置

如果项目不断扩大也不用担心,使用 Chef Solo 制定的自动化配置大多数都可以在 Chef 中使用

Knife 和 Knife Solo

Knife 是一个命令行工具,为本地开发环境中的 Chef 仓库和远程服务器之间提供交互接口

一般情况下,远程服务器必须是主 Chef 服务器。不过 Knife Solo 允许在单机模式下使用 Chef, 可以直接和需要配置的服务器交互

Knife Solo 使用介绍

Berkshelf

在 Chef 中,安装各组件所需的命令成为「配方」

Berkshelf 就像是配方的 Bundler
我们使用 Berkshelf 获取配置服务器所需的 Chef 配方(特定的版本)

Chef 术语介绍

  • 配方 (recipe)

    定义安装单个组件(例如 Ruby, mysql-server, Monit 等)所需的命令

  • 食谱 (cookbook)

    相关的配方集合,例如,mysql 食谱中包含 mysql-server 配方和 mysql-client 配方

  • 节点 (node)

    要配置的远程服务器

  • 角色 (role)

    一系列配方的组合,应用在节点上

    例如,Postgres Server 角色可能会包含安装 postgres-server 的配方,安装和设置防火墙的操作,以及安装合适的服务器监控工具的操作

  • 数据包 (data bag)

    配方所用的元数据,保存为 JSON 格式文件。例如,要创建的用户列表,以及相应的公钥

  • Chef 仓库 (chef repository)

    一系列节点和角色定义

创建 Chef Solo 仓库

  1. 新建文件夹

    mkdir chef_repo
    cd chef_repo
    
  2. 安装工具

    1. 初始化 bundler

      bundle init
      
    2. 在刚生成的 Gemfile 文件中写入如下内容:

      source 'https://rubygems.org'
      
      gem 'knife-solo', '0.3.0'
      gem 'chef', '~> 11.10.0'
      gem 'chef-zero', '1.7.2'
      gem 'berkshelf', '~> 2.0.14'
      
    3. 然后运行如下命令来安装

      bundle install
      

初始化仓库

使用 bundle exec 确保使用的 Gemfile 中定义的 gem 版本来执行命令

bundle exec knife solo init .

生成若干文件,结构如下:

chef_repo
├── .chef
│   └── knife.rb
├── .gitignore
├── Berksfile
├── Gemfile
├── Gemfile.lock
├── cookbooks
│   └── .gitkeep
├── data_bags
│   └── .gitkeep
├── environments
│   └── .gitkeep
├── nodes
│   └── .gitkeep
├── roles
│   └── .gitkeep
└── site-cookbooks
    └── .gitkeep

下面来说说各个文件及文件夹的作用

knife.rb

Knife 是个命令行工具,可以和远程服务器上的 Chef 交互
Knife Solo 添加了额外的命令,可以在本地开发设备上直接和要配置的服务器交互(而不用通过 Chef 中央服务器中转)

knife.rb 中包含针对我们这个仓库的 Knife 设置。这个文件在使用 Chef 中央服务器时更有用,并不符合我们使用的单机模式,但却是 Knife 的主要设置(也是唯一的设置文件)

Knife 的全部设置选项可以在 http://docs.opscode.com/config_rb_knife.html 中查看
目前,我们要设置的重要选项如下:

  • cookbook_path
    这个选项是个数组,包含很多相对于仓库根目录的路径,指定角色中使用的食谱和节点定义的存储位置

  • node_path
    相对仓库根目录的路径,指定节点定义存储的位置

  • role_path
    相对仓库根目录的路径,指定角色定义存储的位置

  • data_bag_path
    相对仓库根目录的路径,指定数据包的存储位置

一般情况下,都可以直接使用默认值

Berksfile

在 Berkfile 中定义 Chef 仓库要使用的食谱,以及各自的版本。然后执行 berks install 命令安装这些食谱。如果食谱在 metadata.rb 文件用 depends 定义了依赖食谱,Berkshelf 会负责安装这些依赖食谱

和 Bundler 一样,Berkshelf 也会生成一个 Berksfile.lock 文件,写入食谱及相应的版本号

cookbooks

cookbooks 文件夹用来存放使用 Berkshelf 安装的他人编写的食谱
如想存放自己编写的食谱,应放在 site-cookbooks 文件夹

千万别在 cookbooks 文件夹中存放没有使用 Berkshel 来管理的食谱,因为,每次运行 knife solo 后,cookbooks 文件夹中的内容都会被抹去重新写入

data_bags

如果食谱中要用到大量数据,就可以将数据存放在这里

environments

对应现实中的工作流,比如说生产环境,准备环境,测试环境,开发环境

nodes

存放节点文件

roles

存放角色文件

site-cookbooks

用来存放自己编写的食谱

食谱介绍

以 redis-tlq 食谱为例:https://github.com/TalkingQuickly/redis-tlq
将其作为自己编写的食谱放到 site-cookbooks 文件夹下

文件结构如下:

redis-tlq
├── attributes
│   └── default.rb
├── metadata.rb
├── recipes
│   └── default.rb
└── templates
    └── default
        ├── redis-server.erb
        └── redis.conf.erb

下面是各个文件及文件夹的说明

metadata.rb

元数据文件中详细说明了这个食谱的作用,编写人,依赖件以及版本号

metadata.rb:

# 食谱的名称
name              "redis-tlq"
maintainer        "Ben Dixon"
maintainer_email  "[email protected]"
description       "Installs redis from rwky's ppa"
# 食谱的版本号
version           "0.0.6"

# 配方的名称
recipe "redis-tlq", "Installs redis"

# 指定这个食谱支持的操作系统
# 这个设置很重要,因为我们这个简单的食谱只支持 Ubuntu, 会用到一些只有 Ubuntu 才支持的命令
supports "ubuntu"

使用 Berkshelf 时,version 和 recipe 这两行是关键的设置,Berkshelf 会查看 metadata.rb 文件,确认要使用的食谱中是否有匹配的配方

还有一个选项 depends 这里没提到,用来指定要依赖的其他食谱

recipes/default.rb

Chef 食谱必须包含一个名为 default 的配方
如果在节点或角色定义中使用到了某个食谱,而没有指定要用哪个配方,就会使用这个默认配方

cookbooks/redis-tlq/recipes/default.rb:

# use redis from ppa rather than the one
# available in the package manager. rwky
# builds the stable version and is kept
# consistently up to date. We need python-software-properties
# for add-apt-repository to work

# Chef 首先会使用操作系统的包管理工具检查是否安装了 python-software-properties 代码包
# 如果没有,那就安装
package 'python-software-properties'

# 告诉 Chef, 其中包含的命令是在 bash 中执行的
# 随后的文本会在配方执行时显示出来,让用户知道配方正在做什么
bash 'adding stable redis ppa' do
  # 以 root 这个用户身份去执行
  user 'root'
  code <<-EOC
    add-apt-repository ppa:chris-lea/redis-server
    apt-get update
  EOC
end

package 'redis-server'

# 上述操作只是安装了 Redis, 我们还要对其进行一些自定义设置,
# 再添加初始化脚本用于启动、停止 Redis 服务器,这就轮到 template 出场了
#
# 这段代码的作用如下:
# 1. 在 redis-tlq/template/default 文件夹中寻找文件 redis.conf.erb
# 2. 解析这个文件中的所有 erb 代码,然后把文件复制到节点上,存放位置为 /etc/redis/redis.conf
# 3. 把这个文件的拥有者设为 root 用户组的 root, 权限为 0644
template "/etc/redis/redis.conf" do
  owner "root"
  group "root"
  mode "0644"
  source "redis.conf.erb"
  notifies :run, "execute[restart-redis]", :immediately
end

# ...

模板文件夹的说明

templates 文件夹中的子文件夹对应的是服务器上所安装的操作系统
如果使用的是 Ubuntu 12.04, 上面的 template 代码块就会在下面的路径中按顺序寻找 redis.conf.erb 文件:

  1. templates/ubuntu-12.04/redis.conf.erb
  2. templates/ubuntu/redis.conf.erb
  3. remplates/default/redis.conf.erb

如果要编写复杂的食谱,照应多种操作系统,这种处理方式的强大就体现出来了
目前,我们仅仅使用 default 文件夹

属性

配方可以使用属性 (attribute) 进行定制。属性可以在多个地方设定,包括:

  • 节点定义
  • 角色定义
  • 食谱默认值

一般情况下,在食谱中会为属性指定默认值,如果需要,可以覆盖默认值

templates/default/redis.conf.erb 文件中有这么一段代码:

<% unless node[:redis] && node[:redis][:dont_bind] %>
  bind 127.0.0.1
<% end %>

如果在节点中有如下代码:

{
  "redis": {
    "dont_bind": true
  }
}

那么就执行 bind 127.0.0.1 这段代码

食谱属性的默认值

在食谱中可以为属性定义默认值。这往往也是推荐的做法,大多数用户只要做少许改动就可使用

属性的默认值在 attributes/default.rb 文件中定义:

# whethere to prevent redis from binding to 127.0.0.1
default[:redis][:dont_bind] = false

上面代码的作用是:
会把 false 应用到食谱 node[:redis][:dont_bind],除非默认值被覆盖了

关于属性的更多说明,请阅读 OpsCode 网站上的文档:
http://docs.opscode.com/essentials_cookbook_attribute_files.html

角色介绍

新建角色文件

touch roles/redis-server.json

写入如下内容:

{
  "name": "redis-server",
  "description": "Redis server",
  "default_attributes": {
    "redis": {
      "dont_bind": false
    }
  },
  "run_list": [
    "recipe[redis-tlq]",
    "recipe[monit-tlq]",
    "recipe[monit_configs-tlq::redis-server]"
  ],
  "json_class": "Chef::Role"
}

代码解释如下:

  • name

    指定角色的名称
    当 Chef 在运行列表中见到 rolo[redis-server] 时,就会寻找名为 redis-server 的角色。寻找的范围是 knife.rb 中 role_path 对应文件夹中所有的 JSON 文件

  • description

    描述这个角色的作用

  • default_attributes

    用来设置默认属性

  • run_list

    是一个数组,Chef 会依次安装执行列出的配方和角色

    角色使用如下格式:

    role[role_name]
    

    配方使用如下格式:

    recipe[cookbook_name::recipe]
    
  • “json_class”: “Chef::Role”

    如果使用 JSON 格式,那么必须加上这一行
    如果使用 Ruby 格式,则不用加

因为使用到了 2 个额外的食谱 monit-tlqmonit_configs-tlq
前者用于监控系统的各种参数,如果进程崩溃或者超出了预设的参数,Monit 会重启该进程
后者是针对系统组件的 Monit 设置

所以我们需要在仓库根目录的 Berksfile 文件中加入如下内容:

cookbook 'monit-tlq', github: 'TalkingQuickly/monit-tlq', branch: 'master'
cookbook 'monit_configs-tlq', github: 'TalkingQuickly/monit_configs-tlq', branch: 'master'

配置远程服务器

  1. 把公钥复制到远程服务器

    ssh-copy-id root@yourserverip
    
  2. 在远程服务器上安装 Chef, 在本地仓库根目录执行如下命令:

    bundle exec knife solo prepare root@yourserverip
    

    若使用 Vagrant 可以用 vagrant ssh-config 命令查看 ssh 配置,然后执行如下类似命令:

    bundle exec knife solo prepare [email protected] -p 2222 -i /Users/mwum/.vagrant.d/insecure_private_key
    

    安装时间稍长,耐心等待吧

    上述命令的作用如下:

    • 在 nodes 文件夹中新建文件 yourserverip.json
    • 通过 SSH 登入远程服务器
    • 检测操作系统的类型
    • 安装相应的 Chef 版本

可能会遇到的问题

如果不确定是什么问题,可以在安装 Chef 的命令加上 -VV 选项来查看 DEBUG 信息

  • 远程服务器的网络有问题,需要设置代理

    1. 用 ssh 登录远程服务器

    2. 在 ~/.bashrc 中加入如下内容:

      export http_proxy="http://your-proxy.company.org:80"
      export https_proxy="http://your-proxy.company.org:80"
      export HTTP_PROXY="http://your-proxy.company.org:80"
      export HTTPS_PROXY="http://your-proxy.company.org:80"
      
    3. 退出远程服务器,重新安装 Chef

应用配方

  1. 配置节点文件 nodes/yourserverip.json, 加入如下内容:

    {
      "redis": {
        "dont_bind": false
      },
      "run_list": [
        "role[redis-server]"
      ]
    }
    

    代码上半段是属性定义,run_list 的作用和角色文件中的一样

  2. 应用配方,在仓库根目录下执行如下命令:

    bundle exec knife solo cook root@yourserverip
    

    这个命令的作用如下:

    • 根据 Berksfile 文件通过 Berkshelf 下载食谱(相当于此命令 `berks vendor cookbookss/)
    • 通过 SSH 登入远程服务器
    • 把本地的配方文件复制到远程服务器
    • 应用配方,并显示输出结果

    如果要经常重用某个节点定义,可以将其另存为,例如 nodes/side_project_server.json, 然后执行如下命令:

    bundle exec knife solo cook root@yourserverip nodes/side_project_server.json
    

参考图书:
Rails 程序部署之道