本BlogのWebServerをWEBrickからUnicornに変更しました。ちなみに Heroku で動かしています。
経緯
毎日 Error R12 というエラーが出ていました。
Rails app hosted on heroku: Error R12 (Exit timeout) - Stack Overflow
回答を読むと、Unicornにすればいいよ、とのことなので試しに移行してみます。
やったこと
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
を送信するので、このときに安全にプロセスをシャットダウンさせる仕組みが必要になります。
UnicornはTERM
シグナルを受け取ると、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'