6.5 编配(Orchestration)
6.5.1 发布作业的反面教材
这里的编配大致上可以简单地理解为发布作业的自动化。下面举一些发布作业中的反面教材,如果其中没有一条和你所在的项目相符合的话,可以直接跳过本节。
手动进行发布作业
发布作业的内容每次都不相同
发布作业需要特殊的知识(其他人不知道如何发布)
不能反复进行任意次数的发布
什么是发布?发布可以理解为通过 SSH 登录到远程服务器,将代码切换到最新的分支并进行更新数据库等操作。小型项目的发布,经过几个到几十个步骤的操作可能就结束了。而一定规模的项目的发布,则需要经过数百以至于数千个步骤。
手动实施上述工作不仅会耗费大量的时间,对于负责发布的工作人员的体力也是极大消耗。因此这样的情况下要反复地进行发布是不现实的。
对正式环境实施的发布总是会伴随着失败的风险。将发布失败的可能性降为零是不可能的,但有方法让它接近于零。那就是推进发布作业的自动化。在测试环境以及 staging 环境上反复演习自动化的发布并进行验证,在正式环境之前发现问题,然后修改有问题的内容,再反复地对发布进行演练,这样就能降低在正式环境中发生错误的风险。
6.5.2 Capistrano
6.4 节中介绍了用 Ruby 编写的 Chef 和 serverspec,发布作业的自动化方面也有用 Ruby 编写的非常强大的工具 Capistrano。这款工具为 Ruby on Rails18 框架项目的副产品,支持 Ruby on Rails 应用程序的部署。使用 Ruby 以外的语言或框架编写的应用程序的发布同样可以使用 Capistrano,并且已经有了很多这样的实例。
●…… Capistrano 的系统构成
使用 Capistrano 时的系统构成如图 6.12 所示。
图 6.12 Capistrano 的构成
只需在执行 Capistrano 的服务器上安装即可使用。也就是说, Capistrano 采用的是 Push 型的架构。不需要在发布对象的服务器上安装代理等,只需要确保执行 Capistrano 的服务器能够通过 SSH 登录就可以了。
例如,将多个服务器按角色归纳为应用程序服务器和数据库(DB)服务器,对应用程序服务器执行代码更新的任务,在数据库服务器上为了修改数据库模式而进行 Dump 等任务,这些发布作业都可以通过 Capistrano 一并执行。
像这样简单且频繁的发布作业,在运维中会频繁地出现。频度高的时候每天、每小时都要进行。用 Capistrano 实现上述作业的自动化,无论重复多少次都能够毫无差池地完成。
●…… Capistrano 的安装
Capistrano 从安装到初始化只需要执行数条命令即可,因此可谓是使用门槛相当低的工具。
$ gem install capistrano $ capify .执行 capify 命令会生成部署配置文件的模板(图 6.13)。
图 6.13 部署配置文件模板的构成
●…… deploy.rb
这里以向 web1 和 web2 这两个应用程序服务器部署文件为例来讲解 deploy.rb 的配置。
role :web, 'web1' , 'web2' task :deploy_task, :roles => :web do upload("/usr/local/src/deploy_file", "/tmp", :via => :scp) run "uname -a > /tmp/uname_file" download "/tmp/uname_file", "/tmp/uname_file.$CAPISTRANO:HOST$" end使用 upload 向远程的 web1、web2 服务器部署文件,还能够在 web1、web2 服务器上执行通过 run 设置好的命令。本例中会执行 uname 命令,并将标准输出定位到 /tmp/uname_file。
最后设置用 download 命令来回收 /tmp/uname_file。保存回收文件的路径中设置有 $CAPISTRANO: HOST$ 这样的变量,这是因为在以多个服务器为对象的情况下,文件名相同会造成原有文件被覆盖,因此用远程服务器名来替换部分文件名。
●…… Capistrano 的执行方法
$ cap deploy_task虽然需要事先确保作为执行对象的 web1、web2 服务器能够通过 SSH 登录,但之后只需要记述不满 10 行的配置文件,无需反复操作就能确保执行相同的命令。
即便部署对象的台数增加,也只需在 role 中添加对象服务器,并执行 cap 这 1 条命令,这样所有的工作就完成了。从有 1 ~ 2 台部署的对象服务器时开始,将反复实施的作业做成 task 并由 Capistrano 来实施,这样即便台数增加也应该可以轻松应对了。
6.5.3 Fabric
和 Capistrano 相同,Fabric 也是一款通过 SSH 登录到多台远程服务器,按顺序执行作业的工具,由 Python 编写。功能上和 Capistrano 几乎完全相同,比较大的区别在于 Capistrano 基本上是并行执行处理,而 Fabric 则能够简单地在串行、并行处理间切换。
一般认为为了高效、快速地进行发布作业,并行处理会比较理想,实际中因为要确保安全地按照手册进行操作,要以串行方式处理的情况并不少见。在 6.6 节中我们将介绍无需停止服务就可以进行的零停机时间部署(Zero Downtime Deployment),如果设定了将两台 Web 服务器依次从网站的负载均衡器上解除,进行更新后再添加回去这样的做法,使用 Fabric 就能够简单地实现。
●…… Fabric(串行执行)的情况
❶ 从负载均衡器解除服务器 A
❷ 更新服务器 A
❸ 将服务器 A 加回负载均衡器
❹ 从负载均衡器解除服务器 B
❺ 更新服务器 B
❻ 将服务器 B 加回负载均衡器
在上述例子中,执行到 ❸ ~ ❹ 步骤时,就完成了一半的发布工作,更新完的服务也将公开。
●…… Capistrano(并行执行)的情况
❶ 从负载均衡器解除服务器 A
❷ 从负载均衡器解除服务器 B
❸ 更新服务器 A
❹ 更新服务器 B
❺ 将服务器 A 加回负载均衡器
❻ 将服务器 B 加回负载均衡器
Capistrano 在 ❶ ~ ❷ 步时服务停止,和预期的动作不相符。虽然有一些用 Capistrano 实现串行处理的解决方案,但使用直接支持串行处理的 Fabric 更为方便。
●…… 理解本地服务器和远程服务器操作上的区别
首先来看一下 Fabric 基本的记述例子。
#! /usr/bin/env python # -- coding: utf-8 -- from fabric.api import * def fabtest(): local('mkdir -p /tmp/local_test') run('mkdir -p /tmp/remote_test') with lcd('/tmp/local_test'): local('pwd') ←lcd/local在本地服务器上执行 with cd('/tmp/remote_test'): run('pwd') ←cd/run在远程服务器上执行Fabric 能够分开记述本地服务器和远程服务器上的操作。用 local 记述的命令在运行 Fabric 的本地服务器上执行,用 run 记述的命令在远程服务器上执行。在移动当前目录并执行命令的情况下,用 with 语句将 lcd 和 local,以及 cd 和 run 绑定到一起来记述。
下面是获取文件并部署的例子。
#! /usr/bin/env python # -- coding: utf-8 -- from fabric.api import * def getputtest(): get("/var/log/httpd/access.log") put("testfile" , "/tmp")Fabric 能够从本地服务器向远程服务器发送文件,反之也是可行的。
上述两个例子展示了基本的 Fabric 记述方法。下面再来看一下零停机时间发布用的 fabfile.py,发布作业自动化的例子如下所示。
#! /usr/bin/env python # -- coding: utf-8 -- import sys, os, re , string from fabric.api import * def getenv(name): ←取得服务器的环境变量 if os.environ.has_key(name): return os.environ[name] return "" def deploy(): change_balancer('disable') build_update() change_balancer('enable') def change_balancer(mode): run('/path/to/change_balancer' + mode) ←切换负载均衡器的命令 def build_update(): with cd('/path/to/build_dir/'): run('git checkout master && git fetch && git checkout -b %s %s' % (getenv('RELEASE_BRANCH'), getenv('RELEASE_TAG'))) run('/path/to/build_dir/application_starter restart') ↑重启应用程序的命令Fabric 可以直接使用 Python 的写法,也可以使用 shell 脚本的记述方式,因此无论对于运维人员还是开发人员来说,Fabric 都是容易上手的工具。上述例子中在运行 Fabric 的服务器上将分支名和标签名分别设置给环境变量 RELEASE_BRANCH 和 RELEASE_TAG,在这样的状态下执行 Fabric 就能切换到任意的版本。
●…… Fabric 的运行方法
$ fab -H 服务器A,服务器B deployFabric 可以在 fabfile.py 中自由地定义 object。这个例子中将 deploy 作为 object 定义并执行。这样就如同“Fabric(串行执行)的情况”中所记述的内容一样,不需要停止服务就可以进行发布作业。上述例子中只涉及了两台服务器,当服务器的台数更多时,如果仍然一台一台地执行的话,发布所花费的时间就会和服务器台数成比例地增加。为了将发布所用的时间控制在一定范围内,可以设置 Fabric 为并行执行,并配置同时执行的数量。
由不少于 4 台服务器运行时的命令如下所示。用 -P 指定并行执行,用 -z 来设置同时执行的台数。
$ fab -P -z 2 服务器A,服务器B,服务器C,服务器D deploy❶ 服务器 A,服务器 B 执行 deploy
❷ 服务器 C,服务器 D 执行 deploy
发布作业可以按照上述顺序进行,如果有 6 台服务器的话,可以将 -z 设置为 3,将发布分为前半部分和后半部分。即使进一步增加服务器台数,也可以用和两台服务器时相同的时间完成发布作业。通过有效运用 Fabric,就能够很容易地将我们从手动发布作业中解放出来。
6.5.4 Jenkins
Jenkins19 作为 CI 工具来说非常实用,作为部署的辅助工具来说同样可以加以有效利用。Jenkins 通过在远程服务器上安装客户端代理(slave agent),可以在远程服务器上执行各类任务。
19 Jenkins 的安装方法请参照 5.4 节。
●…… 主节点(master node)和从节点(slave node)的协作
Jenkins 客户端代理的安装方法非常简单。确保能够从主节点通过 SSH 登录到从节点,这样只需在 Jenkins 管理画面上添加从节点,主节点就会向从节点部署必要的文件,并自动启动客户端代理(图 6.14)。客户端代理的启动可以根据需要设置为一直启动、定时启动,或只在执行任务时启动等。
图 6.14 从主节点经由从节点执行任务
这里将对象服务器作为节点、将部署相关的 task 作为任务进行管理,通过联系各个事 物,来构建自动化发布的环境。和编配工具 Capistrano/Fabric 相比,Jenkins 的优势在于具备控制台输出和用户管理功能。
无论是为了能在 Web 的管理画面上看到谁、何时、执行了什么内容这样的记录,还是从留下跟踪信息的观点出发,Jenkins 可以自动实现上述功能。通过添加 Active Directory plugin20 这款插件,Jenkins 能够和 Active Directory 进行协作。并且通过添加 Role Strategy Plugin21 插件, Jenkins 可以以任务为单位进行用户权限管理。因此,即便是需要严格进行用户管理的企业,也可以放心使用。
20 https://wiki.jenkins-ci.org/display/JENKINS/Active+Directory+plugin
21 https://wiki.jenkins-ci.org/display/JENKINS/Role+Strategy+Plugin
通过利用 Jenkins 的参数化构建(build)这个功能,可以在每次执行任务时传递 shell 变量,以便对任务进行灵活的管理。
下面,我们以从远程服务器上通过 scp 命令发送文件为例,对从节点的添加、任务的设置、执行等一系列流程进行讲解。
●…… 从节点的添加
顺利启动 Jenkins 后,通过浏览器访问 http://${remote_host}:8080 ,能够看到 Jenkins 管理画面的初始状态。该状态允许文件访问,因此正式使用的情况下,建议通过添加用户或防火墙等对 IP 进行适当的限制。
添加从节点时,按照“系统管理”→“管理节点”→“新建节点”这样的流程对节点进行设置。必须设置的项目有 of executors(同时 build 数量)和远程工作目录、启动方法(图 6.15)。
图 6.15 节点的添加
由远程工作目录决定部署客户端代理的路径,如果该目录下还没有部署客户端代理,Jenkins 会自动从主节点向对应的路径进行部署。在启动方法中配置启动客户端代理所需要的路径。
UNIX 系统选择 Launch slave agents on Unix machines via SSH,Windows 机器可以选择 Let Jenkins control this Windows slave as a Windows service。其他启动客户端代理的方法还有 JNLP(Java Network Launching Protocol),该方法适用于主节点在防火墙外,其他的从节点在防火墙内等存在网络限制的情况。
为了通过 SSH 启动客户端代理,需要配置认证信息,这里可以设置 ID/PASS 或密钥(图 6.16)。
图 6.16 认证信息的管理
待节点的配置完成并通过认证,远程服务器上会启动名为 slave.jar 的进程,作为主节点的管理对象由主节点控制。如果添加从节点出错,有可能是因为认证信息没有正确设置,或者 bashrc 中记载了多余的命令而造成了 slave.jar 启动失败。
●…… 任务的添加
添加完从节点后,接着添加在从节点上执行的任务。可以从 Jenkins 首页的“新建”开始添加。选择“构建一个自由风格的软件项目 ”。Jenkins 是作为 CI 工具开发的,所以有构建和代码管理相关的配置,作为编配工具使用时可以直接跳过这部分配置。
在 Restrict where this project can be run 设置刚才添加的节点,点击“构建”下的“增加构建步骤”,选择“Execute shell”,这样就能在远程服务器上执行任意的命令了。作为执行命令时传递变量的方法,可以选中“参数化构建过程”,添加文本、字符串、单选框等输入栏。可以设置多个参数,“名字”一栏中输入的文字能够直接作为环境变量传递给脚本(图 6.17)。
图 6.17 任务的添加
这里定义名为 FILENAME 的字符串变量,shell 脚本中的调用方式如下。
scp ${FILENAME} /tmp/${NODENAME}${FILENAME##*/}除了上述参数之 外,Jenkins 的任务中还可以使用名为 NODE_NAME 的变量。
●…… 任务的执行
任务添加完成后就是执行 了。点击新建任务中的 Build with Parameters,会出现任务中配置参数的输入画面,输入远程服务器上部署文件的路径(图 6.18)。点击开始构建,就可以从远程服务器上获取任意文件。
图 6.18 任务的执行
任务执行的记录会留在“构建历史”中。蓝色的图标表示成功,红色的图标表示失败,一目了然。如果失败的话,点击“构建历史”的链接,通过确认“控制台输出”的 shell 标准输出,就可以确认失败的具体内容。
这个例子中是通过点击构建按键来执行任务的,其他的任务执行时间还可以选择 Build after other projects are built(在其他项目的构建后开始构建)或 Build periodically(定时构建)。Build periodically 能够以 Cron 的形式安排任务执行。因此应用本例中所列举的 scp 任意文件的内容,Build periodically 能够实现从多个从节点上定时收集各类日志这一作业的自动化。
Jenkins 能及时掌握哪个从节点的日志收集处理失败等信息,对失败的处理还可以配置警告,因此可以作为管理工具加以有效利用。
6.5.5 最佳实践
●…… 结合 Jenkins 和 Fabric
至此我们介绍了几款编配相关的工具。并不是说只要选用其中的一个就可以了,只有组合使用上述工具,才能实现先进的自动化环境构建。笔者所实践的自动化部署环境中,用 Jenkins 管理所有的任务,通过 SSH 登录远程主机,执行 shell 的部分则使用 Fabric 来实施(图 6.19)。
图 6.19 Jenkins 和 Fabric 的组合
这个结构的优点在于,通过 Jenkins 的从节点执行 Fabric,能够将 Fabric 的执行结果作为 Jenkins 的控制台日志保存下来。Fabric 添加远程主机非常容易,并且能够作为代码进行管理,因此在版本管理的基础上可以对手头的环境进行验证,或者对各类环境进行操作。
通过组合使用工具,就能够取长补短,提高工作效率。
6.5.6 考虑安全问题
推进发布的自动化需要考虑用 root 账户登录 SSH 并执行命令,或者以有权执行 sudo 的用户进行登录。但从安全方面来说,允许 root 登录是非常危险的,因此很多服务器将 sshd_config 中的 PermitRootLogin 设置为 no。
在这种情况下,可以设置为只有从运行 Capistrano 或 Fabric 的服务器发出的登录请求才允许 PermitRootLogin,禁止除此之外的客户端的 root 登录。
通过设置为只有特定的用户(服务器管理员)才能登录到运行 Capistrano 或 Fabric 的服务器,并采用公钥验证的方式,就能在确保安全性的同时构建自由度较高的发布自动化环境。具体地说就是,对部署目标服务器的 /etc/ssh/sshd_config 进行如下配置。
RSAAuthentication yes PasswordAuthentication no PermitRootLogin no Match Address 192.168.1.1/32 ←从OpenSSH4.4开始可以使用Match命令 PermitRootLogin without-password ↑通过设置without-password,允许密码认证以外的登录方式sshd 中可以用 Match 命令对条件分支进行配置,如果是符合条件的 IP 地址或用户发出的 SSH 连接请求的话,可以按照配置的内容进行控制。本例中,假定运行 Capistrano 或 Fabric 服务器的 IP 地址为 192.168.1.1,则仅允许上述服务器对 root 账户发起 RSA 认证的 SSH 连接请求。从安全性方面来说,需要在兼顾各方面的基础上进行有效的设计。
专栏 手动部署的例子
至此我们介绍了几款部署的自动化工具,但实际运用中仍然有不得不手动作业的状况发生。
■无法实现自动化,不得不手动作业的情况
需要手动作业的案例有下面这些。
❶ 第一次构建自动化环境时
❷ 获取修复故障用的特殊日志
❸ 紧急地重启服务
❹ 预料之外的情况导致自动部署停止时,为了下次部署正常运行而进行的修复工作
关于 ❶~❸,如果今后可能反复发生的话,为了任何时候都能够处理,建议为它们配置自动化。
❹ 的情况下,需要注意的是这里并不推荐在部署失败时强行手动对服务器进行调整。为了通过部署而强行修改配置的话,就会导致验证环境和正式环境产生差异,那么下次部署时就很有可能再次失败。
部署脚本的内容出错的话,应该修正错误并再度实施部署,除此之外的方法这里都不认可。❹ 中的修复工作是指那些在并非由于部署脚本的内容而造成部署停止的情况下进行的修复工作,例如由于磁盘空间不足而造成部署失败时,为了确保剩余空间而删除一些不需要的文件等。
我们不可能提前预测到实际运用中的各种情况并做好准备。1 台服务器的话还可以通过 SSH 登录进行操作,而要对多台服务器实施作业的话,就非常考验耐心和集中力了。
针对这些意外产生的手动作业,也应该尽可能地借助工具,考虑是否可以通过利用工具,实现无论几台服务器都只需要一次输入就能完成作业。
■手动部署的实用工具
表 6.a 中列举了手动作业时的一些实用工具。
表 6.a 手动部署的实用工具
| RLogin | Windows 上运行的名为 RLogin 22 的终端能够向所有打开着的连接同时发送按键输入的操作,这样就能对所有登录状态下的服务器同时实施相同的操作 |
| Tera Term | 借助常规的终端软件 Tera Term 23 的广播命令功能,可以向多个终端同时发送命令。感觉只操作了 1 台服务器,但实际上能够同时对 10 台、甚至 100 台服务器进行操作,还可以减少操作失误的可能性。需要注意的是,原则上同时向多个目标发送键盘的输入,服务器状态不一致的情况下可能会返回不一样的结果。例如执行 cd/usr/local/tmp/ 命令后再执行 touch test 的情况下,1 台特定机器上的 /usr/local/tmp/ 目录损坏时,test 文件就会被生成在登录时的目录下。因此,在每一步操作之后,应该尽可能地确认下所有的终端,或者设法通过操作来吸收这种环境上的差异 |
| ClusterSSH/Parallelssh/ClusterIt | Linux 或 Mac OS 等 UNIX 系统环境上有 ClusterSSH/Parallelssh/Clusterlt 24 等的 OSS,能够在多台服务器上同时执行命令 |
22 http://nanno.dip.jp/softlib/man/rlogin/ 并不是使用 513TCP 端口的工具。
23 http://www.vector.co.jp/soft/win95/net/se276622.html
24 http://sourceforge.jp/magazine/08/11/06/0025200
