【译】Capistrano介绍
翻译来自GREE开发者博客上的文章。链接
译者水平有限翻译如有误请指出。
开始
这篇是GREE Advent Calendar 2013年21日文章。呈上!
大家好,我是九冈。在GREE公司,我为了巩固Java以及Scala的传播基础,一个人制作了发布和监视的系统。
这次将在这个工程中学习到的知识以这片名叫【Capistrano 3入门博客】的形式共享出来。
想将所有产生手动操作的可能性消灭在萌芽状态
这篇博客将介绍Capistrano 3的基础。Capistrano 3是基于Ruby的服务器操作以及自动发布的攻击。Capistrano 3的利用能够将系统发布等复杂的服务器操作自动化。这篇博客里将特别以发布为焦点,说明如何利用Capistrano将服务器操作自动化。
学习Capistrano 3是需要花点时间的。为了降低各位所花的时间,于是我写了这篇文章。此处套话略去1000字。。。
对象
这片博客的对象是以下各位
- 想知道Capistrano的概要的各位
- 想学着使用Capistrano的各位
- 想将手工操作自动化的各位
- 已经通过其他工具实现了自动化
以上述各位作为对象,读了这篇博客之后能取得不错的收获是本文的目标。 (对于已经通过诸如Capistrano、Ansible、Fabic之类的工具实现了服务器操作自动化的各位,可能就没有什么新意了)
为什么deploy会花费大量的功夫
将做好的网络服务之类的应用发布给用户用,并不仅仅是开发这些应用,实际上还必须要讲这些服务放在服务器上让之运行。而且,这些应用的发布,意外的能花费大量的时间。究其原因,是因为发布相关的需求本身就存在这诸多分歧。
发布的需求是什么?
发布的需求大致有以下这些。
- 应用的构成要素
- 实施的环境与规模
- 允许的发布时间间隔
- 发布的最后期限
- 预算是多少
应用的构成要素
采用什么语言开发,框架,是不是有中间层的应用
实施环境和规模
发布在什么样的服务器,多少台上
允许的发布时间间隔
从发布开始到发布结束花多少时间为好
发布的最后期限
有没有写自动化脚本的时间
预算是多少
为什么写自动化脚本会花费初期预算? 比如说,每次都用同样的语言开发,使用同样的框架和中间件,每次都发布在同样的服务器上的话,事情就简单了。但是如果不是这样发布的难度就会增加 。
手动作业好不好?
前述的分歧较多的发布需求,我们采用自动化或者手动作业来完成。
手动作业虽然有初期零投入的优点,但是须要不断有人持续的来写操作说明,并且反复操作过程中有出现错误的可能也是它的一个缺点。这就是想做自动化的原因。
那就做自动化不就好了么?
虽然这样想,但是做自动化有不得不翻越的壁垒。生成性壁垒和保守性壁垒。
生产性壁垒
用shell脚本?Ant?Ansible?Fabric?Capistrano?
开发新项目的时候,是建立自己的发布系统,还是利用现有的比如OSS之类系统,都是一个选项。单独的发布系统编码和测试需要花功夫,用现有的系统的时候学习需要花功夫。想尽早开始开心的编码起来。
保守性壁垒
沿用性?扩展性? 如果自己开发,沿用性有时候非常的困难。设计和编码的重点在什么能够设定,什么不能设定。如果能够留下充足的文档自然非常好,但是也时常有因为时间和预算的原因只够写完脚本而不能完成文档。
想自动化
因为这种原因,实际上我们在将发布自动化的时候,不得不考虑发布系统的沿用性和扩展性问题。这很难取舍。譬如“这次没时间了就手动操作”“先不管怎样自动化做起来,需要的时候在写文档”,也是没办法的事情。
但是,如果说理想情况,那就是超越生产性和保守的屏障做好自动化。
用Capistrano 3开始做自动化
越过上面所述的生产性和保守性的壁垒的一个方法是,降低学习以及持续的成本。本文就是出于这个目的。
期待的结果
采用Capistrano带来的变化 采用Capistrano,能够将各种发布流程同一成所谓的【Capistrano的设定】。
这种变化带来的优点和缺点是什么的?
优点
保守性程度高~maintainable~
写脚本的人设计思想是不一样的,读懂别人的设计思想是需要花时间的。如果大家都懂Capistrano的话,别人写的发布的设定动起来就很容易,就会导致保守性可持续性变高。
生成性程度高~productive~
shell脚本的话,通过软件包管理软件安装要使用的工具,或者通过git获取所需要的脚本,不同的人不同的组织有不同的选择方法。安装的话通过bundle install命令能将包括Capistrano本身,库文件以及扩展在内的所有囊括进来。
柔软度高~flexible~
是不是手上已经有为了发布而写的说明文档或者shell脚本了呢? 因为Capistrano任务本身只是“按照一定的顺序执行脚本”,这就和为了发布为写的脚本和操作流程是一样的。用nohup将操作放到后台运行,upstart/monit/daemontools/god之类的模块化,进程监视,进程ID的管理之类的脚本等等上述通过Ruby代码表象出来就行了。所以,如果与发布相关的东西,如果写进了Capistrano里,就很少有被限制的需求。
易读易写
能够用就像英语一样的精炼的DSL编写代码
而且,本地环境与服务器的操作在DSL语法上被明确的区分开来。首先本地环境上做什么,服务器环境生做什么,所谓的基于大局的脚本在写法上被很自然的被规约起来。
缺点
学习Capistrano需要花时间
确实需要花时间按,尤其是Capistrano 3 2013年8月刚刚发布。首先资料很少,就别说日语资料了。要记住新的东西本身就很困难,资料也少就难上加难了。
于是我写了这篇文章。如果大家能通过这篇文章很快的掌握Capistrano,我的目的也就达到了
首先要记住的是
粗分的话记住一下三个方面就够了
- Capistrano的模块
- Capistrano的工作流程
- Capistrano配置文件的书写方法
Capistrano模块
具体的代码和设定学习之前,如果能掌握Capistrano的根本的工作方式,从全局上理解Capistrano的话,学期里会轻松很多。这就是所谓的Capistrano模块。程序从文档开始,文章从目录开始读的话全局一了解理解起来就会很容易
理解Capistrano的最低要求是一下的概念的理解
- Capistrano
- 库文件
- 配置文件
- host
Capistrano
Capistrano大致由以下三部分组成
- cap命令
- Capistrano的库文件
- 默认的deploy任务
我们利用Capistrano的库文件和默认的deploy任务描述配置文件,然后通过cap命令执行。这样就能使Capistrano自动的进行一系列的操作。
库文件
Capistrano仅仅是一个框架,大家的应用开发时固有的deploy方法,服务器信息并没有被包含在里面。所以为了各自的自动化任务,必须要对Capistrano进行配置。
关于配置,有只是用一次的配置和使用多次的配置之分。再利用程度高的东西被称为插件,具体有以下两种。
- Ruby插件
- Capistrano扩展
配置文件
配置Capistrano的时候,只是用一次的设定(例如某个项目的服务器信息,某个项目固有的数据)不在上述的插件里面设置而在配置文件当中设置。具体有以下两种:
- config/deploy.rb
- config/deploy/任意阶段名称.rb
“deploy.rb”是发布的共通的设定和操作流程的书写的地。“任意阶段名称.rb”是线上环境,测试环境,开发环境等根据环境不同而不同的设定书写的地方。
host
发布的环境可以根据发布对象分为本地环境和服务器环境两种。必须要决定在什么样的环境执行什么样的命令。比如说本地环境build应用,在服务器发布应用,如果有这个认识的话就容易理解 多。
Capistrano的工作流程
为了使用Capistrano,为什么使用,用什么操作,要写什么代码好呢?让我们记住大体的流程吧。
- Capistrano的安装
- 建立配置文件的模型
- 修改配置文件
- 执行cap命令
以上的流程后面将做具体说明。
试着用Capistrano
1. Capistrano的安装
一键安装Capistrano
如果安装了RubyGems的话,执行以下命令就能安装Capistrano
如果执行gem须要root权限的话,别忘了加上sudo
如果是使用bundler的话,在Gemfile里面追加上以下然后运行bundle intall命令就能安装Capistrano
不用bundler的话上述可以忽略。
2. 建立配置文件的模型
Capistrano安装完了之后,建立Capistrano配置文件存放的地方,名字任意
然后运行cap intall命令,上述的配置文件就生成了。
3. 配置文件设定
执行cap install命令之后,上述的配置文件就自动生成了。修改了这些配置文件之后就能实现自动化。
4. 配置文件完成之后大致形态
为了简单的理解,现在把完成之后的配置文件作为示例放出来。这样就能朝着示例文件方向一步一步讲解设定方法。
config/deploy/test.rb
config/deploy.rb
5. Capistrano默认任务的消去
首先把Capistrano建立的默认任务全部删掉。
正如之前所述,Capistrano框架存在一些默认的任务。Capistrano默认任务包含了Capistrano本身推荐的一些deploy方法。例如通过链接将之前deploy的结果保存下来等等。
这里是否使用链接是开发的具体内容,在本片文章的范围之外。正因为如此,我们这里集中将如何将既存的手动作业利用capistrano自动化。
在现有Capistrano推荐的deploy方法基础上,将已有的deploy说明书改成Capistrano比较花费时间。与其相比,不如首先做个自己的deploy配置,如果有需要再慢慢改成Capistrano推荐的deploy流程。为什么这样呢,因为一下子需要改变的东西比较少而已。所以先将Capistrano默认任务全部删除。
调用Capistrano里定义的默认deploy任务,多个[deploy:子任务]将被定义。运行Capistrano里定义的默认deploy的情况下,重定义[deploy:子任务]就够了,非常方便。但这里没有必要座椅全部删掉。删除任务的代码是
实际上Capistrano 3里面定义了叫做Rake的build工具。Rake的作用就是通过命令行或者应用的调用,完成定义好的任务。这里运用Rake将已有的任务删除。
6. 书写类如 config/deploy/阶段名称.rb 的文件
阶段名称.rb是deploy每个阶段的设定。比如有以下这些
- 这个阶段的对象服务器
- 仅仅在这个阶段执行的任务
典型的是之前说 对象服务器的设定,将包含一下这些信息:
- host名
- 服务器角色
- 登录用户
- SSH的设定
- 其他与服务器相关的设定
夭折的那个这些,server这个关键字按照一下的写法来设定
这一行包含了以下信息:
- 将本地环境作为对象(执行Capistrano的主机)
- 赋予这个服务器 webserver的role
- 用vagrant用户登陆这个服务器
7. 书写config/deploy.rb
config/deploy.rb包含了各个阶段共同的设定,经常有的有如下。
- 应用名称
- repository的名称
- 利用的SCM
- 任务
- 各种各样的执行任务的命令
类似这样的设定通过DSL来书写,DSL是有类似如下定义的语法
- 设定的变更和取得
- 任务的定义
设定值的变更和取得
应用名,Repo的名字等等的设定通过set name, value的方式设定,fetch name的方式获取。
一旦在deploy.rb里面设定的值,就能在deploy.rb以及deploy/阶段名.rb的所有地方获取到。
任务的定义
各个任务通过task name do; ... end代码块的方式描述。
例如一个叫做:uptime的任务可以这样定义
这里将接着写task代码块中run_locally do; ... end呀,或者on 对象服务器 do; ... end部分。
前者run_locally块中写要在本地运行的命令
后者on代码块中写在服务器上执行的命令
on块重点对象服务器,能够在前面讲的[阶段名.rb]里面设定。
例如只对被赋予了叫做(:web)的角色的服务器作业的时候,可以向下面这样写。
通过execute执行命令
最后执行的命令写在run_locally或on块里面。
执行命令通过execute关键字。
例如要执行uptime命令写成下面这样
通过capture获取执行结果
执行了execute的场合,这个命令的标准输出就会被丢掉。如果要获取标准输出的时候就可以通过capture命令执行。
单纯的将execute换成capture,作为参数传递给capture的命令就会被执行,执行之后的标准输出文字就会被捕获。所以上面代码的意思就是将命令执行后的标准输出赋给output。
通过info输出到log
仅仅是获取可能没啥意义,尝试用Capistrano输出到log中。为了输出到log,根据run_locally和on块中的log等级,通过debug logmessage, info logmessage, warn logmessage, error logmessage, fatal logmessage等语法输出到log中。例如可以像如下这样使用info命令。
任务的定义:总结
像这样,在run_locally on块中可以使用execute capture info等多种方法。可以利用的方法在这个文件里面定义了。
这里面介绍的语法经常被使用到的是
就像这个命令的名字一样是将文件进行传输。使用的方法是在on 对象服务器 do; ... end块里面执行,这样就能将在Capistrano执行的服务器上的文件通过SCP传输到对象服务器上。
记住这些经常被使用到的命令:
execute capture info upload!
任务的例子
任务在本地环境上执行和服务器上执行的分开来管理。这里有一些例子。
在本地环境上执行的例子
deploy.rb和阶段名.rb上要写的有如下
- 源代码的获取
- build和压缩打包
源代码的获取
在应用发布之前,须要在某个时间获取源代码。
例如,如果是采用Git进行版本管理的项目,下面这段代码就能获取源代码:
在这个例子当中,定义了 :update的任务来获取代码,为了在本地环境上获取代码,代码的执行备方法run_locally里面。
在run_locally里面,根据有没有存在这个文件夹来判断是采用clone方法还是pull方法。
如果没有源代码,就根据设定好的repo_url通过git clone获取代码。保存的路径通过application设定。如果已经存在了源代码,,就先移动到代码目录下,再执行git pull命令更新之。
build和打包压缩
源码的编译在其他资源的操作之前进行,结果通过tarball,zip,war,debian package等等转换成可以再次发布的形式。
例如之前取得了代码的应用,在之后加入build和压缩打包的过程可以如下。
在这里利用叫做sbt的build和压缩工具,需要安装一个叫做sbt-pack的插件。(有兴趣请自己搜索)
按顺序来看。首先,通过task :achieve => :update do这样的写法:achieve任务=> update任务这个顺序。这是任务之间依赖关系的一种表现方式,就是指在执行achieve执行之前必须先执行update命令。实际上运行achieve任务的时候,Capistrano(严格意义上来说应该是作为Capistrano基础的Rake)考虑了这个关系,自动的执行了update之后执行achieve任务。根据这个关系,我们就能将任务之间依赖关系交给Capistrano来管理。
下面是run_locally块,一开始是讲刚才取得的源代码采用sbt和sbt-pack进行build。sbtbuild之后,生成的压缩包的相对路径在标准输出中输出来。
因为这个标准输出文件想要提交,所以将标准输出捕获,用正则表达式将相对路径抽出来转换成绝对路径。
并且,在捕获的结果中用/\e[\d{1, 2}m/这样的正则表达式将匹配到的字符串删除。这样就能消去escape序列。
根据sbt执行的环境,采用ANSI字符escape序列将输出加上颜色。这里为了之后提交,不但将相对路径抽出来,而且将escape序列也翻译过来。像这样就能将这些escape序列删除。
在代码例子的最后通过set将后续需要使用到的变量赋值,后面的任务可以通过fetch来获取。
需要在远程主机上做的事
远程主机需要执行的任务有以下这些类:
- 应用的上传和安装
- 应用的启动和停止
- web server,load balance等等操作
- 监视开始和停止
本次就最基本的应用的上传,安装,启动以及停止来做说明。
应用的上传,安装
为了能使应用启动,首先应该将应用上传。
例如,前面所述的Finagle应用的上传就可以这样做。
应用的启动和停止
根据实施环境采用的框架,web server和应用分开来启动的情况也是有的。
例如:
- 有java,node命令之类是应用启动的场合
- play framwork finagle akka等框架的组合的服务器也存在
例如,之前的upload install之后,启动应用可以像下面这么写
利用tar命令将压缩包解压之前都与之前的upload build一样。
在这之后,压缩包的包含的/bin目录下的应用名加版本号的可执行文件简单的找出来执行。
例如,project_dir变量里面传入可执行文件所在文件夹的路径。
然后,launch变量里传入可执行文件的路径,最后用nohup命令来执行脚本。
使用nohup是为了让脚本持续执行。
采用Capistrano是通过ssh链接服务器,之后连接会断开。之后会向启动脚本发出SIGHUP信号,启动脚本就会结束。为了防止这个采用nohup。
到这里还没有结束,忘了什么没有?
实际上到这里的例子,是基于应用一开始发布时候的的设定而写的。发布完成,开始运行的应用,首先要将其停止。停止应用的一个方法就是杀死进程。进程可以通过进程ID来区分。但是前一次启动的进程ID究竟是什么呢?
实际上在前一次进程启动的时候,进程ID已经被保存下来了。你注意到deploy任务的最后echo $! > RUNNING_PID命令了吗?这个命令将启动之后的进程ID通过文件保存下来了。
利用RUNNING_PID在第二次发布系统的时候将应用先停止。代码如下
在on_roles块中加入了begin; ... ; rescue; ... end追加了块。
这个里面只有当应用文件夹中的保存用进程ID的文件存在的时候才有效。
例子总结
上述的给出了大致的发布脚本的书写方法和流程。
根据这个继续读下面的完成形态的脚本。
config/deploy/test.rb
config/deploy.rb
完成形态
到这里为止说明的使用DSL的示例如下
使用叫做Finagle的框架,最简单的发布设定。
利用Vagrant登录到虚拟机器上,在虚拟机上登录取得Finagle的源代码,然后build install start
config/deploy/test.rb
config/deploy.rb
Cap命令的执行
到这里为止完成的config/deploy/阶段名称.rb(这次的阶段名称是test)以及config/deploy.rb(定义了多个任务),利用这些执行Capistrano。
执行Capistrano是采用没前面说过的cap命令。
cap命令的第一个与config/deploy/test.rb的test对应,第二个参数是执行的任务名称,与task: deploy中的deploy对应。
第一次运行的结果如下
服务器状态的确认
应用安好的通过ID为6908的进程启动起来。
实际上与服务器通信试试
再次运行脚本的时候,只是利用了上次的PID先杀掉进程在执行同样的任务而已。
又长又臭。。
这次简单的以Finagle为例说明了web应用的发布方法。
注意到没有,实际上只是在runlocally和on的块中写入了shell命令。使用任意的命令就能完成各种任务。另外这次省略说明的是Capistrano里面能写任意的Ruby代码。
总结
- 手动deploy需要花费时间
- 自动化需要学习的时间
- Capistrano能完成自动化任务
- 为了减少Capistrano的学习时间,简单说明了Capistrano的基础
- 举了个例子说明了应用的发布脚本
- 根据这个例子,能完成各种各样的自动化任务。
其他
这次说明到Capistrano为止,今后将以以下为话题继续介绍:
- plugin的使用方法
- 使用Play framework等实际的发布方法
- 不停止的deploy(rolling deploy)
编外
Capistrano的标准流程
Capistrano有一套自己的标准的发布流程,在这套发布流程里面,发布的是按照下面的步骤做的:
deploy:starting - start a deployment, make sure everything is ready
deploy:started - started hook (for custom tasks)
deploy:updating - update server(s) with a new release
deploy:updated - updated hook
deploy:publishing - publish the new release
deploy:published - published hook
deploy:finishing - finish the deployment, clean up everything
deploy:finished - finished hook
在hook的部分,你可以定义自己的部分,同样,在回滚的时候也有自己的一套流程,
deploy:starting
deploy:started
deploy:reverting - revert server(s) to previous release
deploy:reverted - reverted hook
deploy:publishing
deploy:published
deploy:finishing_rollback - finish the rollback, clean up everything
deploy:finished
同样可以在hook里面写上自己的发布脚本来完成自定义的发布。
在实际过程中,Capistrano经常与jenkins等CI系统协同工作,来完成自动或者半自动的发布工作。