最近Capistranoを導入しました.
一般的なRailsのプロジェクトにCapistranoを組み込むのは,このあたりを参考にすると割と楽にいける.
ただ,これに合わせて重要になってくるものがあって,どうにもこれだけでは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
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>'