capistrano 自动化项目部署

一、什么是capistrano

A remote server automation and deployment tool written in Ruby

ruby写的自动化集群部署工具,官网:http://www.capistranorb.com/  源码: https://github.com/capistrano/

二、基本原理

原理很简单,就是利用ssh。ssh上服务器—>执行脚本—>返回。虽然简单,但是capistrano做了很多事情,让你比自己写ssh容易太多,而且内置了执行事务,回滚操作。通过capi部署,它会在服务器上维护每次release的版本列表,很方便实现rollback操作。

说到ssh ,就有必要聊到shell的几种模式: non-login  login non-interactive interactive,区别可以google下。capi默认是non-login,non-interactive。原因也提到了,这样会少加载很多不必要的服务器的环境变量,而且也是减少会服务器端环境配置的影响。

服务端环境的变化对capi的来说,是不关心的,也不应该关心。但是这种模式也给脚本编写带来一些不便,后面会提到。

三、安装

  1. gem install capistrano -v 2.15.5
  2. gem install capistrano-ext
capistrano-3.0最近刚发布,网上资料比较少,目前用的是2.15版本。

四、目录结构

创建一个目录,cd至目录执行capify .  就会完成工具的初始化。
  1. admin@ubuntu:/tmp$ mkdir test
  2. admin@ubuntu:/tmp$ cd test/
  3. admin@ubuntu:/tmp/test$ ls
  4. admin@ubuntu:/tmp/test$ capify .
  5. [add] writing ‘./Capfile’
  6. [add] making directory ‘./config’
  7. [add] writing ‘./config/deploy.rb’
  8. [done] capified!

五、部署流程

