グレースフルシャットダウン

OxPHP は SIGTERMSIGINT を処理し、処理中のリクエストが完了してからプロセスを終了します。これはコンテナオーケストレーションにおけるゼロダウンタイムのデプロイやローリングアップデートで重要になります。

シグナルの処理

OxPHP は 2 つのシャットダウンシグナルに応答します。

シグナル 発生源 挙動
SIGTERM コンテナオーケストレーター、docker stopkill グレースフルシャットダウンを開始
SIGINT ターミナルの Ctrl+C グレースフルシャットダウンを開始

どちらのシグナルも同じシャットダウンシーケンスをトリガーします。必要なのは最初のシグナルだけです。サーバーはただちにドレインを開始します。

シャットダウンシーケンス

シャットダウンシグナルを受信すると、OxPHP は次のシーケンスに従います。

  1. 新しい接続の受け付けを停止する — サーバーはメインポートでの新しい TCP 接続の受け付けを停止します。PHP ワーカーは処理中のリクエストを処理するために動作を続けます。
  2. 稼働中の接続を段階的に終了する — HTTP/2 クライアントには GOAWAY フレームが送られ、アイドル状態の HTTP/1.1 keep-alive 接続は閉じられます。これにより、クライアントは終了しつつあるインスタンスに新しいリクエストを多重化するのではなく、正常なインスタンスへ移動します。オープン中のストリームは速やかかつクリーンに終了されます。チャンク出力のフラッシュを開始したレスポンスはすべて対象で、有限サイズのダウンロードも SSE も含まれます。Server-Sent Events を参照してください。
  3. 処理中のリクエストをドレインする — 通常の実行中リクエストはそのまま残され、完全なレスポンスを返して完了できます。サーバーは 100ms ごとに完了を確認します。内部のヘルス/メトリクスサーバーはドレインの間ずっと利用可能なままなので、readiness プローブは引き続き機能します。
  4. ドレインの期限を強制するDRAIN_TIMEOUT_SECONDS を過ぎてもまだ実行中のリクエストはキャンセルされ(その register_shutdown_function() コールバックは引き続き実行されます)、サーバーが処理を進める前に後始末のために約 2 秒が与えられます。
  5. プラグインをフラッシュする — ドレインウィンドウの間にバッファされたアクセスログのエントリと APM スパンがフラッシュされます。
  6. 非同期プールをシャットダウンする — バックグラウンドの非同期タスクプールが停止されます。
  7. 内部サーバーを中断する — ドレインの完了後にヘルス/メトリクスサーバーが停止されます。
  8. 終了する — プロセスはステータスコード 0 で終了します。
Note

PHP ワーカーはステップ 1 で明示的に停止されます。メインサーバーの停止の一環としてエグゼキューターの shutdown() が呼び出され、これによりワーカースレッドは処理中のリクエストを完了してから終了するようシグナルを受け取ります。

設定

変数 デフォルト 説明
DRAIN_TIMEOUT_SECONDS 25 処理中のリクエストがキャンセルされるまでに完了できる最大秒数。プロセスは期限の約 2 秒後に終了します。デフォルト値は、期限後の後始末とテレメトリのフラッシュのための余裕を、Kubernetes のデフォルトの 30 秒の終了猶予期間の中に残しています

DRAIN_TIMEOUT_SECONDS は、想定される最も遅いリクエストに合わせて設定してください。

  • 高速なレスポンスを返す API サーバー: 1015
  • ファイルアップロードや長時間のクエリを伴う アプリケーション: 3060
  • バックグラウンド処理を行う ワーカーモード: 想定される最も長い処理に合わせる

Kubernetes

Kubernetes におけるローリングアップデート中のシャットダウンフローは次のとおりです。

  1. Kubernetes が Pod に SIGTERM を送信します。
  2. Pod が Service のエンドポイント一覧から削除されます。
  3. OxPHP が DRAIN_TIMEOUT_SECONDS 以内に処理中の接続をドレインし、その後で残っているものをキャンセルして、さらに約 2 秒以内に終了します。
  4. terminationGracePeriodSeconds を過ぎても Pod がまだ実行中の場合、Kubernetes は SIGKILL を送信します。

terminationGracePeriodSecondsDRAIN_TIMEOUT_SECONDS + 2 より大きく設定し、ドレイン(期限後の後始末とテレメトリのフラッシュを含む)が強制終了の前に完了するようにしてください。

yaml
apiVersion: apps/v1 kind: Deployment spec: template: spec: terminationGracePeriodSeconds: 45 containers: - name: oxphp image: ghcr.io/oxphp/oxphp:0.10.0 env: - name: DRAIN_TIMEOUT_SECONDS value: "30"

pre-stop フック

外部のロードバランサーからトラフィックを受け取っており、そのエンドポイントの変更の伝播が遅い場合は、pre-stop フックを追加してシャットダウンシーケンスを遅延させてください。

yaml
lifecycle: preStop: exec: command: ["sleep", "5"]

これにより、OxPHP が接続の受け付けを停止する前に、ロードバランサーが自身のターゲット一覧から Pod を削除する時間が確保されます。

Docker

docker stop を実行すると、Docker は SIGTERM を送信します。Docker の停止タイムアウトのデフォルトは 10 秒で、それを過ぎると Docker は SIGKILL を送信します。

OxPHP にドレインのための十分な時間を与えるため、停止タイムアウトを延長してください。

bash
docker stop --time 45 my-oxphp-container

あるいは Compose ファイルで設定します。

compose.yaml
services: oxphp: image: ghcr.io/oxphp/oxphp:0.10.0 stop_grace_period: 45s environment: DRAIN_TIMEOUT_SECONDS: "30"

ログメッセージ

グレースフルシャットダウンの間、OxPHP は監視可能な構造化ログメッセージを出力します。

ドレインの成功時:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"INFO","message":"Draining in-flight connections","active_connections":3} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

ドレインの期限に達した時:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

「Drain timeout reached」の警告が頻繁に見られる場合は、DRAIN_TIMEOUT_SECONDS を増やすか、oxphp_request_duration_us ヒストグラムを使って長時間実行されているリクエストを調査してください。

関連項目