pm2 reloadで無停止デプロイ!クラスタ機能を用いたローリング更新の実践

ゴールデンウィーク中に本番サーバーの更新を止められない、という制約から今回の検討は始まりました。長期休暇はユーザーのアクセスが平常時と変わらず続く一方、社内のサポート体制は手薄になります。メンテナンス時間を確保できない中で、どうやってプロセスを入れ替えるか。結論から言うと、私たちが選んだのはpm2のクラスタ機能を使ったローリング更新でした。

栗原清徳です。GW中に本番を止められない制約から、pm2クラスタでの無停止更新を検討した話をまとめました。コンテナ切替案から方針転換した経緯も含め、pm2 reloadによるローリング更新に落ち着くまでの過程を紹介します。

前提・用語の整理

本題に入る前に、いくつか用語を整理しておきます。pm2はNode.jsアプリケーション向けのプロセスマネージャーで、プロセスの常駐化や自動再起動、ロードバランシングを提供するプロダクション向けツールです[1]。クラスタモードでは、PM2_INSTANCESという設定値(あるいは-iオプション)で起動するプロセス数を指定でき、組み込みのロードバランサーによってリクエストが複数プロセスに分散されます[2]。CPUコア数に合わせて自動でプロセス数を決めるmax指定も可能です[2]

この記事で「無停止デプロイ」と呼ぶのは、稼働中のアプリケーションへのリクエストを取りこぼすことなく、新しいバージョンのプロセスへ切り替えることを指します。比較対象として登場するBlue/Greenデプロイは、稼働中の環境(Blue)とは別に新環境(Green)を丸ごと用意し、準備が整ってから通信の向き先を切り替える手法です。今回検討したコンテナ切替案も、この考え方の一種になります。

GW中に更新を止められない事情

本番反映の予定時期がゴールデンウィークの連休と重なってしまい、通常であれば確保できるメンテナンス時間帯を取れない状況でした。休暇中はユーザーのアクセスが減るどころか、サービスによってはむしろ増えることもあります。障害対応の人員も薄くなるため、切り替え作業でエラーを出す余地はほとんどありません。

そこで今回の成功基準は明確にひとつに絞りました。「ユーザーのリクエストを落とさずに新バージョンへ切り替えること」です。レイテンシの多少の揺らぎは許容しても、5xxエラーやコネクション切断が発生する切り替え方式は採用しない、という線を最初に引きました。

検討した3つの選択肢

この成功基準を満たす手段として、次の3案をテーブルに載せました。

概要
①差分なしデプロイコードも環境変数も変更しない前提で本番反映する
②pm2 reload案PM2_INSTANCESを複数化し、pm2のクラスタ内ローリング更新でプロセスを1台ずつ入れ替える
③コンテナ切替案VM内で別名の新バージョンコンテナをビルドし、nginxをreloadして向き先を切り替える(Blue/Greenの一種)

①は最も単純な発想で、変更点が本当に何もなければ再起動そのものが不要になる、という理屈です。②はpm2の標準機能を使う案で、③は稼働中のプロセス群とは独立した新環境を用意してから通信を切り替える案です。まずはこの3つを並べて、それぞれの実運用への適合度を検討しました。

①案と③案を外した理由

①案から検討を外した理由は単純です。「コードも環境変数も変更しない本番デプロイ」という前提そのものが、実運用ではほぼ成立しません。何らかの修正や設定変更があるからこそデプロイが発生するのであって、無変更を前提にした対策は現実の運用フローに対する解決策になっていないと判断しました。

③案は最初、有力候補として検討していました。VM内で新バージョンのコンテナを別名でビルドし、起動確認が済んだ段階でnginxの設定をreloadして向き先を新コンテナに切り替える。切り替え自体は無停止で行えますし、Blue/Greenデプロイの典型的な実装として一定の実績もある方式です。

ただし検討を進める中で、③案を採用するとpm2クラスタの恩恵を手放すことに気づきました。pm2クラスタモードは複数プロセスへの負荷分散に加えて、1プロセスがクラッシュしても他プロセスが処理を継続する耐障害性を持っています[2]。コンテナ単位で丸ごと切り替える③案は、この複数プロセス構成による分散処理の恩恵を活かせない構成になってしまいます。ここが今回の判断の分かれ目でした。

📦 ③コンテナ切替案(Blue/Green型)

  • 新バージョンのコンテナを別名で丸ごとビルド
  • nginx reloadで向き先を新環境に切替
  • 切替自体は無停止だが、新コンテナのビルド・起動確認の工程が必要
  • pm2クラスタによる複数プロセス分散の恩恵を活かせない

