約束の地

キャロ組

Rails を Capistrano でデプロイした際に Puma が再起動できなくて 3時間 ハマった話

前提

詳しい説明は公式ドキュメントを参照するなどして下さい。投げやりですいません……*1

環境

  • Ruby 2.5.3
  • Rails 5.2.1
  • Capistrano Version: 3.11.0 (Rake Version: 12.3.1)

3つの設定ファイル

Capfileconfig/deploy.rbconfig/deploy/production.rb の内容を書きます。config/deploy/production.rb については他の環境へのデプロイの場合は適宜編集をすればよいです。

なお、Pumaに関係ないところは極力省きます

1. Capfile

(省略)

require 'capistrano/puma'
install_plugin Capistrano::Puma

(省略)

2. config/deploy.rb

ここキモです。

(省略)

namespace :deploy do
  task :restart_puma do
    invoke  'puma:stop'
    invoke! 'puma:start'
  end
end

after 'deploy:finishing', 'deploy:restart_puma'

(省略)

3. config/deploy/production.rb

Puma は、ポート番号を指定して起動するものとします。

(省略)

set :puma_init_active_record, true

(省略)

上記だけ書いて、他の set は要らないのかと思うでしょうが、デフォルト設定で行く場合は実はこれで大丈夫です。ただ、いろいろ書き加えたほうがいいのでそれは後述します。

詳細

3つのファイルそれぞれの内容の詳細を書きます。

1. Capfile の詳細

Capistrano::Puma を使うために require 'capistrano/puma'require しています*2install_plugin を書くところがキモです。

2. config/deploy.rb の詳細

実はここの deploy タスクの部分に追加をしなくても、ログを見ると puma:restart してくれるように見えます。

が、私の環境では実際には restart されていなかったため、このような記述をしています。

この記述にはポイントがいくつかあります。

(a) invoke ではなく invoke! でなければいけない

まず、invoke ではなく invoke! である必要があります。これは、タスク実行時の警告メッセージにて注意されます。

Skipping task `puma:start'.
Capistrano tasks may only be invoked once. Since task `puma:start' was previously invoked, invoke("puma:start") at /Users/FOOBAR/PATH/TO/vendor/bundle/ruby/2.5.0/bundler/gems/capistrano-puma-09b11cafb2cf/lib/capistrano/tasks/puma.rake:71 will be skipped.
If you really meant to run this task again, use invoke!("puma:start")
THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.
https://github.com/capistrano/capistrano/issues/1686

上記に挙げられている Issue はこれですね。

(b) invoke! 'puma:restart' ではだめ

invoke! 'puma:restart' ではなく、invoke 'puma:stop' してから invoke! 'puma:start' する必要があります。これは Stack Overflow で知りました*3

なお、invoke! 'puma:restart' でダメな理由は Rake::Task['puma:restart'].reenable を書いていないからではないか、と思いましたが、書いてもダメでした。

3. config/deploy/production.rb の詳細

本当に最小限の動作ならば set :puma_init_active_record, true だけでいい*4のですが、現実的には以下の内容も設定しておいたほうがいいでしょう。

各種のパスは好きなように設定してよいです。ただ、#{shared_path} の場合は予めデプロイ先にディレクトリが存在していなければいけません。

set :puma_conf, "#{shared_path}/config/puma.production.rb"
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_conf, "#{shared_path}/config/puma.production.rb"
set :puma_access_log, "#{release_path}/log/puma.access.log"
set :puma_error_log, "#{release_path}/log/puma.error.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true

上記の設定をすることで、次の「極めて重要なこと」につなげられます。

極めて重要なこと(ここが一番大事かも)

上記の3つのファイルの設定が終わりました。ここで以下のコマンドを入力して Puma の設定ファイルのひな型を作ります。

$ cap production puma:config

上記のコマンドにより、config/deploy/production.rb 内の set :puma_conf で設定した場所に Puma の設定ファイルのひな型が作成されます*5

config/deploy/production.rb の内容が反映された設定ファイルが作られていますので、基本的にはなにもいじくらないで大丈夫です。置き場所も変える必要はありません*6

ただ一点、ポート指定で Puma を起動したい場合には以下の一行を追加する必要があります。これで 12345 ポートで待ち受けます。

ポート指定した場合は bind は以下のようにコメントアウトして良いでしょう。

port ENV.fetch('PORT') { 12345 }

# bind 'unix:///PATH/TO/shared/tmp/sockets/puma.sock'

なお、Puma の設定は上記ファイルを見ますので、config/puma.rb の内容は無視されます。ただ、ファイルを削除すると警告が出てしまうので、全てコメントアウトするか全て削除してファイルだけ残す、という感じでよいのではないでしょうか*7

補足: restart がダメな場合にどういう現象が起きるか

restart に失敗する場合は既存の puma.pid のような .pid ファイルが消えます。.pid ファイルが消えた状態ですと起動に成功します。起動に成功して .pid が作られた状態で restart に失敗すると .pid ファイルが消えます。

この繰り返しになりました。

補足: これでいいとは思えませんが……

このやり方や結果が正しいとはどう考えても思えませんが、ハマりすぎて泣きそうなのでいったんここまでとしました。

デプロイのたびに下記のようなメッセージが出て心臓に悪いです。

f:id:gregminster:20181030153924p:plain

Skipping taskpuma:start'. Capistrano tasks may only be invoked once.という警告メッセージが出ることから、gemを用いればCapistranoに標準で(何もしなくても)puma:restartしています。しかしその restart がうまくいかないため、無理やり再度invoke!` をしているという流れです。

各命令などの詳細は公式ドキュメントを参照するとよいと思います*8

補足: set :puma_hogehoge の記述を config/deploy.rb に書いても反映されない

set :puma_hogehoge の記述は面倒でも各デプロイ環境の設定ファイルに書かないといけないようです*9。そのため DRY ではなくなりますが、しょうがないようです。

補足: 参考にしようとした記事

ちょっと古いだけでバージョン違いでうまく適用できないですね……。

*1:あまりにハマりすぎて疲れてしまいました

*2:もちろん予め Gemfile に書いて bundle install しておきます

*3:なので、丸ごと信じるのは危険ですが

*4:厳密に言えばこれすら無くてもいい

*5:設定していない場合は #{shared_path}/pumar.rb に作られます

*6:ただし、#{shared_path} 配下でない場所に置きたい場合は手元に持ってくる必要があります

*7:私は後者にしました

*8:Stack Overflow や Qiita は参考程度に

*9:確信は持てないのですが

Powered by はてなブログ