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

lyfi2003 · 发布于 2015年05月12日 · 最后由 chinakr 回复于 2017年01月31日 · 6143 次阅读
121
本帖已被设为精华帖!

写在前面的话

很早大家都有讨论过 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 中, 可以方便回滚代码. 理解这个后, 方便配置的时候路径的调整.

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

共收到 43 条回复
341

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

15

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

17945

下次争取尝试下mina!

273

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

16576

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

9442

又出新品啦!赞

6571

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

121

#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, 用热启动的方式就好了.

121

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

4277

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

121

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

15

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

3

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

3

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

121

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

4584

#15楼 @lgn21st 回不去。。。

Eda824

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

5984

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

3

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

A87c18

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

121

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

3

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

121

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

16154

如果使用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;
}

11307

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

145

版本回退大家怎么做的

10775

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

10775

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

121

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

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

5759

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

845

#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
96

mian的多主机部署是个硬伤

1968

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

130

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

D77582

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

8528

timeout 180 💀

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

414

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

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

6574

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

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