部署只需要三部:
  1. 1. cap deploy:setup                        #在服务器创建必要的目录,用来存放每次的release记录等
  2. 2. cap deploy:check                        #检查一些目录是否存在
  3. 3. cap deploy                              #deploy入口
  4.          deploy:update                     #
  5.                deploy:update_code          #
  6.                       strategy.deploy!     #根据参数更新代码,保存release
  7.                       deploy:finallize_update  #更新文档的时间等
  8.          deploy:create_synlink                 #将current目录ln至最新的release
  9.          deploy:restart                        #重启服务
  1. admin@ubuntu:~/capistrano/dea$ cap deploy
  2.     triggering load callbacks
  3.   * 2013-10-14 17:58:40 executing `development’
  4.     triggering start callbacks for `deploy’
  5.   * 2013-10-14 17:58:40 executing `multistage:ensure’
  6.   * 2013-10-14 17:58:40 executing `deploy’
  7.   * 2013-10-14 17:58:40 executing `deploy:update’
  8.  ** transaction: start
  9.   * 2013-10-14 17:58:40 executing `deploy:update_code’
  10.     updating the cached checkout on all servers
  11.     executing locally: “git ls-remote https://code.jd.com/dea_v2 master”
  12.     command finished in 688ms
  13.   * executing “if [ -d /tmp/jae/dea/shared/cached-copy ]; then cd /tmp/jae/dea/shared/cached-copy && git fetch -q origin && git fetch –tags -q origin && git reset -q –hard 08a647586cf4285019ced6ebde34d427ff65214a && git submodule -q init && git submodule -q sync && export GIT_RECURSIVE=$([ ! \”`git –version`\” \\< \”git version 1.6.5\” ] && echo –recursive) && git submodule -q update –init $GIT_RECURSIVE && git clean -q -d -x -f; else git clone -q -b master https://code.jd.com/dea_v2 /tmp/jae/dea/shared/cached-copy && cd /tmp/jae/dea/shared/cached-copy && git checkout -q -b deploy 08a647586cf4285019ced6ebde34d427ff65214a && git submodule -q init && git submodule -q sync && export GIT_RECURSIVE=$([ ! \”`git –version`\” \\< \”git version 1.6.5\” ] && echo –recursive) && git submodule -q update –init $GIT_RECURSIVE; fi”
  14.     servers: [“10.168.1.12”]
  15. Password:
  1. command finished in 1778ms
  2.     copying the cached version to /tmp/jae/dea/releases/20131014095846
  3.   * executing “cp -RPp /tmp/jae/dea/shared/cached-copy /tmp/jae/dea/releases/20131014095846 && (echo 08a647586cf4285019ced6ebde34d427ff65214a > /tmp/jae/dea/releases/20131014095846/REVISION)”
  4.     servers: [“10.168.1.12”]
  5.     [10.168.1.12] executing command
  6.     command finished in 88ms
  7.   * 2013-10-14 17:58:46 executing `deploy:finalize_update’
  8.   * executing “chmod -R — g+w /tmp/jae/dea/releases/20131014095846 && rm -rf — /tmp/jae/dea/releases/20131014095846/public/system && mkdir -p — /tmp/jae/dea/releases/20131014095846/public/ && ln -s — /tmp/jae/dea/shared/system /tmp/jae/dea/releases/20131014095846/public/system && rm -rf — /tmp/jae/dea/releases/20131014095846/log && ln -s — /tmp/jae/dea/shared/log /tmp/jae/dea/releases/20131014095846/log && rm -rf — /tmp/jae/dea/releases/20131014095846/tmp/pids && mkdir -p — /tmp/jae/dea/releases/20131014095846/tmp/ && ln -s — /tmp/jae/dea/shared/pids /tmp/jae/dea/releases/20131014095846/tmp/pids”
  9.     servers: [“10.168.1.12”]
  10.     [10.168.1.12] executing command
  11.     command finished in 21ms
  12.   * 2013-10-14 17:58:46 executing `deploy:create_symlink’
  13.   * executing “rm -f /tmp/jae/dea/current &&  ln -s /tmp/jae/dea/releases/20131014095846 /tmp/jae/dea/current”
  14.     servers: [“10.168.1.12”]
  15.     [10.168.1.12] executing command
  16.     command finished in 8ms
  17.     triggering after callbacks for `deploy:create_symlink’
  18.   * 2013-10-14 17:58:46 executing `deploy:bundle_install’
  19. Are you sure to bundle install (y):
  20.   * 2013-10-14 17:58:49 executing `deploy:link_config_file’
  21.   * executing “cd /tmp/jae/dea/current && rm config/dea.yml && ln -s /tmp/jae/dea/config/dea.yml config/dea.yml”
  22.     servers: [“10.168.1.12”]
  23.     [10.168.1.12] executing command
  24.     command finished in 9ms
  25.  ** transaction: commit
  26.   * 2013-10-14 17:58:49 executing `deploy:restart’
  27. Are you sure to restart dea(y): y
  28.   * 2013-10-14 17:58:51 executing `deploy:stop’
  29.   * executing “sudo -p ‘sudo password: ‘ kill -9 `cat /tmp/dea_ng.pid`”
  30.     servers: [“10.168.1.12”]
  31.     [10.168.1.12] executing command
  32.     command finished in 14ms
  33.   * 2013-10-14 17:58:53 executing `deploy:start’
  34.   * executing “sudo -p ‘sudo password: ‘             nohup /tmp/jae/dea/current/bin/dea /tmp/jae/dea/current/config/dea.yml >/dev/null < /dev/null 2>&1 &\\\n            sleep 3”
  35.     servers: [“10.168.1.12”]
  36.     [10.168.1.12] executing command
  37.     command finished in 3009ms

六、脚本示例

