Blogaomu

WEBアプリケーション開発とその周辺のメモをゆるふわに書いていきます。

Blogaomu on Unicorn (on Heroku)

本BlogのWebServerをWEBrickからUnicornに変更しました。ちなみに Heroku で動かしています。

経緯

毎日 Error R12 というエラーが出ていました。

Rails app hosted on heroku: Error R12 (Exit timeout) - Stack Overflow

回答を読むと、Unicornにすればいいよ、とのことなので試しに移行してみます。

やったこと

  1. install unicorn
  2. config/unicorn.rb
  3. Procfile
  4. foreman (at local)
  5. deploy

1. install unicorn

Gemfileに追加してbundle installすればOKです。

gem 'unicorn', ~> '4.8.2'

2. config/unicorn.rb

Unicornの設定ファイルを作ります。

$ mkdir config
$ touch config/unicorn.rb

Herokuのドキュメントを参考にしました。
Deploying Rails Applications With Unicorn | Heroku Dev Center

worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end
end

ポイントはTERMシグナルをトラップしてQUITシグナルをマスターに送信する処理です。 Herokuはdynoを再起動するときに起動しているプロセスに対してTERMを送信するので、このときに安全にプロセスをシャットダウンさせる仕組みが必要になります。 UnicornTERMシグナルを受け取ると、immediately shutdownします。そこでQUITを送り直すことで、処理中のリクエストを捌いた後にshutdownすることができます。 workerプロセスに対してはTERMを受け取っても何もしないようにしています。

設定できたらUnicornで起動するか確認します。bundle exec unicorn -c config/unicorn.rb でとりあえず動きました。 コマンド引数でrackup config fileを指定しないときは、デフォルトでカレントディレクトリの config.ru を読み込んでいます。

3. Procfile

以下のページを参考にしてProcfileを作成しました。

Getting Started with Ruby on Heroku | Heroku Dev Center
Deploying Rails Applications With Unicorn | Heroku Dev Center

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb

4. foreman (at local)

foreman はプロセス管理のためのツールで、Procfileで定義したプロセスを実行できます。 ローカルでforeman startを実行してUnicornが起動するか確認します。

foreman については以下のページが詳しいです。

ddollar/foreman
#281 Foreman - RailsCasts
foreman について - 君の瞳はまるでルビー - Ruby 関連まとめサイト

5. deploy

いつもどおりリポジトリにpushした後にgit push heroku masterするだけです。

感想

とても簡単に移行できました。Rackアプリケーション便利だなあ、と改めて感じました。

あとがき

毎日エラーが出ていたのは、TERMシグナルを送ってもWEBrickのプロセスがshutdownしなかった、というのが原因なのでは、と考えています。

WEBrick サーバー (Ruby による web サーバー) を安全に停止する方法とデーモン化する方法 - vivid memo

WEBrickの場合は、TERMシグナルをtrapする処理を書いてあげれば停止させることができるようです。 この処理を書かずにTERMシグナルを送っても、 以下のようにエラーとなってしまいます。

[2014-03-13 21:04:50] ERROR SignalException: SIGTERM
        /Users/takayuki_atkwsk/.rbenv/versions/1.9.3-p448/lib/ruby/1.9.1/webrick/server.rb:98:in `select'