問題と範囲
これはプラットフォームエンジニアおよび Kubernetes エンジニア向けのコントロールプレーンシステムデザインの設問です。Kubernetes v1.36 では、ShardedListAndWatch ゲートの背後にあるアルファ機能としてサーバーサイドシャーディングされた List と Watch が導入されています。この機能はオプションとして扱い、ゲートが無効化されている場合でも正しく動作するコントローラーを設計してください。
面接官の評価ポイント
- キースペースを過不足なく一度だけ(exactly once)カバーするシャード所有権モデル。
- 正しい初期 List、リソースバージョン(resource-version)の処理、および Watch の再接続動作。
- セレクターを無視した API サーバーの検出。
- リシャーディング、レプリカの入れ替わり(churn)、および重複と欠落のトレードオフ。
- API サーバーの CPU、ネットワーク、informer のメモリ、およびリカバリストームに関するキャパシティの考察。
所有権キーは何にするか?
アルファ版の実装では object.metadata.uid と object.metadata.namespace がサポートされています。UID ハッシュは安定した分散を提供します。ネームスペースハッシュはテナントの局所性を維持できますが、偏りが生じる可能性があります。この選択によって、バランシング、セキュリティ境界、および移行コストが変化します。
正確性の目標は何か?
少なくとも1回(at-least-once)の調整(reconciliation)が許容されるか、また重複処理がべき等であるかを確認してください。ビジネス上の副作用が重複を許容できない場合は、イベントストリームだけに頼るのではなく、永続的なワークキーとフェンシングを追加します。
すべての API サーバーとクライアントを同時にアップグレードできるか?
プラットフォームチームが別段の証明をしない限り、バージョンが混在していると想定してください。コントローラーは応答メタデータを検査し、負荷が削減されたと勝手に判断することなく、クライアント側フィルタリングにフォールバックする必要があります。
30秒の回答フレームワーク
「決定論的な 64 ビットのハッシュ空間を重複のない範囲に分割し、各範囲を 1 つのコントローラーレプリカに割り当てます。各 informer は List と Watch の両方でシャードセレクターを送信し、応答に対応するシャードメタデータが含まれていることを検証します。確認(acknowledgment)が得られない場合は、安全な完全ストリームへのフォールバックをトリガーするか最適化を無効化します。リシャーディングには世代管理と重複を伴うハンドオフプロトコル、べき等な調整、およびカバレッジ、重複、ラグ、フォールバックトラフィックのメトリクスを使用します。」
ステップごとの設計
パーティショニングと割り当て
所有権を 64 ビットリング上の半開区間 [start, end) として表現します。範囲ごとに世代(generation)、所有者、およびリース(lease)を保持します。2 レプリカ構成のデプロイではリングを半分に分割できます。レプリカ数が多い場合は、コントローラーが管理する範囲テーブルを使用します。再起動によってギャップが生じる可能性があるため、レプリカの序数(ordinal)のみから所有権を導出してはいけません。
Replica A: [0000..., 8000...)
Replica B: [8000..., 1000...)List と Watch を単一のプロトコルにする
初期 List とすべての Watch 再接続には、同じセレクターとリソースバージョンを含める必要があります。informer は、初期 List が完了した後にのみローカルストアを置き換え、返されたリソースバージョンから Watch を開始します。410 Gone または期限切れのバージョンが発生した場合は、別の範囲を盲目的に再生するのではなく、そのシャードに対して新規 List を実行します。
サーバーサポートの検証
v1.36 のブログでは、適用されたセレクターをエコーバックする shardInfo 応答フィールドについて説明されています。これが存在しない場合は、サーバーがコレクション全体を返したと想定します。クライアントは正確性を保つためにローカルでフィルタリングできますが、フォールバックメトリクスを出力し、未対応サーバーによってレプリカ全体のメモリ使用量が何倍にも増加しないようにバックプレッシャーを適用する必要があります。
欠落のないリシャーディング
新しい世代の範囲を作成します。ハンドオフ中、古い所有者は調整を継続し、新しい所有者は List をウォームアップして既知のリソースバージョンでの Watch を待機します。双方が準備完了を報告した後にのみ所有権の変更をコミットします。調整がべき等であればイベントの重複は許容されます。準備完了に失敗した場合は、リースを失効させて古い所有者を維持します。
キャパシティと障害パス
サーバーサイドフィルタリングにより、破棄されるオブジェクトのバイト数とデシリアライズ処理が削減されますが、API サーバー側でハッシュ計算とセレクターの評価が実行されるようになります。フィーチャーゲート、コントローラーごとの並行性制限、およびロールアウトカナリアで API サーバーを保護します。API サーバーが過負荷になった場合は、再接続をスロットリングして指数バックオフ(exponential backoff)を使用し、すべてのレプリカが一斉に再 List を実行しないようにします。
高品質な回答例
「UID の決定論的ハッシュに基づく世代スタンプ付きの範囲テーブルを使用します。informer は List と Watch にそのセレクターを含め、最適化がアクティブであるとみなす前に shardInfo を要求します。フィールドが存在しない場合は、グローバルな並行性バジェットを伴うローカルフィルタリングにフォールバックします。リシャーディングでは新しい世代を作成し、リソースバージョンから受け入れ側の所有者をウォームアップし、準備完了後にのみリースを切り替えます。調整はべき等であるため重複は安全です。フィーチャーゲートのカナリアリリースを実施し、API の CPU、バイト数、List レイテンシ、Watch 再接続数、シャードカバレッジ、重複、およびフォールバック率を比較・監視します。」
よくある間違い
- レプリカのインデックスでシャードを割り当てる → 再起動によって所有権が変わりギャップが発生する → 世代スタンプ付きの範囲テーブルを永続化する。
- Watch にのみセレクターを適用する → 初期 List が依然としてサーバーに負荷をかけ、ストリームと不一致になる可能性がある → 両方に同一のセレクターとリソースバージョンのルールを使用する。
- 未対応のサーバーを信頼する → すべてのレプリカが完全なストリームを受信してしまう →
shardInfoを必須とし、フォールバックトラフィックを可視化する。 - 範囲を瞬時に移動する → ハンドオフ中にイベントが欠落する可能性がある → 所有者を重複させ、世代でフェンシングし、調整をべき等にする。
- コントローラーの CPU のみ測定する → API サーバーのハッシュ処理や再接続ストームが見過ごされる → コントロールプレーンとクライアントのメトリクスを併せて監視する。
評価基準とセルフチェック
パーティションの正確性、list-watch セマンティクス、フォールバック、リシャーディング、キャパシティ、および可観測性を評価します。優れた回答では、すべてのオブジェクトが 1 つのアクティブな範囲世代に属し、すべてのハンドオフにリソースバージョンの境界があるという不変条件(invariant)が示されます。また、調整がべき等である場合に、なぜ欠落よりも重複の方が安全であるかも説明されます。
フォローアップと発展課題
1つのネームスペースが他よりもはるかに大きい場合はどうするか?
バランスを取るために UID ハッシュを優先するか、ホットなネームスペースを複数の UID 範囲に分割します。オブジェクト数が均等であると仮定するのではなく、範囲ごとの負荷を測定します。
サイレントな欠落をどのように検出するか?
サンプリングしたグローバルなオブジェクト数とシャードごとの合計数を比較し、セレクター世代を記録し、定期的にすべての範囲に合成(synthetic)オブジェクトを流します。ラグやカバレッジの乖離に対してアラートを設定します。
API サーバーのアップグレード中はどのような動作になるか?
カナリアによってすべてのサービングエンドポイントから shardInfo が確認されるまで、フィーチャーゲートは無効のままにします。応答が混在する場合はフォールバックをトリガーし、再接続レートを制限します。
コントローラーが同一オブジェクトを2回処理する可能性はあるか?
はい、重複期間中や再接続時に発生します。オブジェクトバージョンと永続的なワークキーを使用して副作用をべき等にします。状態の欠落という無限のリスクと引き換えに重複調整を排除しようとしてはなりません。