部署 利用 Mina 自动部署 Rails + Sidekiq + Unicorn

luolinae86 · July 27, 2015 · Last by luolinae86 replied at June 22, 2018 · 9639 hits
Topic has been selected as the excellent topic by the admin.

社区上面关于自动部署的文章比较完整和齐全了,在此,我仅将自己最近用 mina 部署 Rails+sidekiq+unicorn 的整个过程,做一个总结,希望对新手做一个指引,也抛砖引玉,希望更多高手发表自己的部署经验,共勉。

部署方案选择

Rails 自动部署,主流的方案有:

  • Capistrano
  • Mina

刚开始选择了 capistrano,感觉比较折腾,最终选择了 mina。最后发现,选择 mina 是值的,特别是配合 mina-sidekiq,和 mina-unicorn,让整个部署更加简洁,高效。

部署步骤

Mina 地址https://github.com/mina-deploy/mina

引入 mina gem

在 Gemfile 中增加:

gem 'mina'

初始化 (mina init)

在相应的 Rails 工作目录,执行

$ mina init

mina init 命令会在 config 目录生成一个默认的配置文件 deploy.rb

引入 mina-sidekiq

项目中,运用到了 sidekiq 做异步队列:

https://github.com/Mic92/mina-sidekiq/blob/master/README.md

Gemfile 中增加

gem 'mina-sidekiq',:require => false 

引入 mina-unicorn

应用服务器用的 unicorn,使用 mina-unicorn,可以非常方便完成进程的启,停,重启

https://github.com/scarfacedeb/mina-unicorn/blob/master/lib/mina/unicorn/tasks.rb

Gemfile 中增加:

gem 'mina-unicorn', :require => false  

注:以下配置都是修改 config/deploy.rb 文件

配置目标主机

set :domain, '*.*.*.*'
set :deploy_to, '/data/project/science_read'
set :user, 'deployer'    
set :port, '3118'    
set :forward_agent, false

其中:domain 为目标主机 IP,deploy_to 为目标主机的部署路径,user 为用户名,port 为 ssh 的端口号。 注:请将自动部署机器的~/.ssh/id_rsa.pub,放置到目标机器的~/.ssh/authorized_keys 列表里面,从而完成自动登陆

设置 git 地址

set :repository, '[email protected]:hesheng/science_read.git'
set :branch, 'master'

设置 sidekiq 和 unicorn 进程 pid 保存地址

set :sidekiq_pid, "#{deploy_to}/tmp/pids/sidekiq.pid"
set :unicorn_pid, "#{deploy_to}/tmp/pids/unicorn.pid"

设置 rvm 的安装路径及 ruby 版本

set :rvm_path, '/usr/local/rvm/bin/rvm'
task :environment do
  invoke :'rvm:use[ruby-2.0.0@default]'
end

设置共享文件或目录

set :shared_paths, ['config/database.yml', 'config/yetting.yml','config/symmetric-encryption.yml','log']

以上设置了 log 目录,以及 database.yml,yetting.yml,symmetric-encryption.yml 为所有版本共享

设置目标主机目录及文件 (mina setup)

以下是执行 mina setup 命令时,在远程目录机执行的创建目录或者文件的操作

task :setup => :environment do
  queue! %[mkdir -p "#{deploy_to}/tmp/sockets/"]
  queue! %[mkdir -p "#{deploy_to}/tmp/pids/"]

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/log"]
  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/config"]

  queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"]
  queue! %[touch "#{deploy_to}/#{shared_path}/config/yetting.yml"]
  queue! %[touch "#{deploy_to}/#{shared_path}/config/symmetric-encryption.yml"]
  queue  %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml','yetting.yml', 'symmetric-encryption.yml'."]

增加部署命令 (mina deploy)

以下是执行 mina deploy 命令时,执行的任务操作,是整个远程部署的核心

desc "Deploys the current version to the server."
task :deploy => :environment do
  to :before_hook do
    # Put things to run locally before ssh
  end
  deploy do
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'

    to :launch do
      # sidekiq stop accepting new workers
      invoke :'sidekiq:quiet'
      invoke :'sidekiq:restart'
      invoke :'unicorn:restart'
    end
  end
end             

task:deploy,首先从 repository 的 branch 中 clone 一份代码到 deploy_to 目录,然后生成连接目录及文件,执行 bundle, db_migrate,assets_precompile 命令 ,最后会执行 launch,里面的 sidekiq 异步任务的重启,以及 unicorn 进程重启。

修改共享文件

