はじめに
Railsをデプロイするために使っている各Gem
ファイルが何をしているかを知りたい人向けの記事です。
というか、僕が知りたいために書いてます。
なお、前回の記事が前提条件となってますので本記事を読む前に一読すると理解が深まります。
とりあえず使いた人向け
特に何も考えずにデプロイだけ行いたいという人は以下のGemを入れて実行してください。
デプロイするフォルダになりますので、適当にフォルダを掘ってから行うと良いです。
source 'https://rubygems.org'
gem 'capistrano'
gem 'capistrano-rails'
gem 'capistrano-rbenv' # rbenvを使ってない場合は不要
gem 'capistrano-bundler'
gem 'capistrano-sidekiq' # sidekiqで遅延処理管理をしている場合
gem 'whenever' # wheneverでスケジューリングしている場合
gem 'unicorn' # unicornで動作させている場合(入れなくてもできる。)
必要なものを定義して
$ bundle install
を実行してください。
Capistranoのインストール
Capistranoを設定します。
$ bundle exec cap install
出来上がったフォルダのCapfileを編集します。
# Capistranoの基本動作を設定。
require 'capistrano/setup'
require 'capistrano/deploy'
# rvm/rbenv/chrubyを使用する場合に合わせてコメントアウトすること。
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
# Railsのレシピを読み込みます。
require 'capistrano/rails'
# capistrano/rails内には以下が定義されています。
# require 'capistrano/bundler'
# require 'capistrano/rails/assets'
# require 'capistrano/rails/migrations'
# 必要に応じてコメントを外してください。
# require 'capistrano/passenger'
# require 'capistrano/sidekiq'
# require 'whenever/capistrano'
# require 'sshkit'
# require 'capistrano3/unicorn'
基本動作に関してはこちらをみてください。
各種設定を行う
ここまでの設定でCapistranoの設定は半分終わってます。
この辺りがCapistrano3の良いところですね。
あとはdeploy.rb
に必要に応じて設定内容を記載します。
以下を参考にしてください。
# 基本設定
set :application, 'application_name' # application名はなんでも良い。
set :repo_url, 'git@gitpub.****.git' # デプロイ対象のリポジトリ
set :branch, 'master' # ブランチの指定をしたい場合はここに記載。
# デプロイ先
set :deploy_to, -> { "/var/www/#{fetch(:application)}" } # デプロイする先(default:"/var/www/#{fetch(:application)")
# set :deploy_to, "~/#{fetch(:application)}/#{fetch(:rails_env)}" # HOMEを指定するとパーミッションに悩まされなくて良いかも。
set :pty, true # sudoを使用する場合はtrueにする。
set :log_level, :info # Capistranoの出力ログの制御
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system') # sharedにシンボリックリンクを張るディレクトリ指定
# set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system} # ここでしか使わないならこれでもOK。
set :linked_files, %w{.env} # シンボリックリンクを張るファイル
set :keep_releases, 5 # リリースフォルダをいくつまで保持するか?
set :default_env, { path: "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH" } # capistrano用bundleするのに必要
# bundleインストール先(以下全てデフォルト。記載なしの場合)
set :bundle_bins, %w{gem rake rails}
set :bundle_roles, :all
set :bundle_servers, -> { release_roles(fetch(:bundle_roles)) }
set :bundle_path, -> { shared_path.join('bundle') } # この値以外は多分操作することはない。
set :bundle_without, %w{development test}.join(' ')
set :bundle_flags, '--deployment --quiet'
# migrateの設定
set :migration_role, :db # どのロールで実施するか?(default:db)
set :conditionally_migrate, false # 不要の場合は実施しないか?(default:false)
# rbenvの設定
set :rbenv_type, :system # rbenv_custom_pathを指定していれば不要
set :rbenv_ruby, '2.2.3' # rubyのバージョン(事前に指定バージョンをインストールしておく必要あり。)
set :rbenv_custom_path, '/opt/rbenv' # rbenvのインストール先
set :rbenv_map_bins, %w{rake gem bundle ruby rails} # rbenv execをつけたいコマンド
# SSH接続時の情報
set :ssh_options, {
keys: [File.expand_path('~/.ssh/id_rsa')], # 鍵の格納場所
forward_agent: true, # サーバーを跨いで鍵を使いたい場合
auth_methods: %w(publickey) # 認証方法(passwordも可能)
#password: 'xxxxx' # password指定
}
# rakeコマンドを実行する際のコマンド指定
SSHKit.config.command_map[:rake] = 'bundle exec rake'
これらの設定を行えば、おしまいです。
なお、こちらのレシピを追加するとdb:create
やサーバー再起動
なども行えます。(後述)
あとは、各サーバー毎に変更する値を記載します。
# rails_envの設定。大抵はrails_envのみ設定すれば問題ない。
set :stage, :staging
set :rails_env, fetch(:stage)
# デプロイ対象のサーバーを指定。
server '172.1.1.1', user: 'app', roles: %w{web app db batch}
server '172.1.1.2', user: 'app', roles: %w{web app}
...
あとは以下のコマンドを実行して完了です。
cap *** deploy
Railsのレシピを深読みする
これだけで使用できますが、実際にrequire
すると何が起きているのかを見ていきます。
'capistrano/bundler'
このrequire
は簡単な動作をしています。
コメント部分を取り除いたコードは以下のようになってます。
namespace :bundler do
task :install do
on fetch(:bundle_servers) do
within release_path do
with fetch(:bundle_env_variables, {}) do
options = ["install"]
options << "--binstubs #{fetch(:bundle_binstubs)}" if fetch(:bundle_binstubs)
options << "--gemfile #{fetch(:bundle_gemfile)}" if fetch(:bundle_gemfile)
options << "--path #{fetch(:bundle_path)}" if fetch(:bundle_path)
options << "--without #{fetch(:bundle_without)}" if fetch(:bundle_without)
options << "--jobs #{fetch(:bundle_jobs)}" if fetch(:bundle_jobs)
options << "#{fetch(:bundle_flags)}" if fetch(:bundle_flags)
execute :bundle, options
end
end
end
end
task :map_bins do
fetch(:bundle_bins).each do |command|
SSHKit.config.command_map.prefix[command.to_sym].push("bundle exec")
end
end
before 'deploy:updated', 'bundler:install'
end
Capistrano::DSL.stages.each do |stage|
after stage, 'bundler:map_bins'
end
namespace :load do
task :defaults do
set :bundle_bins, %w{gem rake rails}
set :bundle_roles, :all
set :bundle_servers, -> { release_roles(fetch(:bundle_roles)) }
set :bundle_path, -> { shared_path.join('bundle') }
set :bundle_without, %w{development test}.join(' ')
set :bundle_flags, '--deployment --quiet'
end
end
ポイントとなる箇所はbefore 'deploy:updated', 'bundler:install'
になってます。
この定義によってdeploy:updated
の前すなわちdeploy:updating
の後に実行されるタスクが定義されます。
こちらに定義している通りで、deploy:updating
はGitからファイルを配置した後になります。
その時にbundle install
を行っていることがわかります。
また、コマンド実行時に指定した引数を渡していることがわかります。
これによって、新しいサーバーに必要なGem
がインストールされます。
map_bins
も注目の箇所です。
簡単に書くとbundle exec
を付けて実行するコマンドをbundle_bins
で定義してます。
'capistrano/rails/assets'
このrequire
はCoffeeScript
やSASS
をコンパイルするタスクになります。
after 'deploy:updated', 'deploy:compile_assets'
after 'deploy:updated', 'deploy:normalize_assets'
after 'deploy:reverted', 'deploy:rollback_assets'
追っていくと以下のことをしていることがわかります。
デプロイ時
デプロイ時は以下の順番に実施されます。
# deploy:compile_assets
execute :rake, "assets:precompile"
backup_path = release_path.join('assets_manifest_backup')
execute :mkdir, '-p', backup_path
execute :cp,
detect_manifest_path,
backup_path
# deploy:normalize_assets
assets = fetch(:normalize_asset_timestamps)
if assets
execute :find, "#{assets} -exec touch -t #{asset_timestamp} {} ';'; true"
end
見るとassets_manifest_backup
にバックアップを取っていることがわかります。
ロールバック時
assets_manifest_backup
から前の状態を復元します。
復元が無理の場合は再度コンパイルします。
begin
invoke 'deploy:assets:restore_manifest'
rescue Capistrano::FileNotFound
invoke 'deploy:compile_assets'
end
設定項目
以下の項目が設定内容として使用できます。
set :assets_roles, fetch(:assets_roles, [:web])
set :assets_prefix, fetch(:assets_prefix, 'assets')
set :linked_dirs, fetch(:linked_dirs, []).push("public/#{fetch(:assets_prefix)}")
'capistrano/rails/migrations'
このタスクもbundle
と同じくらい短いのでコードを載せます。
namespace :deploy do
desc 'Runs rake db:migrate if migrations are set'
task :migrate => [:set_rails_env] do
on primary fetch(:migration_role) do
conditionally_migrate = fetch(:conditionally_migrate)
info '[deploy:migrate] Checking changes in /db/migrate' if conditionally_migrate
if conditionally_migrate && test("diff -q #{release_path}/db/migrate #{current_path}/db/migrate")
info '[deploy:migrate] Skip `deploy:migrate` (nothing changed in db/migrate)'
else
info '[deploy:migrate] Run `rake db:migrate`'
within release_path do
with rails_env: fetch(:rails_env) do
execute :rake, "db:migrate"
end
end
end
end
end
after 'deploy:updated', 'deploy:migrate'
end
namespace :load do
task :defaults do
set :conditionally_migrate, fetch(:conditionally_migrate, false)
set :migration_role, fetch(:migration_role, :db)
end
end
bundle
同様でafter 'deploy:updated', 'deploy:migrate'
のタイミングに実施されます。
on primary
で実施されいる点が味噌になります。
なので、db
ロールを誤って二つ設定しても二重に実行されることはありません。
conditionally_migrate
は面白い項目です。
僕は知らなかったのですが、この値をtrue
にしておけば毎回db:migrate
が実施される事がなくなります。
コードを見ると今回のリリースファイルと現在稼働しているカレントファイルの差分を取って変更があるかをチェックして実行可否を決めてます。
設定内容
設定内容は以下です。特に変えることは無いと思います。
set :conditionally_migrate, fetch(:conditionally_migrate, false)
set :migration_role, fetch(:migration_role, :db)
'capistrano/rbenv'
今までどうやってrbenv
でruby
のバージョンを分けていたかがこのコードでわかりました。
namespace :rbenv do
task :validate do
on release_roles(fetch(:rbenv_roles)) do
rbenv_ruby = fetch(:rbenv_ruby)
if rbenv_ruby.nil?
error "rbenv: rbenv_ruby is not set"
exit 1
end
unless test "[ -d #{fetch(:rbenv_ruby_dir)} ]"
error "rbenv: #{rbenv_ruby} is not installed or not found in #{fetch(:rbenv_ruby_dir)}"
exit 1
end
end
end
task :map_bins do
SSHKit.config.default_env.merge!({ rbenv_root: fetch(:rbenv_path), rbenv_version: fetch(:rbenv_ruby) })
rbenv_prefix = fetch(:rbenv_prefix, proc { "#{fetch(:rbenv_path)}/bin/rbenv exec" })
SSHKit.config.command_map[:rbenv] = "#{fetch(:rbenv_path)}/bin/rbenv"
fetch(:rbenv_map_bins).each do |command|
SSHKit.config.command_map.prefix[command.to_sym].unshift(rbenv_prefix)
end
end
end
Capistrano::DSL.stages.each do |stage|
after stage, 'rbenv:validate'
after stage, 'rbenv:map_bins'
end
namespace :load do
task :defaults do
set :rbenv_path, -> {
rbenv_path = fetch(:rbenv_custom_path)
rbenv_path ||= if fetch(:rbenv_type, :user) == :system
"/usr/local/rbenv"
else
"~/.rbenv"
end
}
set :rbenv_roles, fetch(:rbenv_roles, :all)
set :rbenv_ruby_dir, -> { "#{fetch(:rbenv_path)}/versions/#{fetch(:rbenv_ruby)}" }
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
end
end
rbenv_map_bins
に指定されているコマンドが実行される場合にrbenv_path
とrbenv_ruby
によって導きだされたruby
のフォルダを引き渡すように指定してます。
SSHKit.config.command_map
の便利さを知るコードですね。
'capistrano3/unicorn'
使ってないのでコードが手元に無いのですが、こちらを見るとunicorn
のタスクが書かれている事がわかります。
僕もそうですが、この辺のタスクを自前で登録していた人はこのGem
を使用すると楽になると思います。
なおUnicornに関してはこちらに記載してますのでよければ見てください。
このgem
を使えばinvoke 'unicorn:restart'
でUnicornの再起動ができます。
自前で行う場合は以下のファイルを配置すれば実施できます。
(結構あちらこちらに落ちているコードです。)
namespace :load do
task :defaults do
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }
set :unicorn_config, -> { "#{current_path}/config/unicorn.rb" }
end
end
namespace :unicorn do
task :environment do
end
def start_unicorn
within current_path do
execute :bundle, :exec, "unicorn_rails -c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D"
end
end
def stop_unicorn
execute :kill, "-s QUIT $(< #{fetch(:unicorn_pid)})"
end
def reload_unicorn
execute :kill, "-s USR2 $(< #{fetch(:unicorn_pid)})"
end
def force_stop_unicorn
execute :kill, "$(< #{fetch(:unicorn_pid)})"
end
desc "Start unicorn server"
task :start => :environment do
on roles(:app) do
start_unicorn
end
end
desc "Stop unicorn server gracefully"
task :stop => :environment do
on roles(:app) do
stop_unicorn
end
end
desc "Restart unicorn server gracefully"
task :restart => :environment do
on roles(:app) do
if test("[ -f #{fetch(:unicorn_pid)} ]")
reload_unicorn
else
start_unicorn
end
end
end
desc "Stop unicorn server immediately"
task :force_stop => :environment do
on roles(:app) do
force_stop_unicorn
end
end
desc "Restart unicorn server immediately"
task :force_restart => :environment do
on roles(:app) do
stop_unicorn
start_unicorn
end
end
end
自前タスク
自前タスクをdeploy
の中に書いていっても良いのですが、上記のようにタスク毎にまとめるとスマートに管理ができます。
Capfile
の一番最後に以下のコードが記載されてます。
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
# 古いバージョンでは以下のように記載されてます。
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
なのでlib/capistrano/tasks/*.rake
に上記と同じ形式のファイルを定義すれば自動的に読み込まれます。
また、呼び出すときにはinvoke 'unicorn:restart'
のように呼び出せます。
まとめ
他にもwhenever
やsidekiq
、passenger
などのレシピがありますので必要に応じて読めばタスクの内容が大体わかりました。
一部を除けばそこまで難しい事はかかれてい無いので、必要に応じてタスクを読む程度でrequire
を使いこなせる事がわかりました。
あと、僕があまり把握していなかったのでrequire 'sshkit'
については言及しませんでしたがSSH
に関する事をやりたい場合はこのような事もできるみたいです。