capistranoでunicornのoldプロセスが殺せない

最近Capistranoを導入しました.
一般的なRailsのプロジェクトにCapistranoを組み込むのは,このあたりを参考にすると割と楽にいける.

morizyun.github.io


qiita.com




ただ,これに合わせて重要になってくるものがあって,どうにもこれだけではunicornのリスタートがうまくいかなかったので,書き残しておく.

unicornのoldプロセスが死んでくれない


今回,capistrano3-unicornというgemを使用した.
まぁ内容的にはそんなに難しいことはなく,unicornにUSR2シグナルを送るだけである.

tablexi/capistrano3-unicorn · GitHub



だけど死んでくれない.
これは,unicornのconfigの書き方に寄るところが大きい.

まずcapistrano側でunicornの設定ファイルを置く


上記のcapistrano3-unicornを見ればだいたい必要なことは書いてある.
deploy.rbの,unicornに関係ありそうなところを抜き出してみた.

set :linked_dirs, %w{bin log tmp/backup tmp/pids tmp/sockets vendor/bundle}
set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
set :unicorn_config_path, "#{release_path}/config/unicorn.rb"

大事なのは,pidファイルの保存場所をshared_pathにしておくこと.そしてそれをlinked_dirsに登録しておくこと.
こうしておかないと,pidから現在動いているunicornのプロセスを特定できない.

unicornのconfigファイルはrelease_pathに配置した.これは別にshared_pathでもいいとは思う.

そしてさらに以下のように記述してunicornをrestartさせる.

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end


これでconfig/unicorn.rbがしっかり書いてあれば,unicornのデプロイは成功する.


unicorn.rbを正しく設定する

ここでかなり手間取った.

config/unicorn.rb

shared_path = "/srv/www/asumibot/shared/"
current_path = "/srv/www/asumibot/current"

# ソケット経由で通信する
# ここがcapistranoの設定と合致していないと失敗する
listen File.expand_path('tmp/sockets/unicorn.sock', shared_path)
pid File.expand_path('tmp/pids/unicorn.pid', shared_path)


# capistrano 用に RAILS_ROOT を指定
working_directory current_path

# ダウンタイムなくす
preload_app true

before_fork do |server, worker|
  ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile', current_path)
  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

shared_pathとcurrent_pathはcapistranoの設定に合わせて設定してもらえれば良い.

ソケット大事

unicornが再起動する際,前のプロセスを殺しにいくのだがそのときにソケット経由で通信する.
そのためにソケットファイルのある場所を正確に記述しておく必要がある.

これがcapistranoのrelease_pathと同じディレクトリ指定をされていたために,unicornが古いプロセスを探せなくなり,最終的にunicornの再起動に失敗していた.
いや,確かにcapistranoを導入していなければ,デプロイするたびにsocketファイルの場所が変わったりするなんてことがないので,何も考えずにrails_rootからたどるパスしか書いてなかった.

working_directoryは設定しよう

これを設定してあげないと,unicornが再起動するときに古いrelease_pathのソースを読み取っていることがある.
特にassets周りはproduction環境ではdigestがついてしまうので,古いソースを読み取るともれなくassetsがすべて404になったりしていた.

bundle_gemfileを設定しないとgemが更新されない

新しいgemを追加した際などに,bundle install自体は(capistrano側で)走っていても,unicorn起動時に新しいgemを読み込んでくれない.

実際に出てたエラー

だいたいこんなエラーが出てくる.

I, [2015-04-18T06:05:20.121566 #9390]  INFO -- : Refreshing Gem list
E, [2015-04-18T06:05:22.429058 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:22.429122 #9390] ERROR -- : retrying in 0.5 seconds (4 tries left)
E, [2015-04-18T06:05:22.929649 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:22.929722 #9390] ERROR -- : retrying in 0.5 seconds (3 tries left)
E, [2015-04-18T06:05:23.430183 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:23.430255 #9390] ERROR -- : retrying in 0.5 seconds (2 tries left)
E, [2015-04-18T06:05:23.930722 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:23.930792 #9390] ERROR -- : retrying in 0.5 seconds (1 tries left)
E, [2015-04-18T06:05:24.431303 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:24.431377 #9390] ERROR -- : retrying in 0.5 seconds (0 tries left)
E, [2015-04-18T06:05:24.931837 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
/srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/socket_helper.rb:158:in `initialize': Address already in use - connect(2) for /srv/www/asumibot/releases/20150418060529/tmp/sockets/un\
icorn.sock (Errno::EADDRINUSE)
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/socket_helper.rb:158:in `new'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/socket_helper.rb:158:in `bind_listen'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:242:in `listen'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:809:in `block in bind_new_listeners!'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:809:in `each'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:809:in `bind_new_listeners!'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:138:in `start'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/bin/unicorn:126:in `<top (required)>'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/bin/unicorn:23:in `load'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/bin/unicorn:23:in `<main>'