要点: 停止時間の多くは、能力不足だけでなく、システムが切り替わる瞬間に発生します。
この記事の用語: Readiness:トラフィックを受けられる状態。Drain:新規受付を止め、処理中リクエストを完了させること。
現場の課題
新しいプロセスが準備前に通信を受けたり、古いプロセスが処理中のまま停止されたりすると障害になります。CPU平均だけでは、この二つの問題を検知できません。
単純な対策だけでは不十分な理由
オーケストレーター、ロードバランサー、アプリケーションが、いつトラフィックを受け、いつ処理完了とみなすかで一致する必要があります。Readinessが早すぎても、外部依存を見すぎても停止を招きます。
処理フロー
p99と飽和度→Autoscaler
早めに台数追加→Readiness
依存先を確認→ロードバランサー
安全な通信だけ転送
アーキテクチャ上の判断
飽和の前にスケールする
p95/p99 遅延、Active 同時実行数、最古ジョブの待ち時間、依存先の飽和度を指標にします。Image Pull、プロセス起動、Readiness Delay、キャッシュ Warm-upにかかる時間を見込み、その分だけ早めに処理能力を追加します。
Readinessは有意味かつ局所的にする
通信の処理に必要な設定と、重要なLocal依存だけを確認します。すべてのDownstreamに依存させると、一つのRemote障害だけで、本来使えるレプリカまで全て外れてしまいます。
終了の前にDrainする
SIGTERMを受け取ったら、まずUnreadyにしてロードバランサーへの反映を待ちます。新規のBackground ジョブ取得を止め、実行中のリクエストを完了し、その後で有限のGrace Period内にDBとブローカーの接続を閉じます。
さらに深く考える
Probeは一般的なHealthではなくライフサイクルを表す
Startup Probeは、遅い初期化をLivenessによる再起動から守ります。Livenessはプロセスが前進できるかを答えるもので、通常は局所的にします。DB障害で全アプリケーションのレプリカを同時に再起動させてはいけません。ReadinessはDrain中や、本当に必須な依存を失ったときに変化してよい指標です。可能であれば、Probe Handlerを過負荷のリクエスト プールから切り離し、その遅延も計測します。常にSuccessを返すProbeは無意味ですが、依存サービス Graph全体を再帰的に確認するProbeも、提供可能な処理能力を全て外して障害を増幅する危険があります。
処理能力 Planningには変更中の余力も含める
通常のトラフィックに加えてRolling Updateを行うには、Surge 予備能力が必要です。ピーク 同時実行数、Instance当たりのSafe 同時実行数、そして1つの障害領域喪失から、Minimum Healthy レプリカを計算します。次に、StartingとDrainingのInstanceがReady 処理能力を最小値より下げないよう、maxUnavailableとmaxSurgeを選びます。オートスケールにはStabilization Windowが必要で、Rollout自体が生む一時的なメトリクスに反応しないようにします。キュー コンシューマーでは、終了をLeaseや処理権限の有効時間と調整します。WebSocketやStreamingの通信では、通常のHTTP Drainの前提が当てはまらないため、Reconnectの動作を定義します。
実装手順
- 平均値だけでなく、P99遅延、資源飽和度、キュー待ち時間でスケールします。
- Livenessは単純に保ち、Readinessでトラフィックを受けられるかを判断します。
- 終了時は新規トラフィックを止め、処理中リクエストを完了してから依存接続を閉じます。
コード例: アプリケーション終了の状態機械
RUNNING --SIGTERM--> DRAINING
DRAINING: readiness = false
DRAINING: Queue Jobの新規取得停止
DRAINING: Load Balancer反映待ち
DRAINING: await inFlightRequests == 0
DRAINING: DB・Broker PoolをClose
DRAINING --deadline--> TERMINATED
terminationGrace >= propagation + maxRequest + cleanupShutdownの期限は、通常の最大リクエスト時間より長く、それでいて有限にします。長時間のデータ出力はHTTPを開いたままにせず、Lease 復旧付きの非同期ジョブにします。
想定しておく障害
- マイグレーション、プール、必須設定の準備が整う前に、ReadinessがHealthyになってしまいます。Startup Probeと、順序を明確にしたInitialization Barrierを使います。
- Readinessの変更後も、ロードバランサーが通信を送り続けます。実際のPropagation Timeを測り、Pre-stop DelayとTermination Graceに含めます。
- Auto ScalingとRolling Updateが、同時に処理能力を減らします。maxUnavailable、Surge 処理能力、Disruption Budgetを、同じ最小健全容量モデルから決めます。
監視すべきこと
| 指標 | 何が分かるか |
|---|---|
| 利用者視点のSuccess Rateとp99 | 実際のリクエストが成功し続け、遅延目標の範囲内にあるときだけ、リリースをHealthyと判断できます。 |
| Ready・Starting・Drainingのレプリカ数 | 実行中のプロセスをすべて処理能力とみなすのではなく、ライフサイクルの遷移を可視化します。 |
| 終了時のIn-flight リクエスト数 | 0を超える強制終了は、Drain時間の不足かStuck リクエストの存在を示します。 |
| デプロイ中のエラー-budget Burn | 小さな失敗が全Rolloutへ広がる前に、自動的に一時停止またはロールバックできます。 |
設計の検証方法
- Rolling Update中に実リクエストを連続で送信し、Success率、p99、接続 Resetがいずれも目標内に収まるか確認します。
- Startup依存を通常のWarm-up以上に遅延させ、早期に通信を受けずに再起動 Loopを防げるか検証します。
- 短時間・長時間のリクエストを実行中にInstanceを終了し、期限の前に新規割り当てが止まるか確認します。
- デプロイ中に1つのAvailability Zoneを外し、Disruption BudgetがMinimum Healthy 処理能力を守るか試験します。
- 不良なリリース Signalを発生させ、自動一時停止またはロールバックがエラーバジェット内で完了するか測ります。
本番導入の進め方
起動時間、ロードバランサーの反映時間、最長リクエスト時間を測ります。小さなグループでDrainを有効化し、強制終了を確認した後、SLOに基づく一時停止とロールバックを導入します。
本番前チェックリスト
- Readinessの確認項目が多すぎると、値が揺れて正常な台数まで外れてしまいます。
- スケール条件には、Warm-up時間も含めます。
- 本番前に、実運用相当の同時実行数でRolling Updateを検証します。
まとめ
無停止デプロイは、準備、受付、Drain、終了までのトラフィックのライフサイクルを管理することです。