dea的部署脚本:
  1. #require ‘bundler/capistrano’           #添加之后部署时会调用bundle install, 如果不需要就可以注释掉
  2. require “capistrano/ext/multistage”     #多stage部署所需
  3. set :stages, [“development”,”part1″, “part2″,”part3”]  #利用stage将dea分为三个部分来操作,避免全部dea同时重启造成的服务不可用
  4. set :default_stage, “development”  #测试环境
  5. set :application, “dea”   #应用名称
  6. set :scm,:git
  7. set :repository,  “https://code.jd.com/dea_v2”
  8. set :branch,”master”
  9. set :git_enable_submodules,1   #有子模块,要加上这个配置
  10. set :git_enable_recursion,1    #有多重子模块
  11. set :keep_releases, 5          #只保留5个备份
  12. set :deploy_to, “/tmp/jae/#{application}”  #部署到远程机器的路径
  13. set :user, “admin”              #登录部署机器的用户名
  14. default_run_options[:pty] = true          #pty: 伪登录设备
  15. #default_run_options[:shell] = false      #Disable sh wrapping
  16. set :normalize_asset_timestamps, false
  17. set :deploy_via, “remote_cache”           #本地cache,第一次全量,后面git pull就是增量了
  18. set :default_environment,{                #环境变量,以key value的方式写上,因为non-login的关系,默认为会载入系统的环境变量
  19.   “GIT_SSL_NO_VERIFY” => ‘1’
  20. }
  21. set :use_sudo, false                          #执行的命令中含有sudo, 如果设为false, 用户所有操作都有权限
  22. set :runner, “admin”                          #以admin用户启动服务
  23. set :config_file, “dea.yml”
  24. namespace :deploy do
  25.     after “deploy:create_symlink” , “deploy:bundle_install”
  26.     after “deploy:create_symlink”,”deploy:link_config_file”
  27.     #由于每个服务器的配置文件不一样,dea没有跟rails一样,在一个config中写上不同环境的值。这里的解决办法是每个dea的配置文件只保存在本地,
  28.     #存放在#{deploy_to}/config/dea.yml,部署的时候用软链接
  29.     desc “config file is unique on each server,we store it on deploy_to/config/xx.yml,use link to make it work”
  30.     task :link_config_file,roles => :app do
  31.           run “cd #{deploy_to}/current && rm config/#{config_file} && ln -s #{deploy_to}/config/#{config_file} config/#{config_file} “
  32.     end
  33.     #不是每次部署都要bundle install,之所以不用capistrano/bundle,它会将gem安装在其它地方,破坏了原来工程的和谐
  34.     desc “it’s optional to bundle install everytime,unless modify the Gemfile etc.”
  35.     task :bundle_install, roles => :app do
  36.        answer = Capistrano::CLI.ui.ask(“Are you sure to bundle install (y): “)
  37.        if answer == “y”
  38.           run “cd #{deploy_to}/current && bundle install” do |channel,stream,data|
  39.             if data =~ /password/
  40.               answer = Capistrano::CLI.ui.ask(“Enter your password to install the bundled RubyGems to your system: “)
  41.               channel.send_data(answer + “\n”)
  42.             else
  43.                # allow the default callback to be processed
  44.                Capistrano::Configuration.default_io_proc.call(channel, stream, data)
  45.             end
  46.           end
  47.        end
  48.     end
  49.     #
  50.     desc “start the dea server,exclude dir server and warden”
  51.     task :start,roles => :app do
  52.        sudo <<-EOC
  53.             nohup #{deploy_to}/current/bin/dea #{deploy_to}/current/config/dea.yml >/dev/null < /dev/null 2>&1 &
  54.             sleep 3
  55.        EOC
  56.     end
  57.     #
  58.     desc “stop the dea server”
  59.     task :stop,roles => :app do
  60.        sudo “kill -9 `cat /tmp/dea_ng.pid`”
  61.     end
  62.     #
  63.     desc “restart dea”
  64.     task :restart ,roles => :app do
  65.        answer = Capistrano::CLI.ui.ask(“Are you sure to restart dea(y): “)
  66.        if answer==”y”
  67.          deploy.stop
  68.          sleep 2
  69.          deploy.start
  70.        end
  71.     end
  72.    #up and donw dir server
  73.    #up and donw warden
  74.    desc “migrate do nothing here”
  75.    task :migrate, roles => :app do
  76.    end
  77. end
如果想用 stage(即多个环境),可以在config文件夹下,再创建一个目录”deploy”,deploy下创建与stage同名的文件,如part1.rb,part2.rb。如dea,我的part1.rb其实只指定了一组服务器ip
  1. role :app,”ip1″,”ip2″,”ip3″,…….

有两个地方要说明,也是我在编写脚本时碰到的问题:

