supervisord + unicornでhot restart (deploy) する


前置き

superviseされているunicornをhot restartさせたいときは、unicornherderを噛ませるのが定石だったのですが、unicornherderの中でunicornを実行するコマンドがおかしくなっており、assetsを読み込むpathに影響が出ていて使えない状態になっていました(以前は問題なかった?)。

pull req出してませんが、修正patch当たってる版をサーバーにインストールし、supervisordの設定を以下の様にしたらhot restartいけました。

※ちなみにunicorn_railsでのみ試してるので、普通のunicornでおかしかったりしたらすいません。

前提

  • supervisrdでunicornの死活監視を行ってる
  • unicornの設定の中にUSR2後にold processをQUITする記述がある

※何を言ってるかわからなかったら最後の参考URLを御覧ください。

install

pip install git+https://github.com/kazuph/unicornherder.git

supervisordの設定

[web]
command=bash -lc 'bundle exec unicornherder -u unicorn_rails -p tmp/pids/unicorn.pid -- -c config/unicorn.rb -E production'
numprocs=1
autostart=true
autorestart=true
stopsignal=QUIT
startsecs=1
user=yourname
redirect_stderr=true
stdout_logfile=/var/log/appname/app/app.log
stdout_logfile_maxbytes=0
stdout_logfile_backups=0
stderr_logfile=/var/log/appname/app/app.log
stderr_logfile_maxbytes=0
stderr_logfile_backups=0
environment=HOME="/home/app",USER="app"
directory=/home/yourname/appname

※unicorn_railsとなってるのは適宜読み替えてください。

hot restart

$ cd /path/yourname/appname
$ kill -USR2 `cat tmp/pids/unicorn.pid`

↑を直接サーバーで叩くもよし, capistrano経由で叩くもよし。

後はお好きに。

参考

zero-downtime deployments with unicorn and supervisord

追記(2014/04/26 15:53)既存のunicornherderであった問題点

もっと詳しく知りたい人向けに追記します。

そもそもなんでunicornherderを噛ませなければいけないか?

噛ませなかった場合の流れ。

  1. USR2シグナルをunicornのmasterプロセスに送信した際に、新しいunicornのmasterプロセスが立ち上がる
  2. 平行して元のmasterのプロセスは一時的に生きたままの状態になる(新しいプロセスに置き換わるまでの間代わりにリクエストを捌く必要があるため)
  3. 置き換わる過程で元のmasterプロセスが死ぬ
  4. このときにsupervisordがプロセスの死を感知して再度立ち上げようとする
  5. 新しく立ち上がっている方のunicornのプロセスとぶつかってunicornが「もうすでにプロセス立ち上がってるよ!」ってエラーを吐くのでプロセスが立ち上がらない
  6. supervisordが諦めない限り永遠にこの処理が続く
  7. しょうがないので先ほどUSR2で出来た新しいプロセスにQUITを送り停止させる
  8. 無事に衝突せずsupervisord経由でunicornが立ち上がる

長くなりましたがこんな感じのはずです(詳しい人の修正・追記求む)。

ポイントは、

  • USR2時にできるプロセスはsupervisordが感知しないプロセス
  • 置き換え終了後に死ぬプロセスはsupervisordが監視してるプロセス
  • unicornのmasterプロセスが死ぬことでしかhot restartできない

ことですかね。

つまりunicornは自分の分身をつくり、元いた方を殺すことでhot restartを実現しているが、supervisord的には死んでもらった困るわけです。

なので解決する方法としては、

  1. supervisord自身がhot restartを許容(実装)する
  2. supervisordにプロセスの死を知らせない方法でhot restartを達成する

となります。

で、やっとunicornherderの登場となります。

unicornherderはunicornをデーモン化するためのpython製モジュールです。

以下のような内部の記述にあるように、-Dでデーモン化してます。

unicornherder/unicornherder/herder.py
COMMANDS = {
    'unicorn': 'unicorn -D -P "{pidfile}" {args}',
    'unicorn_rails': 'unicorn_rails -D -P "{pidfile}" {args}',
    'unicorn_bin': '{unicorn_bin} -D -P "{pidfile}" {args}',
    'gunicorn': 'gunicorn -D -p "{pidfile}" {args}',
    'gunicorn_django': 'gunicorn_django -D -p "{pidfile}" {args}',
    'gunicorn_bin': '{gunicorn_bin} -D -p "{pidfile}" {args}'
}

unicorn自体をsupervisordで直接管理させずにunicornherderで管理し、supervisordにはunicornherderを管理させるということを行います。

これにより、unicornが分身しようが死のうがsupervisordにはわからないので、変な競合を起こさなくていいってことですね。

(ここまで書いてて思ったのですが、一枚かましたいだけならunicornherderを使う必要ない?そしてunicornherderで-Dでデーモン化する必要もない?)追記:2014/04/26 17:35 しばらく色々いじって遊んでみましたが、unicornherder必要だし-Dも必要ですね。

unicornherderの修正箇所

-Pをunicornに対して打つと

Use of --pid/-P is strongly discouraged
Use the 'pid' directive in the Unicorn config file instead

って感じでpid指定したいならconfigに書けよって出るし、しかもunicornの実装で、

  opts.on("-P PATH", "DEPRECATED") do |v|
    warn %q{Use of -P is ambiguous and discouraged}
    warn %q{Use --path or RAILS_RELATIVE_URL_ROOT instead}
    ENV['RAILS_RELATIVE_URL_ROOT'] = v
  end

って感じでRAILS_RELATIVE_URL_ROOTっていう全然pidに関係ない値がセットされて、静的コンテンツのpathがおかしくなってスタイル崩れるは画像が表示されないわおかしなことになります。

なので自分のpatchとしては-Pを指定しないようにしています(その代わりちゃんとunicornのconfigにpidのpathを書きましょう)。

-    'unicorn': 'unicorn -D -P "{pidfile}" {args}',
-    'unicorn_rails': 'unicorn_rails -D -P "{pidfile}" {args}',
-    'unicorn_bin': '{unicorn_bin} -D -P "{pidfile}" {args}',
+    'unicorn': 'unicorn -D {args}',
+    'unicorn_rails': 'unicorn_rails -D {args}',
+    'unicorn_bin': '{unicorn_bin} -D {args}',

はい、という感じで、だいぶ長文になって疲れたのでこのへんで止めたいと思います。

でもここまで調べてこの修正に自信ができたので、pull reqしてもいいかなと思い始めました。

時間があったらunicornherderを使わずにhot restartができるのかも試したいですね。