課題とスコープ
これは、ノードのコントロールプレーンとランタイムの境界に関するシステムデザインの設問です。標準の CRI である ListContainers、ListPodSandbox、ListImages は、1 回のレスポンスですべての結果を返す単一(unary)RPC です。高密度ノードでは、シリアライズされた結果が gRPC のデフォルトであるメッセージあたり 16 MiB の上限を超え、kubelet の調整(reconciliation)が妨げられることがあります。Kubernetes v1.36 ではアルファ版の CRIListStreaming フィーチャーゲートが導入され、kubelet はサーバーサイドストリーミング RPC 経由で結果を受信できるようになりました。この設計では、スループット、メモリ、互換性フォールバック、およびロールアウトリスクを包括的に対処する必要があります。
面接官の評価ポイント
- 単にノードが大きいと言うだけでなく、メッセージサイズ、シリアライゼーションのメモリ割り当て、調整の失敗を結びつけて説明できているか。
- リストとイベントの整合性を維持しつつ、ストリーミングリスト転送を watch やイベントストリームと明確に区別できているか。
- コンシューマーにバックプレッシャー、キャンセル処理、デッドライン、明示的な部分結果セマンティクスが備わっているか。
- デフォルトで無効化されているアルファゲート、ランタイムの機能、自動フォールバックを理解しているか。
- カナリア、メトリクス、アラート、およびロールバック条件を定義できるか。
確認すべき明確化のための質問
- ピーク時に実行中、停止中、サンドボックスのオブジェクトはいくつ存在し、その数はどのくらいの速度で増加するか?
- コンテナランタイムは 3 つのストリーミング RPC すべてを実装しているか、またアップグレードウィンドウに含まれるバージョンはどれか?
- 兆候はメッセージ制限エラーなのか、kubelet のメモリ圧迫なのか、ランタイム内部でのリスト構築の遅延なのか?
- 短時間の調整の再試行は許容されるか、またフェイルクローズ(fail closed)にしなければならない障害はどれか?
- 分離されたノードプールでアルファゲートを有効化できるか、またどのくらい迅速にロールバックできるか?
30秒での回答
「まず、単一(unary)CRI リストはすべてのオブジェクトを 1 つの gRPC メッセージに含めるため、約 1 万個のオブジェクトでデフォルトの 16 MiB 制限に達し、メモリ割り当てのスパイクが発生することを確認します。Kubernetes v1.36 の CRIListStreaming ゲートはアルファ版でデフォルトで無効になっています。これを有効にすると、kubelet は 3 つのサーバーサイドストリーミング RPC を使用し、ランタイムはチャンクを送信してコンシューマーがそれを段階的にマージします。この RPC をサポートするランタイムのみをカナリア展開し、バッファを制限し、キャンセルを伝播させ、リスト所要時間、メッセージバイト数、メモリ、調整エラーを監視します。非サポートのランタイムは自動的に単一 RPC にフォールバックしますが、従来の障害モードが残るため、高密度ノードには依然としてアラートが必要です。」
ステップごとの詳細解説
ステップ 1: 単一メッセージの障害を切り分ける
単一レスポンスには完全なリストが含まれているため、クライアント側では Protobuf のデコード、オブジェクトの割り当て、状態のマージによるピークが発生します。デフォルトの gRPC メッセージ制限はおよそ 16 MiB であり、オブジェクト数、フィールド長、イメージ名によって超過するかどうかが決まります。およそ 1 万個のコンテナというのは経験則に基づくスケールのシグナルであり、プロトコルの閾値ではありません。オブジェクトサイズ、ランタイムログ、kubelet メトリクスで確認してください。
ステップ 2: ストリーミングのコントラクトを定義する
CRIListStreaming を有効にすると、kubelet は StreamContainers、StreamPodSandboxes、StreamImages を使用します。サーバーとしてのランタイムはバッチを送信し、クライアントはリスト全体を 1 つのメッセージに収めるのではなく、各バッチをデコードしてマージします。既存の調整ロジックを継続する前に、最終リストには依然として一貫したスナップショットが必要です。ストリーミングリストは watch ではなく、継続的なイベント購読を作成するものでもありません。
ステップ 3: バックプレッシャー、キャンセル、障害の制御
デッドラインを設定し、未処理のバッチおよびオブジェクトバッファを制限します。処理が追いつかない場合は読み取りを一時停止するか、ランタイムにフロー制御を検知させます。ノードが消失した場合、同期バージョンが期限切れになった場合、または呼び出し元がキャンセルした場合はストリームを閉じ、goroutine や接続のリークを防ぎます。ストリーム途中の切断を完全な成功として報告してはなりません。一時的なスナップショットを破棄して再試行するか、完全な状態をコミットする前に明示的にバージョン管理されたチェックポイントとべき等なリカバリを使用します。再試行によってオブジェクトが二重カウントされてはなりません。
ステップ 4: 互換性を保ちながらロールアウトする
ランタイムが 3 つのストリーミング RPC すべてを実装しているかをインベントリで確認し、隔離されたノードプールでフィーチャーゲートを有効化します。サポートされていないランタイムは、後方互換性のために自動的に単一 RPC にフォールバックします。そのフォールバックは容量の根本的な解決策ではありません。ノードの機能をラベル付けし、単一 RPC のままの高密度ノードにはアラートを設定します。カナリア展開中には、ストリーミングノードとフォールバックノードの間でリスト所要時間、ピーク RSS、デコードキュー、ストリーム再試行、調整失敗率を比較します。
ステップ 5: オブザーバビリティとキャパシティ保護の追加
リストごとに、オブジェクト数、バッチ数、バッチあたりのバイト数、合計所要時間、最初のバッチまでの時間、ストリームキャンセル、再試行理由を記録します。これらを kubelet の working set、ランタイムの CPU、接続数、ノードプレッシャーと関連付け、メモリのピークが処理時間の長期化に変わっていないか確認します。オブジェクト制限、デッドライン、受付制御(admission control)を設定し、制限を超えた場合はリストを暗黙的に切り詰めるのではなく、診断可能なエラーを返します。成功とは、単に最初のバッチが速いことではなく、完全な調整状態が得られることを意味します。
ステップ 6: ロールバックとアップグレードの境界を定義する
アルファゲートはデフォルトで無効であり、その適用範囲は kubelet 設定を通じて元に戻せる必要があります。ランタイムのクラッシュ、切断の増加、リスト整合性の失敗、またはメモリの改善が見られない場合は、ゲートを無効にして影響を受ける kubelet を再起動し、ランタイムと kubelet のログを調査します。ランタイムのアップグレード中も機能検出とフォールバックを維持し、マルチバージョンマトリクスのテストを通過した後にのみノードプールを拡張します。
高品質な回答例
「私はこのインシデントの原因を、単に gRPC 制限を引き上げるのではなく、単一(unary)CRI リストによる単一メッセージおよびオブジェクト割り当てのピークにあると特定します。Kubernetes v1.36 の CRIListStreaming はデフォルトで無効なアルファ機能です。これを有効にすると、kubelet は 3 つのサーバーサイドストリーミング RPC を呼び出し、ランタイムはコンテナ、サンドボックス、イメージの結果をバッチで送信します。クライアントはデッドライン、制限付きバッファ、キャンセル処理を用いて、バッチを一時的なスナップショットにマージします。切断が発生した場合は不完全なスナップショットを破棄し、調整前にべき等に再試行します。カナリアでは、まずランタイムの RPC サポートを確認し、次にバッチ数、初回および合計所要時間、kubelet RSS、再試行、状態エラーを比較します。サポートされていないランタイムは単一 RPC にフォールバックするため、16 MiB 制限とメモリスパイクのリスクが残る高密度のフォールバックノードには引き続きアラートが必要です。整合性エラーが発生した場合は即座にゲートのロールバックを実行します。」
よくある間違い
- ストリーミングリストを watch として扱い、スナップショットとイベントの境界を失ってしまう。
- シリアライゼーションや kubelet のデコードピークを無視して、gRPC の制限のみを引き上げる。
- すべてのバッチが到着する前に調整をコミットし、切断後に部分的な状態を残してしまう。
- サポートされていないランタイムを特定せずに、自動フォールバックで問題が解決したと思い込む。
- RSS、バッチサイズ、再試行、状態の完全性を測定せず、平均所要時間のみを測定する。
- オブジェクトサイズやメトリクスを確認せず、約 1 万個のコンテナを固定のトリガーとして提示する。
フォローアップの質問と回答
ストリーム途中で切断された後、既に受信したオブジェクトを保持できますか?
デフォルトでは完全なリストとして保持することはできません。バッチを一時的なスナップショットに書き込み、切断後に破棄するか、バージョン管理されたチェックポイントからべき等に復旧します。プロトコルの完了マーカーと検証が成功した後にのみコミットしてください。
ランタイムがストリーミング RPC を 1 つしか実装していない場合はどうなりますか?
メソッドごとに機能をプローブし、サポートされていないメソッドは単一 RPC のままにします。ノードごとに機能ラベルを記録し、高密度ノードに対してアラートを発報します。部分的な有効化ではすべてのリスト障害モードが解消されるわけではありません。
単にメッセージ制限を引き上げてはいけない理由は?
制限を拡大しても、単一メッセージの障害を先送りするだけで、メモリ割り当て、コピー、GC のピークが増大します。また、kubelet/ランタイムのレイテンシースパイクを増幅させる可能性もあります。チャンク単位のストリーミングを採用し、メトリクスを用いて状態の完全性とキャパシティの改善を実証することが望ましいです。