Rails 中自动布署工具 mina 的经验谈

rails

自动布署工具的意义

在敏捷开发中, 如果说自动化测试是它的一条腿, 自动化布署就是它的另一条腿, 缺一不可.

Rails 在 assets pipeline 的支援下, 是拥有着到目前为止最佳的布署方案, 布署复杂度就相对较高, 但如果手工来处理, 太繁琐了.

所以说, 自动布署在 Rails 中, 甚为有用.

下面先来 PK 下 Rails 中自动布署工具.

mina VS Capistrano

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 做成延迟加载.

自动发布最佳实践

  1. 无论何时何地, 可以快速回滚

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 中, 可以方便回滚代码. 理解这个后, 方便配置的时候路径的调整.

  1. 配置文件是链接过来, 而不是写死的

shared 中存放所有配置文件, 并软链到 current 中.

这在 mina 上是默认非常简单的, 只要以下配置:

set :shared_paths, ['config/database.yml', 'config/application.yml', 'log', 'tmp/sockets', 'tmp/pids', 'public/uploads']

然后会自动软链接过去.

  1. 保持环境是独成一体的

我们经常会将两套环境放在一个 server 上, 所以保持环境独立是非常重要的, 主要是指如果有依赖 redis 之类的, 要设定好命名空间.

插件生态

我个人偏好使用 rbenv + unicorn + nginx 来布署 Rails 应用. 常用的组件如下:

  1. 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
  1. mina/rbenv , mina/whenever

内置插件, 非常方便.

  1. mina-sidekiq

更新与重启 sidekiq 使用

自定义插件

mina 编写自己的插件非常简单, 只要是符合 Rake 的 task 就可以自动加入, 有几个 API 说明一下:

  1. queue

这个命令可以将你自己的插件代码插入到整个发布脚本中. queue! 只是详细模式.

  1. 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

选择 mina 还是 Capistrano

实际上 Capistrano 的生态环境更加良好, 但 mina 设计超级简单.

如果你像我一样, 对任何事情都想必须弄明白, 而且理解简单这个理念的重要性:

如无必要, 勿增实体.

那么强烈建议你选用 mina, 无论是发布速度, 还是未来的自动布署扩展性都会掌控在你的手中.

如果非常在意生态环境, 建议一定要选择 Capistrano, 作为 Rails 的发布工具, mina 可以与 Capistrano 叫板, 但 Capistrano 不仅如此, 它还是一个很成功的运维工具, 其设计理念, 复杂度都上升了一个维度.

这个时候, 就建议你试试 Capistrano.

但无论如何, 都强烈希望你能重视自动发布.

也许第一次, 你花了几天才把它调试成功, 但以后每次, 它都将节省你大量的时间.

关于真正的代码持续发布的特性( 本质上就是 webhook + hook system ), 留在以后的文章中介绍.

发表于 2015.05.12