一つ落としたら、何人が痛むのか
一言でいうと
Podを1つ殺したときに何人が痛むかは、レプリカ数よりも終了手順のほうが大きく左右します。
なぜ必要なのか
「レプリカを増やしたので、無停止です」という文は、半分しか正しくありません。レプリカが3つあっても、デプロイのたびに失敗が数件ずつ漏れ出すサービスは、よくあります。いつも数件だけなので誰も報告せず、そのため何年もそのままです。この漏れるリクエストがどこから出てくるのかを知るには、Podが死ぬ過程を、1段階ずつ見る必要があります。
どう動くのか
Podの削除リクエストが入ると、2つのことが同時に始まります。1つは、コントロールプレーンがそのPodをServiceのEndpointSliceから外すことで、もう1つは、kubeletがコンテナを停止することです。Podのライフサイクルのドキュメントは、この2つが順序ではなく並列だとはっきり書いています。終了中のエンドポイントのready状態は常にfalseになり、負荷分散の対象から外れますが、その事実がノードのiptablesルールまで広がるには、時間がかかります。
問題は、ここで生まれます。ルールがまだ広がっていない、その短い間、新しい接続が、死にかけているPodに入り続けますが、そのPodのプロセスはすでにTERMを受けてソケットを閉じてしまっています。リクエストは接続拒否で失敗します。アプリケーションは正常で、Kubernetesも正常なのに、ユーザーだけが失敗します。
解決の鍵は、プロセスをもう少し粘らせることです。コンテナライフサイクルフックのpreStopは、コンテナにTERMシグナルを送る前に実行され、このフックが終わって初めてシグナルが出ます。フックで数秒だけ引き留めておけば、その間にエンドポイントの削除が広がり、新しい接続は、生きているPodだけに行きます。フックが動いている間も、アプリケーションは引き続きサービスしているという点が、核心です。
잘못된 기대 실제 순서
1. 엔드포인트에서 뺀다 엔드포인트 제거 ─┐ 동시에 시작
2. 그다음 TERM 을 보낸다 TERM 전송 ──────┘
preStop 이 있으면 TERM 만 뒤로 밀린다
時間のバジェットは、terminationGracePeriodSecondsが決めます。デフォルトは30秒で、この時間が過ぎてもコンテナが生きていれば、ランタイムがKILLで終わらせます。そして、猶予期間のカウントダウンは、preStopフックが始まる前に、すでに始まっています。フックに5秒を使うことにしたなら、猶予期間はそれより余裕がある必要があります。
もう1つ区別しておくことがあります。中断(disruption)のドキュメントは、中断を自発的なものと非自発的なものに分けています。PodDisruptionBudgetは自発的な中断(ノードのドレイン、クラスターのアップグレード)にだけ介入します。私たちが手でkubectl delete podをしたり、プロセスが死んだりすることは、PDBでは防げません。「PDBを掛けたので安全です」が危険な理由です。
接続を再利用するクライアントなら、話がさらに1層複雑になります。エンドポイントの一覧は、新しい接続がどこへ行くかを決めるだけで、すでに開いている接続を移してはくれません。keep-aliveで接続を握っているクライアントは、エンドポイントから外れたPodと話し続け、そのPodが死ぬ瞬間に失敗します。そのため、終了処理をきちんとするには、preStopで時間を稼ぐことに加えて、アプリケーションがTERMを受けて開いている接続を片付けながら終了するコードまで必要です。このラボのロードジェネレーターが、リクエストごとに新しい接続を開くのは、この変数をわざと除くためです。
現場での姿
ノード1台をドレインする作業で、この3つが一度に表に出ます。PDBがなければ、同じサービスのPodが同時にすべて抜けて、完全な停止になり、PDBがあっても、終了処理がなければ、Podが抜けるたびに失敗が漏れ出します。レプリカ数は、どれだけ大きく痛むかを決め、終了手順は、そもそも痛むかどうかを決めます。
デプロイ戦略も、同じ軸で読みます。ローリングアップデートは、一度にいくつまで抜けてよいか(maxUnavailable)と、一時的にいくつまで多く起動してよいか(maxSurge)で、爆発半径を調節する仕組みです。逆にRecreateは、古いPodをすべて落としてから新しいPodを上げるので、停止が必ず生じます。実験環境でRecreateを使うのは、原因を1つだけ残すための統制条件であり、運用の推奨値ではありません。古いPodが残っていると、新しい設定の効果が古いPodの応答に隠れて、数字がぼやけます。
次のクイズで確認すること
エンドポイントの削除とTERM送信の順序、preStopフックが何を後ろへ遅らせるのか、PodDisruptionBudgetがどの中断にだけ適用されるのかを確認します。