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

lyfi2003 · 2015年05月12日 · 最后由 lengcb 回复于 2018年05月03日 · 12423 次阅读
本帖已被管理员设置为精华贴

写在前面的话

很早大家都有讨论过 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, '[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 做成延迟加载。

自动发布最佳实践

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

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

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

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

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

然后会自动软链接过去。

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

我们经常会将两套环境放在一个 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

2. mina/rbenv , mina/whenever

内置插件,非常方便。

3. mina-sidekiq

更新与重启 sidekiq 使用

自定义插件

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

1. queue

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

2. 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 ), 留在以后的文章中介绍。

来自 WinDy's Blog

很棒,这是重点,"也许第一次,你花了几天才把它调试成功,但以后每次,它都将节省你大量的时间。"

mina 部署很快,也用了很久了,不过碰到个烦心事,assest_path 目录内容不动他就会跳过 assets_precompile,用 js.erb, 就比较悲催了

下次争取尝试下 mina!

mina 部署脚本配置超级简单,生态圈要有的都有。https://github.com/div/mina-stack.git 这个很全面,可以仿照开发自己的脚本,也很简单。

比 capistrano 快,但速度差距没有那么夸张,特别是国内服务器网络很快的情况下,速度的差距就更小了,其实 capistrano 用在网络传输的时间也不是很多,更多的时间还是在服务器执行命令花费的

又出新品啦!赞

mina 使用了半年,其他都挺好,唯一让我苦恼的是 mina 在重启服务器的时候第一次,停止 unicorn 总是失败,总需要执行两次 mina deploy 才能重启成功,大家有遇到跟我一样的情况吗?

#4 楼 @quakewang 结论非常中肯,对于小规模应用来说,mina 很方便易用。对于它的相关插件,关注的人不多,成熟度还不够,有时候需要自己去折腾。

对于 mina-unicorn 重启的问题,我的解决方式与 @saberma 一致:在 unicorn 里面加上一个

before_exec do |server| # fix hot restart Gemfile
  ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile"
end

#8 楼 @a167651202 可以试试 mina-unicorn, 用热启动的方式就好了。

#2 楼 @huobazi 可以试试自己写一个对应的 task fix 一下。

mina 使用的挺方便,遇到唯一一个问题就是shared_paths下的文件需要改动,得上去手动改一下,不知道有没有更 mima 的方法可以根据自定义的某种规则自动修改软链的配置文件,像 crontab update 那样

#12 楼 @dddd1919 是直接与 current 目录的 path 一一对应的,这些建立软链接的命令都可以在 mina 的源码很容易找到。如果需求不满足,就可以自己写。

#11 楼 @lyfi2003 fix 就把 mina 改成每次都做 assets_precompile 了,也挺不合适的。

自己全面切换到 mina 部署已经好几年了,遇到各种问题,不过都能解决,应该是再也回不去 cap 了。

看到上面大家都在讨论 unicorn 跟 mina 的问题,估计像我一样因为嫌部署麻烦,所以选择 Passenger 的是少数吧?

#16 楼 @lgn21st 一个 nginx 反代多个 rails app 的话,unicorn 还是方便点。不过现在 puma 也很好哈。

#15 楼 @lgn21st 回不去。。。

#16 楼 @lgn21st 我比你还懒,一直用 passenger 和 cap,都没考虑换过。passenger 的开源版本最不好的地方是不支持 zero downtime deploy,不过我也不在乎这点时间。。

用的是 mina-rails-unicorn-nginx-god,无论是 mina 还是 cap 都需要对 shell 比较了解,不然调试会遇到很多困难。

#17 楼 @lyfi2003 在 production 服务器上好像很少用一个 nginx 带多个 app,而是反过来,根据应用需要起几个 instances 来估算需要的内存用量,然后 double 一下当作 buffer,然后根据需要开不同配置的 VPS。

多环境发布自己传参数也挺方便的,multistage 不要也行。mina 不足之处就是并行部署,@quakewang提到的这个方法也想过,但是因为对并行暂时没有太多需求也就没考虑。没有用过 cap,不知道 cap 是如何解决并行部署的问题的。