编辑,database.yml,yetting.yml,以及 symmetric-encryption.yml 文件,以使其符合正式部署的需求。

修改 config/unicron 配置文件

listen "/data/project/science_read/tmp/sockets/unicorn.sock", :backlog => 64
pid "/data/project/science_read/tmp/pids/unicorn.pid"

之所以这样修改,是因为,自动部署后,APP_HOME = Rails.root 会从未部署时候的/data/project/science_read/变成/data/project/science_read/current

执行 mina setup 远程部署命令

$ mina deploy

设置 crontab 定时远程部署

在实际的项目中,可能会定时完成自动部署,比如每小时的 0 分钟时部署一次,crontab 任务

0 * * * * cd /data/project/mina_do_not_remove/science_read && mina deploy

整个 config/deploy.rb 部署脚本

require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rvm'    
require 'mina/unicorn'
require "mina_sidekiq/tasks"

set :domain, '*.*.*.*'
set :deploy_to, '/data/project/science_read'
set :user, 'deployer'         
set :port, '3118'            
set :forward_agent, false

#设置git地址及分支
set :repository, '[email protected]:hesheng/science_read.git'
set :branch, 'master'

set :rvm_path, '/usr/local/rvm/bin/rvm'

set :shared_paths, ['config/database.yml', 'config/yetting.yml','config/symmetric-encryption.yml','log']

#设置sidekiq的进程保存地址
set :sidekiq_pid, "#{deploy_to}/tmp/pids/sidekiq.pid"


#设置unicorn pid 及 进程启动环境
set :unicorn_pid, "#{deploy_to}/tmp/pids/unicorn.pid"
set :unicorn_env, 'production'

task :environment do
  invoke :'rvm:use[ruby-2.0.0@default]'
end

task :setup => :environment do
  # unicorn and sidekiq needs a place to store its pid file
  queue! %[mkdir -p "#{deploy_to}/tmp/sockets/"]
  queue! %[mkdir -p "#{deploy_to}/tmp/pids/"]

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/log"]
  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/config"]

  queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"]
  queue! %[touch "#{deploy_to}/#{shared_path}/config/yetting.yml"]
  queue! %[touch "#{deploy_to}/#{shared_path}/config/symmetric-encryption.yml"]
  queue  %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml','yetting.yml', 'symmetric-encryption.yml'."]

  queue %[
    repo_host=`echo $repo | sed -e 's/.*@//g' -e 's/:.*//g'` &&
    repo_port=`echo $repo | grep -o ':[0-9]*' | sed -e 's/://g'` &&
    if [ -z "${repo_port}" ]; then repo_port=22; fi ]
end

desc "Deploys the current version to the server."
task :deploy => :environment do
  to :before_hook do
    # Put things to run locally before ssh
  end
  deploy do
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'

    to :launch do
      # sidekiq stop accepting new workers
      invoke :'sidekiq:quiet'
      invoke :'sidekiq:restart'
      invoke :'unicorn:restart'
    end
  end
end

主标题和二级标题不清晰。二级要用四个#号,才有区别。

@chenge 二级标题已经修改为 head 4

请问 mina 到底比 cap 快在哪些地方?

一些比较慢的操作是不可避免的呀。

比如每次部署最耗时间的部分 assets compile 是逃不过去的呀?

@xiaoronglv 我尝试过用 cap 部署,但比较麻烦,我就直接改用 mina 了,所以没有 cap 和 mina 的直接对比。 mina 配合 mina-sidekiq, mina-unicorn,整个部署起来的确非常简单,容易

-----> DB migrations unchanged; skipping DB migration
-----> Skipping asset precompilation
-----> Cleaning up old releases (keeping 5)
-----> Deploy finished
-----> Building
-----> Moving build to releases/2
-----> Build finished
-----> Launching
-----> Updating the current symlink
-----> Launching
-----> Stop sidekiq
       Sidekiq shut down gracefully.
-----> Start sidekiq
-----> Duplicating Unicorn...
-----> Done. Deployed v2
       Elapsed time: 7.26 seconds

从部署日志来看,如资源文件不变,那么会跳过预编译,整个部署时间在 10s 内。

-----> Skipping asset precompilatio

10 秒内。。。相比,cap 花的时间比较长

@pathbox 每次部署时间和工程大小,改动的多少,执行任务的个数强相关,所以光看某一次的部署时间,无法反应更无法横向比较 mina 和 cap 的部署速度。

该文章,主要是利用 mina 并接合 mina-sidekiq 和 mina-unicorn 两个 gem 包,将整个部署变得简单明了一些,不用自己再去写一些重启服务的脚本。