1. 服务器端的交互,capi提供的 sudo或其他方法,只是一次性交互,但如果在执行脚本过程中碰到需要交互的地方,如bundle install 一半要你输入密码。capi客户端也会提示你输密码,但输入后按回车就“卡”住了,没反应,这就是因为non-interactive引起的,capi只管登录–>执行脚本–>等待回显,登出返回,而且每个脚本一个来回。如
  1. run “cd /tmp”
  2. run “bin/dea config/dea.yml”

这就两个来回了,第二句执行的时候就不是在/tmp目录下,而是在/home/admin目录下

这时可以用 channel搞定,我的理解是在同一个session当中,可以实现交互的效果。
  1. task :bundle_install, roles => :app do
  2.      answer = Capistrano::CLI.ui.ask(“Are you sure to bundle install (y): “)
  3.      if answer == “y”
  4.         run “cd #{deploy_to}/current && bundle install” do |channel,stream,data|
  5.           if data =~ /password/
  6.             answer = Capistrano::CLI.ui.ask(“Enter your password to install the bundled RubyGems to your system: “)
  7.             channel.send_data(answer + “\n”)
  8.           else
  9.              # allow the default callback to be processed
  10.              Capistrano::Configuration.default_io_proc.call(channel, stream, data)
  11.           end
  12.         end
  13.      end
  14.   end

利用 run的 callback功能。

2. 当服务端有程序需要后台运行,一般的做法是加&就可以了。但是capi不行。必须再加上nohup才行。但是nohup+&在事实证明中也是不靠谱的。再次因为“capi只管登录–>执行脚本–>等待回显,登出返回” 。它执行完 nohup bin/dea config/dea.yml & 后立刻返回,就是登出了,就断了,程序还没来得及提交给后台运行就挂了。所以发必须sleep 几秒,让程序提交后台运行后再登出。
如果不sudo启动,可以用run
  1. run “(nohup #{deploy_to}/current/bin/dea #{deploy_to}/current/config/dea.yml  &) && sleep 3”

错误,因为capi会等待回显,傻傻地等,就是“卡”住了。所以一定要将nohup输出定向到某个地方,可以做到执行脚本的立刻返回。

  1. <pre name=”code” class=”java”>run  “(nohup #{deploy_to}/current/bin/dea #{deploy_to}/current/config/dea.yml >/dev/null < /dev/null 2>&1 &) && sleep 3″</pre>

这样才行。

如果用 sudo,把run换成sudo
  1. sudo  “(nohup #{deploy_to}/current/bin/dea #{deploy_to}/current/config/dea.yml >/dev/null < /dev/null 2>&1 &) && sleep 3”

错误,语法上通不过。这个方法在服务器上是这么执行的,会提示语法错误,用分号之类的都不行。可能shell掌握不太好,没有搞定。

  1. executing “sudo -p ‘sudo password: ‘ (nohup /export/servers/jae/dea/current/bin/dea /export/servers/jae/dea/current/config/dea.yml >/dev/null < /dev/null 2>&1 &) && sleep 3”

一个方法是写个shell文件,sudo 去执行shell文件,没有试过,但以下做法的效果是一样的:

  1. #
  2.     desc “start the dea server,exclude dir server and warden”
  3.     task :start,roles => :app do
  4.        sudo <<-EOC
  5.             nohup #{deploy_to}/current/bin/dea #{deploy_to}/current/config/dea.yml >/dev/null < /dev/null 2>&1 &
  6.             sleep 3
  7.        EOC
  8.     end

七、结束语

capistrano是很轻量级的,没有像其它集群部署工具那样,要在服务器端安装agent,如puppet等。协议也简单。用的是ssh。capistrano设计之初是为了解决集群部署rails的问题,发展到现在,基本可以部署很多主流的语言框架。capistrano也有很多第三方扩展,方便部署诸如apache,mongo等。详见:https://github.com/capistrano/capistrano/wiki
capistrano可以执行shell脚本,这就给了我们很多的自主空间,第三方扩展都是些包装好的东西,可以自己写shell,理论上什么都可以部署。

原文链接:http://blog.csdn.net/k_james/article/details/12711443

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注