TT Lab
はじめる
学ぶ 学習パス コース

壊すのは私 — 仮説を先に書くカオス実験室

一つ落としたら、何人が痛むのか

TT Labで続きを見る

一言でいうと

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がどの中断にだけ適用されるのかを確認します。