社区用 cap 部署的用户也不在少数,做为开发人员,我们选择适合自己的部署方式就行了 ^.^

请问你们是部署在国内哪个托管服务器上?

@george911 目前我们用的阿里云服务器,带宽的选择可以选 100M 的按量付费

@luolinae86 好的,谢谢。 请问数据库不用特别设置吗?我们用的 postgresql.

@george911 自动部署会执行 db:migrations 相关操作,但自动部署不会去动你环境中的数据库配置的,所以不用特别设置。

问下楼主,mina 有办法使用 sudo 吗,支持输入密码进行 sudo 操作,cap2.0 版本是支持的。

用 iOS 上的 Prompt 软件,配合 mina(将 mina 的 config 文件,在服务器上也放一份),在手机上也可以启动部署,很是方便。

@Tim_Lang

在 mina 中 queue! 命令是执行 shell 相关操作的,你可以修改 deploy 脚本,加上 sudo 命令,但如果 sudo 需求输入密码,我觉得破坏了自动部署的过程,你可以对指定用户设置 sudo 不输入密码,从而保证部署时不用人为参与。

mina 是相比 cap 要简洁的多,不过多个 domain 部署,cap 是可以的,不知道 mina 是否也行?有大神解惑一下

@elele,mina 也是支持部署多个 server 的,定义一个 domains 的数组,然后遍历数组,分别执行 deploy 即可。

set :domains, %w[host1 host2 host3]

desc "Deploy to all servers"
task :deploy_all do
  isolate do
    domains.each do |domain|
      set :domain, domain
      invoke :deploy
      run!
    end
  end
end

感觉 database.yml setting.yml 之类的配置文件没必要共享。默认 RAILS 的方式把 production 的 value 放到 ENV 省事一些

@suxu

配置文件共享的原因

这几个配置文件,涉及到帐号的敏感信息,没有上代码库,所以设置了共享,这样只需要编辑一次,后面部署的时候就不用再去动了。

默认 RAILS 的方式把 production 的 value 放到 ENV 省事一些

这一句,我没能够理解到你的意思,@suxu,能否以以代码配置的形式指点下,多谢。

@luolinae86 你好啊,先谢谢你的分享。我不知道你用的 RAILS 版本,4.0 以后 (具体哪个版本忘记了),默认的 database.yml 如下:

#
#   production:
#     url: <%= ENV['DATABASE_URL'] %>
#
production:
  <<: *default
  database: metro_production
  username: metro
  password: <%= ENV['DATABASE_PASSWORD'] %> #配置好ENV 直接读取就可以,而database.yml可以加入版本库的

敏感信息配置到服务器环境变量里,然后所有的 yml 都用 ENV 读取环境变量就可以了 ,

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  weixin_token: <%= ENV["WEIXIN_TOKEN"] %>
  weixin_appid: <%= ENV["WEIXIN_APPID"] %>
  weixin_secret: <%= ENV["WEIXIN_SECRET"] %>
  weixin_mchid: <%= ENV["WEIXIN_MCHID"] %>
  weixin_pay_secret: <%= ENV["WEIXIN_PAY_SECRET"] %>

@suxu 非常感谢你的回复,你提供的这种方式值得参考。 Rails 版本,我用的 3.2

bin rvm 可以吗? 确定是 set :rvm_path, '/usr/local/rvm/bin/rvm' 而不是:set :rvm_path, '/usr/local/rvm/scripts/rvm'

@michael_roshen

这里的 rmv_path,根据目标服务器 rvm 的安装路径而设定:

deployer@*******:~$ which rvm
/usr/local/rvm/bin/rvm

所以,不同用户的机器,which rvm 输出可能不同,从你的回复来看,你机器上面输出是: /usr/local/rvm/scripts/rvm

cd /data/project/mina_do_not_remove/science_read

这个目录是做什么的?

Reply to FrankFang

只是举了个业务中的实例,是业务中自己创建的代码目录,可以不用关注。

Reply to luolinae86

我现在也在用 mina deploy,但是我不知道怎么做定时部署。 是不是应该写个 bash 脚本,不能用 mina 来做定时部署?

Reply to FrankFang

是的,定时部署,我目前用的 cron 定时调用部署脚本。

部署完后该怎么访问呢?

服务器上面,再配置好 nginx 即可,请参考:

https://ruby-china.org/topics/471

luolinae86 in 解决 mina unicorn restart 不生效的问题 mention this topic. 27 Nov 12:14
You need to Sign in before reply, if you don't have an account, please Sign up first.