普段作るRailsのプロジェクトはGitで管理しているので、アプリケーションのアップデート手順というと、sshでログインしてgit pull。あと必要に応じてbundle installやrake db:migrateして、touch tmp/restart.txtで完了。

たいした手間ではないし、昔のように更新されたファイルを選んでFTPでアップロードしてた頃に比べたら格段に楽だし確実になった。

とはいえ今までは複数のサーバを一斉にアップデートする。ということはあまりなかったのでこれで良かったんだけど、今やってるプロジェクトはローンチして間もないため頻繁にアップデートがある上、複数に適用する必要があるため、微妙に手間がかかるし、たまにcache:clearやassets:precompileを忘れて変なことになる。

ということで遅ればせながらRuby界隈のデプロイツールとして定番のCapistranoを使ってみることにした。

Capistranoは最初に生成されるファイルがコメント沢山の大きなファイルで(まあRails自体がそんな感じなんだけど)、学習コストが高そうな印象を受けるんだけど、思い切ってコメントを全部消してシンプルな設定ファイルにしてみると、それほど大変ではなさそうという気がしてくる。特にRakeのタスクを書き慣れている人なら馴染みやすい感じ。一度書いてしまえば後々のプロジェクトでも流用できるし。

主に書かなければいけないのがデプロイ手順を書いたconfig/deploy.rbで、こんな感じになった。

lock '3.2.1'

set :application, 'Project'
set :repo_url, 'git://github.com/miyamae/xxxxx.git'
set :keep_releases, 5
set :bundle_path, -> { shared_path.join('vendor/bundle') }
set :linked_files, %w{ config/database.yml
  config/application.yml config/newrelic.yml }
set :linked_dirs, %w{ log tmp vendor/bundle public/system }

namespace :deploy do

  desc 'Upload sample configuration files'
  task :config do
    on roles(:app) do |host|
      if test("[ ! -d #{shared_path}/config ]")
        execute("mkdir -p #{shared_path}/config")
      end
      if test("[ ! -e #{shared_path}/config/database.yml ]")
        upload!('config/database.yml.sample',
          "#{shared_path}/config/database.yml")
      end
      if test("[ ! -e #{shared_path}/config/application.yml ]")
        upload!('config/application.yml.sample',
          "#{shared_path}/config/application.yml")
      end
      if test("[ ! -e #{shared_path}/config/newrelic.yml ]")
        upload!('config/newrelic.yml.sample',
          "#{shared_path}/config/newrelic.yml")
      end
    end
  end

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      execute :touch, release_path.join('tmp/restart.txt')
    end
  end
  after :restart, :clear_cache do
    on roles(:app), in: :groups, limit: 3, wait: 10 do
      within current_path do
        with rails_env: fetch(:rails_env) do
          execute :rake, 'tmp:cache:clear'
        end
      end
    end
  end
  after :restart, :assets_precompile do
    on roles(:app), in: :groups, limit: 3, wait: 10 do
      within release_path do
        with rails_env: :production do
          execute :rake, 'assets:precompile'
        end
      end
    end
  end

  before :starting, :config
  after :publishing, :restart

end

まだ理解不十分のため最適化できていないと思います。きもちいい!って感じではないけど、一応やっていることは理解できるコードかな。database.ymlなどの設定ファイルサンプルのコピーをアップロードするconfigタスクがちょっと汚い。あと、capistrano-railsを使ってるけど、なぜかdeploy:compile_assetsが動いてくれなかったので、自前でタスクを定義している。たぶん正しい方法ではないけど、自前で定義しても大したコストではない。

今回はRuby環境がターゲットによってRVMだったりrbenvだったりするため、capistrano-rvmやcapistrano-rbenvは使わなかった。

そしてデプロイ先の設定はconfig/deploy/*.rb。これはdeploy.rbの内容をデプロイ先ごとに細かく設定できるファイルですね。最小限だとsshの接続先とデプロイ先パスを書くだけでシンプル。

server 'www.example.com', user: 'web', roles: %w{ app db }
set :rails_env, :production
set :deploy_to, '/var/www/application'

これで、Ansibleでサーバをセットアップして、Capistranoでアプリケーションのデプロイ、という感じで意外にコストのかかる日常業務がほぼ自動化されました。多少ターゲットの台数が増えてもそれほど手間が大きくならずに済みます。