#23 楼 @lgn21st 我经常会布署一些小站,挂在一起方便,这才用 nginx + unicorn 布署很多小站。要是主要用来运营的,就要像你说的,分开布署会更靠谱。

#25 楼 @lyfi2003 可是 Passenger 也支持挂很多小应用啊,而且貌似还挺方便的。

#26 楼 @lgn21st 也能支持,我当时的结论是,最好的办法也是把 rails container 独立出来,这时用 passenger 还是 unicorn 都可以了。布署难度也就相当了。unicorn 热布署很吸引人:)

如果使用mina-puma的话,我部署过,下面就贴一段 puma 的配置文件 config/puma/staging.rb

app_path = File.expand_path( File.join(File.dirname(__FILE__), '..', '..'))
# Change to match your CPU core count
workers 1

# Min and Max threads per worker
threads 1, 6

# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env

# Set up socket location
bind "unix://#{app_path}/tmp/sockets/pumactl.sock"

# Logging
stdout_redirect "#{app_path}/log/puma.stdout.log", "#{app_path}/log/puma.stderr.log", true

# Set master PID and state locations
pidfile "#{app_path}/pids/puma.pid"
state_path "#{app_path}/tmp/sockets/puma.state"
activate_control_app

on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_path}/config/database.yml")[rails_env])
end

至于 nginx 上的配置跟 unicorn 是一样的,不过是改了一下 upstream 中 socket 的位置

upstream xy_staging {
    # Path to Unicorn SOCK file, as defined previously
    # server unix:/tmp/unicorn.xy_staging.sock fail_timeout=0;
    server unix:///home/hfpp2012/xy_staging/current/tmp/sockets/pumactl.sock fail_timeout=0;
}

部署回退时数据库的数据怎么处理呢?比如有 seed.rb 插入的数据,seed.rb 不分版本,不能回退

版本回退大家怎么做的

多机器部署的做法跟 quakewang 的差不多。不过如果要在特定服务器执行特定脚本就显得比较麻烦了。主要的一个原因在于:invoke :deploy 无论 invoke 多少次都只算 invoke 一次,而加了 reenable 参数也没什么用,因为 :deploy 中还 invoke 了其他 task,并且其他 task 并不会继承 reenable

现在解决的办法就是,根据特定的需求,分开写成不同的 task,再写个脚本跑全部

#29 楼 @kevinclcn #30 楼 @suupic 回滚直接用 ln -s current ../realease/xx, 然后重启 rails container 就可以了。

如果有数据也需要回滚,那需要单独写个 task, 但这种情况非常少。

#17 楼 @lyfi2003 来个续,介绍一下 Mina+Puma?呵呵

#4 楼 @quakewang mina gateway 可以通过 ~/.ssh/config 配置来解决,如下配置。mina 里的 domain 配置 target 就好了。

Host gateway
    HostName gateway.address
    User root

Host target
    HostName target.address
    User root
    ForwardAgent yes
    ProxyCommand ssh -q gateway netcat %h 22

mian 的多主机部署是个硬伤

想简单就用 mina, cap 没理理不好懂,尤其是 cap3, 很多插件未有,要自己写好多东西。

「一开始我是用 mina 的,但现在我要换 capistrano 了,因为要部署新机器了」

mina 比 cap 其实除了表面看上去快一点,格式好看点,并没有其他好处。带来的坑也不少。 比如,用户比 cap 少太多,很多东西没有现成的方案得自己写。个人用了一段时间,属于“然而并没有什么卵用”的系列工具。

timeout 180 💀

webhook + hook system 关于这块,技术总监想要统一使用 jenkins,我在 gitlab ci 和 gitlab runner 的路上走着

Capistrano 3 已經是類似的做法了(丟腳本上遠端去跑) 而且 Mina 好像還不支援多主機?

我現在都是用 Capistrano 搭配 docker 在跑,deploy task 都是呼叫 docker-compose up -d 不一定是部署 Rails 專案了。

“布署”是不是应该改为“部署”?

看大家讨论的好热烈。学习部署没多久,用的是 puma+mina+nginx,准备把 god 加进去。用 unicorn+capistrano 失败了😓 在想要不要再倒腾下 unicorn 跟 capistrano

需要 登录 后方可回复, 如果你还没有账号请 注册新账号