世界上最伟大的投资就是投资自己的教育

首页服务器部署
随风 · 练气

使用 capistrano 部署 Rails 应用

随风发布于3996 次阅读

1. 介绍

本站有很多篇使用 mina 来部署的文章,但是相信一定有很多人是使用 capistrano 来部署 rails 应用的。所以本篇文章也来讲讲如何用 capistrano 部署 ruby on rails 应用。

capistrano 比 Mina 给人的感觉部署时间要长,mina 比较精简,而 capistrano 流行度比较广,功能也很强大,插件也是相当多。

2. 安装

添加下面这行到 Gemfile 文件:

group :development do
  gem "capistrano", "~> 3.4"
end

然后执行:

$ bundle install

创建部署的各种文件:

$ bundle exec cap install

这条命令会输出下面的信息:

mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

由此可看出,跟 Mina 不同,capistrano 默认情况下就是多个 stage 的部署环境,而 Mina 是要用插件才能做到,所以在这上面,Mina 显得简单些,因为并不是每个人都需要多 stage 的环境的。

3. 修改部署文件

这其中有比较重要的几个文件:Capfileconfig/deploy.rbconfig/deploy/production.rb

先来说Capfile文件。

这是一个主要的文件,用来加载其他各种文件或组件模块的,Mina 没有这个文件,它这样的功能都放到config/deploy.rb中了。

# Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

默认情况下,只有上面的三行,其他的都是注释,现在我们要加载 rbenv,rails,bundler 等组件,所以要把相应的组件打开。

# Capfile
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

这些组件都是一个个的 gem,所以你需要在 Gemfile 上添加这些 gem。

先添加rbenv

gem 'capistrano-rbenv'
gem 'capistrano-bundler'
gem 'capistrano-rails'

config/deploy.rb文件中添加下面几行。

set :rbenv_type, :user # or :system, depends on your rbenv setup
set :rbenv_ruby, '2.3.0'

# in case you want to set ruby version from the file:
# set :rbenv_ruby, File.read('.ruby-version').strip
#
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
set :rbenv_roles, :all # default value

# 可以看到bundle install的输出
set :bundle_flags, ''

接下来是config/deploy.rb文件。

这个文件指定的是部署的应用名称,主机地址和目录,源码库地址等信息,Mina 也是在这个文件中指定。

# config/deploy.rb
# 部署的应用名
set :application, 'rails365_cap'
# 源码地址
set :repo_url, 'git@github.com:yinsigan/rails365.git'
# 源码分支
set :branch, "cap"
# 部署目录
set :deploy_to, "/home/yinsigan/#{fetch(:application)}"
# 链接的文件
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/application.yml')
# 链接的目录
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'pids', 'tmp/sockets')

至于config/deploy目录下的文件就是不同的 stage 的部署文件,是对config/deploy.rb的继承,可以重写config/deploy.rb的配置,它主要是指定了部署的主机和用户名,还有一些 ssh 连接的信息。

# config/deploy/production.rb
server 'www.rails365.net', user: 'yinsigan', roles: %w{app db web}
set :rails_env, "production"
set :stage, :production

4. 部署之前

上面的工作看似准备差不多了,有了主机的地址,用户名,还有源码库的地址,分支,就可以下载代码。

首先要确保你的主机是能下载到代码的,所以有必要先将主机的 ssh public key 上传到托管源码的网站上。

然后是要确保 linked_files 文件是存在的,例如上面的config/database.ymlconfig/application.yml,这两个文件可以到主机上自己创建。

存放的位置是 shared/config 目录下,例如rails365_cap/shared/configrails365_cap是部署的目录。

还有,在部署之前,要先安装bundler这个 gem。

5. 部署命令

现在可以开始来部署。

输出所有可用的命令。

$ bundle exec cap -T

执行部署。

$ bundle exec cap production deploy

可以看到,跟 Mina 不同的是,它不用自己去手动生成 linked_dirs 目录,capistrano 会自动生成各种链接目录。

如果怕出错,还可以模拟生产环境的部署,但实际上没有做任何事。

$ bundle exec cap production deploy --dry-run

6. 让应用跑起来

上面是看似完成了部署,但是我们的应用没有跑起来。所以我们需要使用 unicorn 来加载整个应用,整个应用监听在一个文件 socket 上。

6.1 unicorn

只要我们做到部署的时候,unicorn 也让它重启就 OK,没有必要自己写 shell 脚本,我们用一个插件来完成。

这个插件是capistrano3-unicorn

添加下面的代码到Gemfile文件:

group :development do
  gem 'capistrano3-unicorn', '~> 0.2.1'
end
gem 'unicorn', '~> 4.9.0'

添加下面这行到Capfile文件:

require 'capistrano3/unicorn'

找到config/deploy.rb文件,修改如下:

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end

  task :restart do
    invoke 'unicorn:legacy_restart'
  end

end

除些之外,unicorn 是需要配置的,比如配置文件存放的位置,pid 文件的位置,监听的 socket 存放的位置,加载的环境 (development,production) 等。默认的情况下是这样的:

set :unicorn_pid, -> { File.join(current_path, "tmp", "pids", "unicorn.pid") }
set :unicorn_config_path, -> { File.join(current_path, "config", "unicorn", "#{fetch(:rails_env)}.rb") }
set :unicorn_roles, -> { :app }
set :unicorn_options, -> { "" }
set :unicorn_rack_env, -> { fetch(:rails_env) == "development" ? "development" : "deployment" }
set :unicorn_restart_sleep_time, 3

