サーバー再起動後も PM2 を生き残らせる(必要なのは二つ)
サーバーが再起動して、サイトが戻ってこなかった。戻ったのは一時間後、誰かがデプロイを流したときです。デプロイの問題に見えますが違います。そのデプロイがやったのは、手作業で pm2 start をもう一度実行したことだけです。
アプリを自動で起動させるには、独立した二つの仕組みが必要で、片方だけでは何の役にも立ちません。
二つの仕組み
pm2 save は現在のプロセス一覧を ~/.pm2/dump.pm2 に書き出します。PM2 が復帰させるのはこのファイルの中身だけです。pm2 delete と pm2 start はするのに保存しないデプロイスクリプトは、古いままのダンプを残します。一度も保存していなければ、そもそも空です。
pm2 startup は、起動時に pm2 resurrect を実行する systemd ユニットを入れます。そのダンプを読むのがこのユニットです。
片方だけだと、何も起動しないブートか、誰も読まない保存済みリストのどちらかで終わります。
落とし穴
権限のないユーザーで pm2 startup を実行しても、何ひとつインストールされません。systemd を検出し、ユニットを入れるためのコマンドを組み立て、それを画面に出して終了するだけです。出力は長く、緑色で、最後に「このコマンドをコピーせよ」という行が付きます。成功したように読めます。実際には何も起きていません。
だから root で実行し、確認は PM2 の出力ではなく systemd で取ります。
正しい順番
保存より先にアプリが動いている必要があります。そうでないと空のリストを保存することになります。
# <user> で実行 — アプリが動いていることを確認
pm2 status
# root で実行 — そのユーザー用のユニットを入れる
env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>
先頭の env PATH=$PATH:/usr/bin は飾りではありません。root の PATH には普通 Node が入っておらず、ユニットは生成時のパスをそのまま記録します。これがないとユニットは存在するのに、起動時に node を見つけられません。
# ふたたび <user> で — ダンプを書き出す
pm2 save
それぞれを別々に確認する
壊れるときは別々に壊れるので、確認も別々にします。
systemctl is-enabled pm2-<user>
enabled と出なければいけません。disabled でも Failed to get unit file state でも、ユニットが入っていないという意味です。PM2 が何を表示していたかは関係ありません。
grep -o '"name":"[^"]*"' /home/<user>/.pm2/dump.pm2
ここに <app> が出る必要があります。ファイルが無い、あるいは grep が何も出さないなら、アプリが動いている状態で pm2 save を実行したことが一度もありません。
再起動せずに試す
これが動くかどうかを知るために、本番機を再起動する必要はありません。
# <user> で
pm2 kill
# root で
systemctl start pm2-<user>
# <user> で
pm2 status
curl -sf http://127.0.0.1:3000/health
<app> がオンラインに戻れば、二つとも効いています。ブート時に起きるのとまったく同じことです。
pm2 kill のあとに pm2 save を実行してはいけません。 デーモンを落とした状態ではプロセス一覧が空で、保存するとその空っぽがダンプに書き込まれます。いま試していたものを自分で壊すことになります。反射的にやってしまったら、アプリを起動し直してから pm2 save をやり直してください。
デプロイスクリプトに pm2 save を入れる
デプロイがプロセスに触れた瞬間から、ダンプはずれ始めます。削除して起動し直すスクリプトなら、そのあとに保存が要ります。
pm2 delete <app> || true
pm2 start ecosystem.config.cjs --only <app>
pm2 save
|| true が効くのは初回です。削除する対象が無いと pm2 delete は 0 以外で終了し、set -e のもとではスクリプト全体がそこで止まります。
この最後の一行がなくても、アプリ名を変えるか、インスタンス数を変えるか、エントリポイントを差し替えるデプロイが来るまでは動き続けます。そこから先、ダンプはもう存在しないプロセスを指し、次の再起動はそれを復活させます。
起動順序について
生成されるユニットは After=network.target です。データベースの後ろには並んでいません。コールドブートでは、MariaDB や PostgreSQL が接続を受け付ける前にアプリが起動し、最初のクエリで失敗して落ちることがあります。
たいていは自然に収まります。PM2 がプロセスを再起動し、二回目か三回目にはデータベースが立ち上がっていて、サイトが上がります。見分けるしるしは、ほかは健全なプロセスの再起動カウンタがゼロより大きいこと。pm2 status の ↺ 列に出ます。
回復しない場合だけ、順序を明示します。
sudo systemctl edit pm2-<user>
[Unit]
After=network.target mariadb.service
実際に失敗を見たときだけ追加してください。遅いサービスや別ホストのサービスの後ろにユニットを並べると、再試行して復帰するブートが、止まったまま待つブートに変わります。