けんごのお屋敷

2013-12-12

capistrano をバージョン 3 にアップデートして時代の流れに乗る

2013 年 6 月頃に capistrano のバージョン 3 がリリースされました。かなり久しぶりのメジャーバージョンアップで色々変わっているようです。

Intobox でもデプロイには capistrano を使っていて、今回 capistrano のバージョンアップをしましたので手順の紹介とハマったことなどをまとめたいと思います。

capistrano v3

capistrano v3 は、より良いモジュール化、より簡単なデバッグ、より高速なデプロイ、などを設計目標として掲げています。

変更点が多いのでアップグレードガイドが公式サイトより提供されています。基本的にはここを見ながら進めていくのですが、やることといえば

  • Gemfile に gem 'capistrano', '~> 3.0' を追加
  • bundle install
  • bundle exec cap install

をやって Capfileconfig/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, 'git@github.com:tkengo/repo.git'

だったのが v3 からは

set :repo_url, 'git@github.com: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

こんな風に書くようです。runexecute に置き換えるだけでも問題ありませんでした。

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   - デプロイ後処理後のフック

フックと書いているところは afterbefore などを使って独自のタスクを入れ込む用のタスクです。

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, 'git@bitbucket.org:xxx/xxx.git' # リポジトリ名は伏せます
set :user, 'xxx' # ユーザー名も伏せます

role :app, %w{xxx@intobox.in}
role :web, %w{xxx@intobox.in}
role :db,  %w{xxx@intobox.in}

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 のポート
  • このエントリーをはてなブックマークに追加