6.4 配置(Configuration)

    6.4.1 不使用自动化时的问题

    说起部署,人们往往只关注发布工作,但其实服务器的构成管理成本也非常高,这一直是一个问题。例如,某应用程序的运行环境非常复杂,构建该环境需要经过数十、数百个步骤的操作,若在这样的情况下搭建新的验证环境或开发环境,会有怎样的问题呢?

    笔者所经历过的某个应用程序开发中,由于没有人负责维护构建环境的手册,只提供了正常运行环境的访问权限,因此此只能一边确认此环境上安装的模块及中间件的配置,一边构建手头的环境,白白浪费了时间和精力。

    利用虚拟化技术,充分利用作为原型的虚拟镜像,的确能多少跳过一些中间步骤,但即使是习惯的人也要花费几小时,不习惯的人甚至要花费几天的时间。在实际的开发工作中,当团队人员增加或发生工作交接等情况时,构建环境以外的人也必须能够在该环境上运行应用程序。

    耗费了一定的时间终于构建起了大致版本一致或相同的运行环境,程序也开始运行了。但这并不意味着各个环境之间就绝对一致。

    若在这样的环境下进行开发,就会出现“明明在本地环境上能够正常运行,但在测试环境下就运行不了”的问题。正是因为模块间微妙的版本差异而产生了 bug。

    作为上述问题的解决方案,可以使用配置(Configuration)相关的自动化工具,从一开始就采用完全相同的方法对环境进行安装。

    6.4.2 Chef

    Chef 是用 Ruby 编写的服务器配置的自动化工具。只要准备好名为 Cookbooks(食谱)的服务器构建手册形式的配置文件等,就能按照文件所记述的规则为服务器安装软件包并配置中间件。

    无论 10 台还是 1 万台机器,只需要 1 份 Cookbooks,就能构筑起符合要求的环境。像 Facebook 这样大规模的运维也同样使用 Chef 10 。

    10 http://www.infoq.com/cn/news/2013/02/facebook-chef

    Chef 不仅限于大规模基础设施的构建,在小规模基础设施构建中也能发挥其真正的价值,因此即便只是构建 1 台机器的环境,也可以对其加以有效利用。

    ●…… Chef 的构成

    Chef 的运行大致有以下 3 种结构。

    ❶ Chef Server(图 6.3)

    6.4 配置(Configuration) - 图1

    图 6.3 Chef Server

    ❷ Chef Solo+ Knife Solo(图 6.4)

    6.4 配置(Configuration) - 图2

    图 6.4 Chef Solo + Knife Solo

    ❸ Chef Solo(图 6.5)

    6.4 配置(Configuration) - 图3

    图 6.5 Chef Solo

    ❶ ~ ❸ 所使用的 Cookbooks 可以是相同的。在由 1 ~ 2 台服务器构成的环境下,可以从 Chef Solo 的构成方式开始,然后随着服务器台数的增加,再考虑构建 Chef Server 的环境。

    为了学习 Chef 最基本的使用方法,这里我们以利用 Chef Solo 构建简单的 Web 服务器环境为例进行讲解。

    Chef 的安装和 Vagrant 一样可以采用 RPM 等包的形式进行。过去是通过 gem 命令进行安装的,现在安装包中还提供了 Ruby 的本体,因此不会和 OS 上的 Ruby 发生版本依赖等问题,能够直接使用 Chef。

    ●…… 目录构成和文件配置

    这次介绍的 Chef 的文件和目录结构如图 6.6 所示。Chef 的配置文件统称为 Cookbooks,其中特别是 Recipe 和 Template 起着主要的作用。下面就对这些文件逐个地进行讲解。

    6.4 配置(Configuration) - 图4

    图 6.6 Chef 的文件和目录结构

    文件和目录可以用 Chef 的命令行接口 Knife 自动生成。

    $ knife cookbook create <Cookbook name> -o <output dir>

    这样就能自动生成必要的目录结构和文件,所以在第一次构建 Chef 环境时,请试着执行上述命令。但是指定执行对象 Recipe 的 node.json 等配置文件需要手动生成。

    ●…… node.json

    这个文件是运行 Chef 所必需的文件。这次 node.json 中记载的内容参照了 roles 目录下的 setup.json。本例中记述的是执行 main 目录下的名为 default.rb 的 Recipe。

    { "run_list": [ "role[setup]" ←参照setup.json ] }

    ●…… setup.json

    这个文件就是配置实际的 Recipe 文件的地 方,同时还记载了 attribute。attribute 定义的变量能在 Recipe 文件和 Template 文件中使用。

    { "name": "setup_examplep_program", "override_attributes": { "apache": { "documentroot": "/var/www" ←能够在recipe或template中使用的变量 } }, "json_class": "Chef::Role", "description": "Setup Examplep Program", "chef_type": "role", "run_list": [ "recipe[main::default]" ←配置 Recipe ] }

    ●…… solo.rb

    这个文件是运行 Chef 所必需的文件。记载有 Chef 的 Cookbooks 的路径以及 roles 的文件路径。Chef 是用 Ruby 编写的,所以路径的配置同样可以使用 Ruby 的 File.expand_path。

    其他 Ruby 的语法也可以使用,但原则上作为服务器构建的自动化工具应该尽量将所使用的语言限制在 Chef 的 DSL(Domain Specific Language,领域语言)范围内。

    cookbookpath File.expandpath("../cookbooks", FILE) role_path File.expand_path("../roles", __FILE)

    ●…… default.rb

    这个文件就是被称为 Recipe 的重要的配置文件。在 package 那一行中,RHEL 系列的话使用 yum/rpm,Debian GNU/Linux 系列的话使用 apt/deb 来安装 Apache。

    template 是在部署配置文件时使用的,这里记述的处理是在 Apache 的 conf.d 目录下添加新的配置文件。variables 用于在 Recipe 中接收 template 的变量。Chef 中对变量的处理是非常重要的。通过记述在 attribute 中,就可以在 Recipe 和 template 中作为变量使用,但 attribute 可以记载在几个文件中,并且有着各自不同的优先级。本书不做详细的介绍,只是提一下优先级最低的 attribute/default.rb 文件中所记载的是作为全体的默认值,关于服务器的作用、测试环境、正式环境这样不同环境的环境变量,可以记载在 Role 或 Environments 中。通过灵活运用 attribute,能够调整单个 Cookbooks 使其适用于各种环境。

    在 directory 那行中,虽然只定义了新建指定的目录,但需要注意的是这里用到了定义在 attribute 中的 #{node[:apache][:documentroot]} 这样的变量。

    git 那行记述了 checkout 代码库的 master 分支到指定的目录下。通过指定 actionsync ,checkout 时就会更新最新的版本。

    在 service 那行中,可以操作中间件的启动或停止。这里定义为启动 Apache。

    package "httpd" ←安装httpd   template "/etc/httpd/conf.d/#{ENV['HOSTNAME']}.conf" do ↑根据template部署文件 source "virtualhost.conf.erb" mode 0644 owner "root" group "root" variables({ :env_hostname => "#{ENV['HOSTNAME']}", }) end   directory "#{node[:apache][:documentroot]}/#{ENV['HOSTNAME']}" do action :create ←创建由attribute指定的目录 end   git "/var/www/#{ENV['HOSTNAME']}" do repository "https://github.com/example/program.git ↑指定任意程序的Git仓库 reference "master" action :sync end   service "httpd" do action :start ←启动httpd end

    ●…… virtualhost.conf.erb

    这是 Recipe 中指定的 Template 文件。这里可以内嵌 attribute 或 Recipe 文件中以 variables 形式指定的变量。这里可以使用 Ruby 的 erb 模板。

    <VirtualHost *:80> ServerAdmin serveradmin@<%= @env_hostname %> DocumentRoot <%= @node.apache.documentroot %>/<%= @env_hostname %> ServerName <%= @env_hostname %> ErrorLog logs/<%= @env_hostname %>-error_log CustomLog logs/<%= @env_hostname %>-access_log combined </VirtualHost>

    ●…… Chef 的运行方法和运行结果

    只需指定 node.json 和 solo.rb,就可以运行 Chef Solo。

    $ sudo chef-solo -j node.json -c solo.rb

    执行 Chef 后显示如下结果。

    [root@ip-10-132-183-178 chef]# chef-solo -j node.json -c solo.rb Starting Chef Client, version 11.6.0 Compiling Cookbooks… Converging 5 resources Recipe: main::default package[httpd] action install ←安装httpd - install version 2.2.25-1.0.amzn1 of package httpd   template[/etc/httpd/conf.d/ip-10-132-183-178.conf] action create - create new file /etc/httpd/conf.d/ip-10-132-183-178.conf - update content in file /etc/httpd/conf.d/ip-10-132-183-178.conf from none to 3cb554 ←部署Template中指定的文件 —- /etc/httpd/conf.d/ip-10-132-183-178.conf 2013-09-22 10:12:45.278335727 +0000 +++ /tmp/chef-rendered-template20130922-6866-6hkxkf-0 2013-09-22 10:12:45.290335967 +0000 @@ -0,0 +1,8 @@ +<VirtualHost :80> + ServerAdmin serveradmin@ip-10-132-183-178 + DocumentRoot /var/www/ip-10-132-183-178 + ServerName ip-10-132-183-178 + ErrorLog logs/ip-10-132-183-178-error_log + CustomLog logs/ip-10-132-183-178-access_log combined +</VirtualHost> ↑文件的内容被替换成了attribute的值 + - change mode from '' to '0644' - change owner from '' to 'root' - change group from '' to 'root'   directory[/var/www/ip-10-132-183-178] action create - create new directory /var/www/ip-10-132-183-178 ↑创建了由attribute指定的目录 git[/var/www/ip-10-132-183-178] action sync - clone from https://github.com/example/program.git into /var/www/ip-10-132-183-178 ←使用git命令进行clone - checkout ref 05e45129b1fd1bbad18c790fcd814ba36403cab1 branch master   service[httpd] action start ←启动httpd - start service service[httpd]   Chef Client finished, 5 resources updated

    使用这个 Cookbooks 安装 Apache,并将服务器配置为基于名称的虚拟主机,在 DocumentRoot 中通过 Git checkout 任意程序并启动 Apache。到此为止的处理就实现自动化了。如果是 checkout 后能够立即使用的应用程序的话,那么通过上述一连串的处理,环境构筑应该就能完成了。

    ●…… 使用 Chef 的优点

    可以看出用 Chef 的 DSL 编写的 Cookbooks 非常容易理解,学习成本非常低。Cookbooks 是用 Ruby 的代码编写的,因此还可以进行版本管理,即相当于在版本管理系统上保存了“服务器正常状态及其历史记录”。

    至今为止服务器的结构和构建管理是被分割开的,通过对 Cookbooks 进行版本管理,服务器的构建有了历史记录管理。并且只需执行 Chef 命令就能实现服务器构建的自动化。也就是说,用 Ruby 的代码来管理服务器状态的“Infrastructure as Code”这种服务器管理的新时代已经到来了。

    ●…… 使用 Chef 时的注意事项

    使用 Chef 时有一点需要注意,那就是既然已经使用 Chef 对服务器进行管理,就要禁止手动构建服务器,要记住必须使用 Chef 进行相关作业。如果手动地安装包、修改配置等,就会造成服务器的状态和 Cookbooks 中的定义发生背离,而为了达到相同的状态就不得不执行多条命令,并且只有进行此操作的人才知道具体的内容。但如果将上述操作记载到 Cookbooks 的话,那么谁都可以通过 1 条 Chef 命令将服务器配置到相同的状态。

    从构建管理的角度来看,不使用 Cookbooks 而手动构建服务器也是不明智的。全部采用 Chef 来构建环境,即使最初只需要 1 条命令就能完成构建工作,编写 Recipe 文件也是必要的。虽然对编写 Recipe 的工作感到焦躁不安的人不在少数,但经过 1 个月、1 年的时间后,随着配置修改的内容不断积累,你就会感受到使用 Chef 所带来的好处。

    ●…… 使用 Chef 的时间点

    可以在构建开发环境的阶段,也就是开始开发前后一段时间开始使用 Chef。使用和开发环境、测试环境、正式环境同样的 Cookbooks,因环境而有所差异的配置,可以全部通过 attribute 来控制。

    如果想进一步了解 Chef 的话,可以参考相关资料。

    6.4.3 serverspec

    ●…… 什么是 serverspec

    如果说 Chef 是用代码来管理服务器环境构建的工具,那么 serverspec 就是对服务器的构成进行单元测试的测试框架。从名字就可以看出,其内部使用了 Ruby 的 BDD 框架 RSpec11 。

    11 http://rspec.info/

    serverspec 虽然不是直接进行配置的工具,但为了确保 Chef 等通过代码管理服务器构建的工具能够持续地正常运行,serverspec 可以说是必不可少的测试框架。

    ●…… serverspec 的安装

    执行如下命令来安装 serverspec 的运行环境。

    $ gem install serverspec $ serverspec-init Select a backend type:   1) SSH 2) Exec (local)   Select number: 2

    serverspec 支持远程服务器的测试,当然也可用于测试本地服务器(图 6.7)。执行测试需要 root 权限,或者通过 sudo 来执行。第一次初始化时无论选择远程主机(SSH )还是本地主机(Exec (local) ),之后都可以进行切换,以验证为目的的话可以选择“2”,并制作测试用例。

    6.4 配置(Configuration) - 图5

    图 6.7 serverspec 的运行环境

    ●…… 测试文件的记述方式

    执行 serverspec-init 命令,会自动生成文件和目录(图 6.8)。

    6.4 配置(Configuration) - 图6

    图 6.8 serverspec 的目录结构

    localhost 目录下的 xxxx_spec.rb 文件就是测试用例。假设在执行之前提到的 Chef 的 Cookbooks 后进行测试,我们来试着制作测试用例。测试的内容包括确认 httpd 的安装及启动、确认通过 template 部署的文件是否存在,以及对文件内容的检查。

    ●…… httpd_spec.rb

    require 'spec_helper'   describe package('httpd') do it { should be_installed } ←检查httpd的安装 end   describe service('httpd') do it { should be_running } ←检查httpd是否启动 end   describe port(80) do it { should be_listening } ←检查是否在侦听80端口 end check_config = "/etc/httpd/conf.d/#{ENV['HOSTNAME']}.conf" describe file(check_config) do it { should be_file } ←检查通过template部署的文件的有无   it { should contain "ServerName #{ENV['HOSTNAME']}" } end ↑检查文件的内容   describe file(check_config) do it { should be_mode 644 } it { should be_owned_by 'root' } it { should be_grouped_into 'root' } ↑检查文件的权限以及所有者 end

    除了 httpd 以外,我们再试着添加 git clone 是否成功执行的测试用例。新建名为 spec/localhost/git_spec.rb 的文件,如下所示。

    ●…… git_spec.rb

    require 'spec_helper'   describe file("/var/www/#{ENV['HOSTNAME']}") do it { should be_directory } end ↑检查git clone是否成功执行

    ●…… serverspec 的执行方法及执行结果

    执行 serverspec 的测试时,在 Rakefile 层执行下面的命令。

    $ rake spec

    执行后会显示如下结果。

    /usr/bin/ruby -S rspec spec/localhost/git_spec.rb spec/localhost/httpd_spec.rb ……   Finished in 0.21744 seconds 9 examples, 0 failures

    在上述例子中,可以确认 9 个测试用例都通过了。测试失败的话,failures 的数目会增加。如果要输出测试的详细内容,可以设置 SPEC_OPTS="—format=documentation—colour" 这样的环境变量,就能输出 RSpec 的 documentation format。

    要注意的是几乎所有 serverspec 的测试用例都被描述为了“should be_xxxxx”,一看便知这是在对服务器当前的状态进行检查。正因为可以像这样以近似自然语言的形式来描述测试用例,所以测试用例还可以被当作服务器的需求规格书来使用,制作非常简单。

    ●…… serverspec 的优点

    serverspec 的特别优秀之处在于测试的执行不依赖于 Ruby 的运行环境这一点。测试代码的执行是通过 shell 命令进行的,因此只需要在运行 serverspec 的服务器上安装 Ruby 和 serverspec,远程服务器只需要能够通过 SSH 登录即可进行测试。

    serverspec 还支持多种 OS,可以对 RHEL 以外的 OS 如 Solaris、Debian GNU/Linux 进行测试。

    使用 serverspec 能够对任意环境应有的状态进行检查,配合 Chef 一起使用,就能够放心地对服务器进行动态的配置修改。

    6.4.4 最佳实践(其 1)

    在讲解 Chef 时提到了“Infrastructure as Code”这个单词,为了保证代码的质量,进行测试是很重要的,并且测试还必须能够持续地执行。在实现了服务器构建的自动化之后,CI 的实施就变得非常重要(图 6.9)。

    6.4 配置(Configuration) - 图7

    图 6.9 Jenkins+Chef+serverspec+Vagrant

    让我们试着结合 Jenkins+Chef+serverspec+Vagrant 来实际进行上述处理。

    Vagrant 提供了启动虚拟服务器时执行 Chef 或 shell 的机制,这使得构建理想中的环境成为可能。执行 vagrant init 时会生成名为 Vagrantfile 的虚拟机配置文件。

    在 Vagrantfile 中添加 Chef Solo 或 shell 脚本等服务器预处理(provisioning)的相关内容,在第一次执行 vagrant up 时,这些处理会被执行。除第一次启动之外,可以通过启动时添加选项 vagrant up —provision ,或者在虚拟服务器启动后运行 vagrant provision 来执行上述预处理。

    在 GitHub 上设有 Chef 或 serverspec 的代码库的情况下,通过配置 Webhooks,并设置在执行 Vagrant 的预处理的过程中启动 Jenkins 的任务,这样 CI 环境就构建完成了。git 代码库的话可以使用 git hooks12 ,几乎所有的代码库都支持在 commit 或 push 的钩子中执行命令。

    12 http://git-scm.com/book/zh/v1/自定义-Git-Git挂钩

    Vagrantfile 中可以通过关键字 inline: 来提示 shell 脚本。下面的 Vagrantfile 会执行 git clone 命令。

    ●…… Vagrantfile

    # -- mode: ruby -- # vi: set ft=ruby :   # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2"   Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "centos"   config.vm.provision "shell" , ←chef的安装 inline: "curl -L https://www.opscode.com/chef/install.sh | bash"   config.vm.provision "chef_solo" do |chef| chef.add_recipe "main" ↑设置安装git和serverspec的Recipe文件 end   end config.vm.provision "shell" , ←clone Cookbooks和测试用例 inline: "rm -rf cookbooks_serverspec && git clone git@github.com/my/ cookbooks_serverspec.git"   config.vm.provision "shell" , ←执行clone下来的Cookbooks inline: "cd cookbooks_serverspec/chef-repo && chef-solo -j node.json -c solo.rb"   config.vm.provision "shell" , ←最后执行serverspec inline: "cd cookbooks_serverspec/serverspec && rake spec"     end

    通过指定 add_recipe 在主服务器上准备好 Recipe 文件,就会向虚拟服务器自动部署 Recipe 文件并执行 Chef Solo(图 6.10)。还可以通过指定 cookbooks_path 来使用定制过的 Cookbooks。

    6.4 配置(Configuration) - 图8

    图 6.10 指定 add_recipe 的情况下的目录结构

    由于 Vagrant 本体中没有提供 serverspec,因此通过 inline: 来执行。或者也可以使用名为 vagrant-serverspec 的插件。

    ●…… default.rb

    package "git" package "ruby" package "rubygems" gem_package "serverspec" do action :install end

    这只是服务器构建 CI 的一个例子。将 Vagrant 部分替换为 Docker13 等技术也可以实现相同的功 能,Chef 部分也可用 puppet14 来代替。 serverspec 部分也可以用 Cucumber Chef15 或 Test Kitchen 16 等测试框架。这里的重点在于要选择团队最易于使用的、适合自己的工具,而不是局限于本书中所提到的工具。这对于持续地运行这些工具来说也是非常重要的,因此工具的选择可以在慎重地交流沟通后再决定。

    13 https://www.docker.io/

    14 http://puppetlabs.com/

    15 http://www.cucumber-chef.org/

    16 https://github.com/test-kitchen/test-kitchen

    6.4.5 最佳实践(其 2)

    因为重视硬件性能或受服务合同上的限制,正式环境中使用物理服务器 17 的现场不在少数。在这样的情况下,比较理想的是尽量避免手动升级服务器。Kickstart 能够实现物理服务器的 OS 安装的自动化。通过组合使用 Kickstart+Chef+serverspec,能够缩短服务器从投入到开始提供服务的时间(图 6.11)。

    17 相对于虚拟服务器来说。——译者注

    6.4 配置(Configuration) - 图9

    图 6.11 通过 Kickstart+Chef+serverspec 实现服务器安装自动化的例子

    6.4.6 实现物理服务器投入运营为止的所有步骤的自动化

    Kickstart 同样适用于虚拟环境的安装,因此可以在 VirtualBox、Xen、KVM 等上面事先对这样的流程进行验证。Chef 和 serverspec 的组合可以用来进行测试,并且通过创建之前叙述的构建服务器的 CI 环境,还可以实现频繁地构建服务器,这样的成功案例已经有很多了,因此可以将执行 Chef 这一步之前的软件包安装以及和硬件相关的分区设置等交由 Kickstart 来处理。