✅ ②pm2 reload案

  • 既存のpm2クラスタ内でプロセスを1つずつ入れ替え
  • 常に稼働中のプロセスを残しながら更新できる[3]
  • PM2_INSTANCESの複数化のみで既存構成を活かせる
  • 複数プロセスによる負荷分散・耐障害性をそのまま維持

採用した設計:pm2 reloadによるローリング更新

最終的に採用したのは②のpm2 reload案です。pm2 reload <app_name>を実行すると、クラスタ内の全プロセスを同時に落とすのではなく、1つずつ順番に再起動します。この間、少なくとも1プロセスは稼働し続けるため、リクエストを受け続けたままバージョンの入れ替えが完了します[3]

この方式が機能する前提として、PM2_INSTANCESが2以上に設定されている必要があります。1インスタンスしか立てていない構成でreloadを実行すると、入れ替え中に稼働プロセスがゼロになる瞬間が生じてしまうためです。今回の設計変更の核心は、実はコードの変更ではなくこのインスタンス数の見直しにありました。

公式ドキュメントでは、reloadがタイムアウトした場合は通常の再起動にフォールバックする点、そしてリクエストを取りこぼさないためにはアプリケーション側でグレースフルシャットダウンを実装しておく必要がある点も注意点として挙げられています[3]。今回のアプリケーションでは既存のシグナルハンドリングで賄えると判断しましたが、この前提を確認せずにreloadだけ導入すると、切り替え中の瞬断につながりかねません。

実装フロー

設定変更点は、環境変数PM2_INSTANCESを1から複数(CPUコア数や想定負荷に応じた値)に変更することが中心です。あわせて、reload実行時にクラスタ内でどのようにプロセスが入れ替わっていくかを整理しておきます。単純な一本道の起動シーケンスではなく、複数プロセスが並行して稼働しながら順番に置き換わっていく構造なので、図に起こしました。

No
Yes
並行して稼働
pm2 reload 実行
対象プロセス群を1つずつ選択
選択したプロセスへの新規リクエスト割当を停止
処理中リクエストの完了を待機
旧プロセスを終了し新バージョンで起動
全プロセス入替済み?
ローリング更新完了
他プロセスはリクエスト処理を継続

内部的なタイムアウト挙動やフォールバック条件の詳細は公式ドキュメントに委ねますが[3]、運用側で押さえておくべき点は「PM2_INSTANCESを複数化しない限りこの仕組みは機能しない」という一点に尽きます。

工数見積もりと採用判断

採用にあたっては工数面でも③案との差が判断材料になりました。②案は実装1.5人日、レビュー0.5人日の計2人日という見積もりです。既存のpm2運用に環境変数変更を1つ加えるだけの変更のため、見積もりの精度も比較的高いと考えています。

項目②pm2 reload案
実装1.5人日
レビュー0.5人日
合計2人日

③案は新規コンテナのビルドパイプラインとnginx設定の切替ロジックを別途構築する必要があり、実装対象の範囲が広い分、見積もりの前提も増えます。今回は工数の絶対値だけでなく、既存構成を大きく崩さずに済むという複雑度の低さも②案を後押しする材料になりました。

残る検討事項

pm2 reload案の採用によって切り替え方式の骨格は決まりましたが、これで全てが解決したわけではありません。ステージング環境でのイメージスワップをどう本番反映のフローに組み込むかは、まだ確定していない部分です。本番反映方法そのものの確定作業は別タスクとして残っており、pm2 reloadの導入はあくまで「無停止で切り替える手段」を決めた段階にとどまります。

また、グレースフルシャットダウンの実装がアプリケーション側で十分かどうかも、reload運用を続ける中で継続的に見ていく必要がある項目です。設定を変えれば終わりという話ではなく、実際の切り替え挙動を本番相当の環境で確認する工程は別途組む予定です。

まとめ

今回のGW無停止更新の検討では、pm2クラスタのPM2_INSTANCESを複数化した上でpm2 reloadを使うローリング更新方式を採用しました。既存のクラスタ構成が持つ負荷分散と耐障害性を維持したまま、プロセスを1つずつ入れ替えることでリクエストを落とさずに切り替えるという、成功基準に対して過不足のない選択だったと考えています。

最初は新環境を丸ごと用意するコンテナ切替案(③)を有力視していましたが、検討を進める中でpm2クラスタの恩恵を手放す構成になると気づき、方針を転換しました。この判断の転換点こそが、今回の検討で一番学びの多かった部分です。本番反映フローの確定という宿題は残っていますが、切り替え方式の骨格を固められたことは前進だったと感じています。同じ制約に直面する方の参考になれば幸いです。

🔗 参考リンク

  1. PM2 – Configuration File
  2. PM2 – Load-Balancing | Guide | PM2 Documentation
  3. PM2 – Load-Balancing | Guide | PM2 Documentation