2013 年 6 月頃に capistrano のバージョン 3 がリリースされました。かなり久しぶりのメジャーバージョンアップで色々変わっているようです。
Intobox でもデプロイには capistrano を使っていて、今回 capistrano のバージョンアップをしましたので手順の紹介とハマったことなどをまとめたいと思います。
capistrano v3
capistrano v3 は、より良いモジュール化、より簡単なデバッグ、より高速なデプロイ、などを設計目標として掲げています。
変更点が多いのでアップグレードガイドが公式サイトより提供されています。基本的にはここを見ながら進めていくのですが、やることといえば
- Gemfile に
gem 'capistrano', '~> 3.0'
を追加 bundle install
bundle exec cap install
をやって Capfile
や config/deploy.rb
を変更点に合わせてひたすら修正していくだけになります。ただ、バージョンアップ中にハマったポイントはあったので v2 と v3 の変更点も見ながら紹介していきます。
v2 と v3 の変更点
必要な gem が変わった
v3 からは、何をデプロイするかによって必要な gem が異なり、細かく分かれています。たとえば rbenv を使う Rails プロジェクトをデプロイする場合は
gem 'capistrano'
gem 'capistrano-rails'
gem 'capistrano-bundler'
gem 'capistrano-rbenv'
と、それぞれ必要な gem を指定してあげる必要があります。一見必要な gem が増えて面倒くさそうですが、これは capistrano が Rails だけのものではなくなったということです(まぁ v2 の頃から Rails 以外でもデプロイできてたわけですが。v3 では明示的に必要なものがモジュール化されています)。
※2013-12-12 現在の話ですが rubygems にある capistrano はどうもうまく動かないようで、調査に時間がかかってハマってしまいました。github 上にある最新版のソースコードでは問題が修正されているので Intobox では以下のように指定しています。
gem 'capistrano', git: 'https://github.com/capistrano/capistrano.git'
capify がなくなった
capistrano をインストールして最初にすることは
$ bundle exec capify .
でしたが v3 からはこのコマンドが無くなって
$ bundle exec cap install
に変わっています。このコマンドを実行することで以下の設定ファイルが生成されます。
Capfile
config/deploy.rb
config/deploy/production.rb
config/deploy/staging.rb
生成されるファイルを見てもわかると思いますが v3 ではデフォルトでマルチステージ機能が有効になっています。
ちなみに v2 の頃の設定ファイルがあると、上書きされてしまうのでアップグレードガイドでは最初にバックアップを取ることを勧めています。
$ mkdir old_cap
$ mv Capfile old_cap
$ mv config/deploy.rb old_cap
$ mv config/deploy/ old_cap # v2 の頃にマルチステージを使っていた場合のみ
Capfile の中身が変わった
Capfile
は v2 からもありましたが、最初に capify
した時からまったく触らない空気のような存在でした。
load 'deploy'
load 'deploy/assets'
load 'config/deploy'
こんな感じ。Rails デプロイでよくある Capfile
です。変わって v3 の場合は cap install
して生成された Capfile
を見てもわかるように、デプロイに必要なモジュールを require するものになっています。
# Load DSL and Setup Up Stages
require 'capistrano/setup'
# Includes default deployment tasks
require 'capistrano/deploy'
# Includes tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
# https://github.com/capistrano/rvm
# https://github.com/capistrano/rbenv
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails/tree/master/assets
# https://github.com/capistrano/rails/tree/master/migrations
#
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
# require 'capistrano/bundler'
# require 'capistrano/rails/assets'
# require 'capistrano/rails/migrations'
# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
生成された Capfile
はデフォルトではこのようになっています。最初に gem が分割されたことを挙げましたが require する時も必要なものを読み込む感じになっています。
タスクの定義の仕方が変わった
v2 の頃のタスクの定義は
task :unicorn_restart, roles: :app do
...
end
このように記述していましたが v3 からは
task :unicorn_restart do
on roles(:app) do
...
end
end
のように on
でロールを指定するようになりました。
パラメータが変わった
リポジトリ
リポジトリのURLの設定方法が変わっています。v2 の頃は
set :repository, '[email protected]:tkengo/repo.git'
だったのが v3 からは
set :repo_url, '[email protected]:tkengo/repo.git'
と、キーの名前が変わりました。
デプロイ方法
また v2 の頃は deploy_via
という設定項目もありましたが v3 からは無くなりました。デプロイ先のサーバーに ssh でログインして git clone する形になります。
パラメータの取得
set :application, 'Intobox'
などと設定したパラメータは v2 では deploy.rb
の中で application
という変数名でアクセス出来ましたが v3 では
fetch(:application)
という風に値を取得する必要があります。
git のみになった
Mecurial / Subversion / CVS のサポートはなくなって git のみになったようです。
デフォルトのタスクが変わった
deploy:setup
deploy:setup
がなくなりました。いきなり
$ bundle exec cap production deploy
とすれば、ディレクトリの生成やらも全部やってくれます。
deploy:cold
deploy:cold
もなくなっています。deploy:cold
は対象のアプリサーバーが起動していない一番最初にデプロイする時に使うタスクだったのですが、これも deploy
の方に統一されてそうです。
Intobox ではアプリサーバーとして unicorn を使っているため、サーバー起動のタスクとして
- pid ファイルがあるかどうかを確認する
- pid ファイルがあればその pid の unicorn に対して USR2 シグナルを送って再起動させる
- pid ファイルがなければ
bundle exec unicorn
で新しく unicorn を起動する
という処理を定義しています。
DSL が変わった
デプロイ対象のサーバーに ssh でログインしてコマンドを実行する run
というメソッドがありましたが、新しい記法がいくつか追加されており、そっちを使ったほうが良いと公式に書いてあります。むしろ v3 でのタスク定義のやり方で on
のブロックの中で run
を使おうとするとエラーが起きてしまいました。
run <<-CMD.compact
cd -- #{latest_release} &&
RAILS_ENV=#{rails_env.to_s.shellescape} #{asset_env} #{rake} assets:precompile
CMD
v2 でありそうな Rails の assets precompile のタスクですが v3 では
within fetch(:latest_release_directory)
with rails_env: fetch(:rails_env) do
execute :rake, 'assets:precompile'
end
end
こんな風に書くようです。run
を execute
に置き換えるだけでも問題ありませんでした。
execute <<-CMD.compact
cd -- #{latest_release} &&
RAILS_ENV=#{rails_env.to_s.shellescape} #{asset_env} #{rake} assets:precompile
CMD
デプロイフローが変わった
公式サイトからの引用ですが v3 で deploy
タスクを実行した時、以下のタスクが連続で実行されるようになっています。
deploy:starting - デプロイ準備。必要なディレクトリを作ったりなど
deploy:started - デプロイ準備後のフック
deploy:updating - デプロイ先サーバーのアプリを更新する
deploy:updated - アプリ更新後のフック
deploy:publishing - 更新したアプリをパブリッシュする
deploy:published - パブリッシュ後のフック
deploy:finishing - デプロイ後処理。不要なディレクトリを削除したりなど
deploy:finished - デプロイ後処理後のフック
フックと書いているところは after
や before
などを使って独自のタスクを入れ込む用のタスクです。
Rails をデプロイする場合、詳しくは以下のような流れになっているようです。
deploy
deploy:starting
[before]
deploy:ensure_stage
deploy:set_shared_assets
deploy:check
deploy:started
deploy:updating
git:create_release
deploy:symlink:shared
deploy:updated
[before]
deploy:bundle
[after]
deploy:migrate
deploy:compile_assets
deploy:cleanup_assets
deploy:normalise_assets
deploy:publishing
deploy:symlink:release
deploy:restart
deploy:published
deploy:finishing
deploy:cleanup
deploy:finished
deploy:log_revision
最後に
さてこれだけあればたぶん Capfile
config/deploy.rb
の編集もできそうです。
最後に Intobox で利用している設定ファイルを貼り付けておきます。参考になれば幸いです。
Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
set :rbenv_type, :system
set :rbenv_ruby, '2.0.0-pxxx' # 詳しいバージョンは伏せます
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
config/deploy.rb
set :application, 'Intobox'
set :stages, %w(production staging)
set :repo_url, '[email protected]:xxx/xxx.git' # リポジトリ名は伏せます
set :user, 'xxx' # ユーザー名も伏せます
role :app, %w{[email protected]}
role :web, %w{[email protected]}
role :db, %w{[email protected]}
set :keep_releases, 5
before 'deploy:published', 'deploy:unicorn_start'
before 'deploy:finished', 'whenever:update_crontab'
namespace :deploy do
desc 'restart a unicorn or start a unicorn if it is not launch'
task :unicorn_start do
on roles(:all) do
execute "mkdir -p #{shared_path}/pids"
pid_file = "#{shared_path}/pids/unicorn.pid"
if test "[ -e #{pid_file} ]"
execute "kill -USR2 `cat #{pid_file}`"
else
execute "cd #{current_path} && UNICORN_PID=#{pid_file} bundle exec unicorn -p #{fetch(:unicorn_port)} -E production -c #{current_path}/config/unicorn.rb -D"
end
end
end
desc 'stop a unicorn'
task :stop do
on roles(:all) do
execute "kill -QUIT `cat #{shared_path}/pids/unicorn.pid`"
end
end
desc 'restart a unicorn'
task :restart do
on roles(:all) do
execute "kill -USR2 `cat #{shared_path}/pids/unicorn.pid`"
end
end
end
namespace :whenever do
desc "update crontab using whenever's schedule"
task :update_crontab do
on roles(:all) do
execute "cd #{current_path} && RAILS_ENV=#{fetch(:rails_env)} bundle exec whenever -w"
end
end
end
config/deploy/production.rb
set :stage, :production
set :branch, :master
set :deploy_to, '/xxx/xxx/xxx' # デプロイ先のパス
set :rails_env, 'production'
set :unicorn_port, 'xxxx' # unicorn のポート