我们要确保 pid 和 socket 文件是在 linked_dirs 目录下。

添加下面一行到config/deploy.rb文件中:

set :unicorn_config_path, -> { File.join(current_path, "config", "unicorn.rb") }

主要的配置文件是config/unicorn.rb,大多数内容在里面定义。

app_path = File.expand_path( File.join(File.dirname(__FILE__), '..', '..'))
worker_processes   1
timeout            180
listen             "#{app_path}/shared/tmp/sockets/unicorn.sock"
pid                "#{app_path}/shared/tmp/pids/unicorn.pid"
stderr_path        "log/unicorn.log"
stdout_path        "log/unicorn.log"

# preload
preload_app true

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| # 修正无缝重启unicorn后更新的Gem未生效的问题,原因是config/boot.rb会优先从ENV中获取BUNDLE_GEMFILE,而无缝重启时ENV['BUNDLE_GEMFILE']的值并未被清除,仍指向旧目录的Gemfile
  ENV["BUNDLE_GEMFILE"] = "#{app_path}/current/Gemfile"
end

再次执行bundle exec cap production deploy部署,完成后可以用 ps 来查看进程的情况。

root@iZ94x9hoenwZ:~# ps -ef | grep unicorn
yinsigan  5457     1  3 17:07 ?        00:00:03 unicorn master -c /home/yinsigan/rails365_cap/current/config/unicorn.rb -E production -D                                                       
yinsigan  7409  5457  0 17:07 ?        00:00:00 unicorn worker[0] -c /home/yinsigan/rails365_cap/current/config/unicorn.rb -E production -D

unicorn 现在是加载了整个 rails 应用并以文件 socket 的形式监听,我们可以用 nginx 反向代理到这个文件 sokcet 上就可以实现外网访问了。

6.2 sidekiq

除了 unicorn,我们还需要把 sidekiq 给跑起来。

我们使用是capistrano-sidekiq这个插件。

添加下面的代码到Gemfile文件:

group :development do
  gem 'capistrano-sidekiq', '~> 0.5.4'
end

添加下面两行到Capfile文件中:

require 'capistrano/sidekiq'

修改config/deploy.rb文件:

set :rbenv_map_bins, %w{rake gem bundle ruby rails sidekiq sidekiqctl}

再重新部署。

7. 高级选项

这部分介绍一些 capistrano 部署时的高级选项。

7.1 capistrano 支持回滚。
$ bundle exec cap production deploy:rollback
7.2 设置日志等级。

默认情况下,部署时输出的日志等级为 debug。

我们可以指定为 info,让输出的信息更少一些。

# config/deploy.rb
set :log_level, :info
7.3 使用 ask 命令

使用 ask 命令,可以让部署的时候暂停,等待输入,并传递输入的内容给指定的变量,使用它可以轻易地实现交互式的部署。

# 输入部署的分支
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
7.4 自定义 task

lib/capistrano/tasks这个目录可以放自己的 task。

比如运行rake db:seed

# lib/capistrano/tasks/setup.rake
namespace :setup do

  desc "Seed the database."
  task :seed_db do
    on roles(:app) do
      within "#{current_path}" do
        with rails_env: :production do
          execute :rake, "db:migrate"
          execute :rake, "db:seed"
        end
      end
    end
  end

end

运行bundle exec cap production setup:seed_db可执行上面的 task。

上传一个文件到服务器中。

# lib/capistrano/tasks/setup.rake
namespace :setup do

  desc "upload rails.conf to shared dir"
  task :upload_nginx do
    on roles(:app) do
      upload! StringIO.new(File.read("config/rails.conf")), "#{shared_path}/config/nginx.conf"
    end
  end

end

查看生产环境的日志。

# lib/capistrano/tasks/setup.rake
namespace :setup do

  desc "tailf production log"
  task :production_log do
    on roles(:app) do
      execute "tail -f #{current_path}/log/production.log"
    end
  end

end

固定的分支才能部署。

namespace :deploy do

  desc "Makes sure local git is in sync with remote."
  task :check_revision do
    unless `git rev-parse HEAD` == `git rev-parse origin/deploy`
      puts "WARNING: HEAD is not the same as origin/develop"
      puts "Run `git push` to sync changes."
      exit
    end
  end

  before :deploy, "deploy:check_revision"
end

8. 加速 rake assets:precompile

在每次部署的时候,都会重新编译整个静态资源,即rake assets:precompile,这个过程又很慢。

我们需要有一个机制,就是有静态文件改变的时候才重新编译,不然的话不用重新编译。

capistrano-faster-assets就是解决这个问题的。

添加下面这行到Gemfile文件中:

gem 'capistrano-faster-assets', '~> 1.0.2'

添加下面这行到Capfile文件中,但需要在require 'capistrano/rails/assets'之后:

require 'capistrano/faster_assets'

重新部署即可。

完结。

本站文章均为原创内容,如需转载请注明出处,谢谢。

1 条回复
喜欢
统计信息
    学员: 29921
    视频数量: 1996
    文章数量: 526

© 汕尾市求知科技有限公司 | Rails365 Gitlab | 知乎 | b 站 | csdn

粤公网安备 44152102000088号粤公网安备 44152102000088号 | 粤ICP备19038915号

Top