很早大家都有讨论过 mina, 现在再来写一篇相关文章会有过旧之嫌,不过本文定位在实践与经验,应该还好。
所以本文是好几个 Rails 项目开发经历后的布署经验总结,欢迎各路高手拍砖。
在敏捷开发中,如果说自动化测试是它的一条腿,自动化布署就是它的另一条腿,缺一不可。
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, '[email protected]: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, '[email protected]: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
将命令带上输出,方便测试。
可以看一个例子:
除此之外,我推荐一些方便的小贴士出来:
# 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 ), 留在以后的文章中介绍。