在敏捷开发中, 如果说自动化测试是它的一条腿, 自动化布署就是它的另一条腿, 缺一不可.
Rails 在 assets pipeline 的支援下, 是拥有着到目前为止最佳的布署方案, 布署复杂度就相对较高, 但如果手工来处理, 太繁琐了.
所以说, 自动布署在 Rails 中, 甚为有用.
下面先来 PK 下 Rails 中自动布署工具.
Capistrano 是 Rails 中最常见的选择, 尤其是 Capistrano2. 它的工作原理如下:
即, 先针对 server 打开一个 ssh 隧道, 然后不断的发送命令给 server 执行, 每个命令是由本地 PC 生成的, 然后一一发送给
这样的优势在于, 命令更加容易通过 Ruby 脚本来控制, 易于编写更强大的插件, 维护更多的服务端实例.
而 mina 则不同, 它的特点就是更快, 工作原理类似于:
即, 本地直接生成完整的发布脚本( bash 脚本 ), 通过 ssh 隧道, 一起上传给 server 执行.
这样的优势就是非常快, 几分钟的发布过程, 只需要十几秒完成. 缺点是多环境与多服务端会有一些特别的注意事项.
如果你对上面的工具还不熟悉, 建议分别去官方网站看看.
下面我想将 mina 的生态作一些介绍, 这样, 才能用的放心与舒心.
先讲讲最为常见的多环境发布支持.
所谓多环境发布是指, 在开发过程中, 我们要发布代码到测试环境, 发布代码到生产环境, 甚至英文与中文两个环境等等.
我都希望一条命令就可以搞定发布:
mina staging deploy mina production deploy
mina 本质上只是一个基础脚本生成框架, 帮助我们快速生成对应的发布脚本, 完成自动上传 server 并执行的框架. 所以并没有对多环境发布提供官方支持.
但设计良好的系统, 扩展起来非常简单.
经过实践, 自己编写一个扩展可行, 但没有必要, 直接用 mina-multistage 即可,
对应的 deploy.rb 举例如下:
set :stages, %w(en zh) set :default_stage, 'zh' require 'mina/multistage' require 'mina/bundler' require 'mina/rails' require 'mina/git' require 'mina/rvm' # for rvm support. (http://rvm.io) require 'mina/unicorn' require 'mina_sidekiq/tasks' # Manually create these paths in shared/ (eg: shared/config/database.yml) in your server. # They will be linked in the 'deploy:link_shared_paths' step. set :shared_paths, ['config/mongoid.yml', 'config/application.yml', 'log', 'tmp', 'public/uploads', 'public/personal' ] task :environment do queue! %[source /usr/local/rvm/scripts/rvm] queue! %[rvm use 2.0.0] end
然后在 config/deploy/ 创建两个对应的文件:
en.rb:
set :domain, 'yafeilee.me' set :deploy_to, '/home/ruby/wblog_en' set :repository, 'git@github.com:windy/wblog.git' set :branch, 'master' set :user, 'ruby' set :unicorn_config, -> { "#{deploy_to}/#{current_path}/config/unicorn/en.rb" }
zh.rb
set :domain, 'yafeilee.me' set :deploy_to, '/home/ruby/wblog' set :repository, 'git@github.com:windy/wblog.git' set :branch, 'master' set :user, 'ruby' set :unicorn_config, -> { "#{deploy_to}/#{current_path}/config/unicorn/zh.rb" }
你可以尽情地为每个环境配置自己的变量. 之后就可以用:
# 发布英文版本 mina en deploy
# 发布中文版本 mina zh deploy
非常方便. 需要注意的是, 在一些要引用其他变量的地方, 需要用 lambda 做成延迟加载.
mina 是通过以下目录结构实现回滚的:
/var/www/flipstack.com/ # The deploy_to path |- releases/ # Holds releases, one subdir per release | |- 1/ | |- 2/ | |- 3/ | '- ... |- shared/ # Holds files shared between releases | |- logs/ # Log files are usually stored here | `- ... '- current/ # A symlink to the current release in releases/
通过 current 软链接到 releases/x 中, 可以方便回滚代码. 理解这个后, 方便配置的时候路径的调整.
shared 中存放所有配置文件, 并软链到 current 中.
这在 mina 上是默认非常简单的, 只要以下配置:
set :shared_paths, ['config/database.yml', 'config/application.yml', 'log', 'tmp/sockets', 'tmp/pids', 'public/uploads']
然后会自动软链接过去.
我们经常会将两套环境放在一个 server 上, 所以保持环境独立是非常重要的, 主要是指如果有依赖 redis 之类的, 要设定好命名空间.
我个人偏好使用 rbenv + unicorn + nginx 来布署 Rails 应用. 常用的组件如下:
mina-unicorn
默认使用热布署方案, 非常方便:
# config/unicorn/zh.rb app_path = File.expand_path( File.join(File.dirname(__FILE__), '..', '..')) worker_processes 1 timeout 180 listen '/tmp/unicorn_zh.sock' pid "#{app_path}/tmp/pids/unicorn.pid" stderr_path "log/unicorn.log" stdout_path "log/unicorn.log" before_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! end old_pid = "#{server.config[:pid]}.oldbin" if File.exists?(old_pid) && server.pid != old_pid begin Process.kill("QUIT", File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH # someone else did our job for us end end end after_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection end end before_exec do |server| # fix hot restart Gemfile ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile" end
mina/rbenv
, mina/whenever
内置插件, 非常方便.
mina-sidekiq
更新与重启 sidekiq 使用
mina 编写自己的插件非常简单, 只要是符合 Rake 的 task 就可以自动加入, 有几个 API 说明一下:
queue
这个命令可以将你自己的插件代码插入到整个发布脚本中. queue!
只是详细模式.
echo_cmd
将命令带上输出, 方便测试.
可以看一个例子:
mina-unicorn-tasks
除此之外, 我推荐一些方便的小贴士出来:
# add this after config/deploy.rb desc "Shows logs." task :logs do queue %[cd #{deploy_to!}/current && tail -f log/production.log] end
使用 mina logs
来实时查看生产环境的日志, 非常方便.
desc "Display the unicorn logs." task :unicorn_logs do queue 'echo "Contents of the unicorn log file are as follows:"' queue "tail -f #{deploy_to}/current/log/unicorn.log" end
使用 mina unicorn_logs
来实时查看 unicorn 日志.
如果遇到问题, 可以使用:
mina deploy -v
来详细查看对应的命令执行过程.
如果遇到死活排查不出的问题, 可以用 mina deploy -S
来查看整个发布脚本生成情况, 然后手动到发布环境去执行对应的命令.
我的开源博客系统, 也采用的 mina, 可以做为本篇文章的参考实例: wblog mina example
实际上 Capistrano 的生态环境更加良好, 但 mina 设计超级简单.
如果你像我一样, 对任何事情都想必须弄明白, 而且理解简单这个理念的重要性:
如无必要, 勿增实体.
那么强烈建议你选用 mina, 无论是发布速度, 还是未来的自动布署扩展性都会掌控在你的手中.
如果非常在意生态环境, 建议一定要选择 Capistrano, 作为 Rails 的发布工具, mina 可以与 Capistrano 叫板, 但 Capistrano 不仅如此, 它还是一个很成功的运维工具, 其设计理念, 复杂度都上升了一个维度.
这个时候, 就建议你试试 Capistrano.
但无论如何, 都强烈希望你能重视自动发布.
也许第一次, 你花了几天才把它调试成功, 但以后每次, 它都将节省你大量的时间.
关于真正的代码持续发布的特性( 本质上就是 webhook + hook system ), 留在以后的文章中介绍.