課題とスコープ
ノードが起動した後、GPU ドライバ、CNI、ストレージプラグイン、またはローカルエージェントが使用可能でない場合でも、kubelet が Ready を報告することがあります。スケジューラが Pod を過剰に早い段階で配置すると、ワークロードが繰り返し失敗したり、サイレントに性能低下したりします。追加の前提条件を宣言し、それらが満たされるまでスケジューリングをブロックし、ノードのライフサイクルの後半で依存関係に障害が発生した場合にも対処する Node Readiness Controller を設計してください。
これは、プラットフォームエンジニアリング、SRE、および Kubernetes コントローラーの面接に適したテーマです。Kubernetes の公開面接資料では、NotReady ノード、taints と tolerations、および Pending Pod のトラブルシューティングが扱われます。公式の Node Readiness Controller プロジェクトでは、NodeReadinessRule API、自動テイント管理、ブートストラップ専用モードおよび継続モード、ドライランが説明されています。以下の設計は論理的に構築された面接の回答例であり、特定の企業で実際に出題されたと主張する質問ではありません。
面接官が評価するポイント
面接官は、「ノードが Ready であること」と「ノードが特定のワークロードクラスに適していること」を明確に区別できているかを見ています。優れた回答では、コンディションのソース、ルールのスコープ、テイントの冪等性、再起動時の復旧、オブザーバブルな状態、そしてクラスタ全体が誤ってブロックされるのを防ぐ保護策を定義します。
不十分な回答では、すべてをチェックする DaemonSet を作成してしまいます。優れた回答では、コンディションのレポートがポリシーの適用からなぜ疎結合化されているのか、ブートストラップ専用と継続的適用の違い、ドライランによる影響範囲の推定方法、そして古いコンディション、コントローラーの分断、競合するルールの処理方法を説明します。
回答前の明確化のための質問
- コントローラーはノードのブートストラップのみを保護すべきですか、それともドライバが後から失敗した場合にも新しいスケジューリングを停止すべきですか? これにより、適用モードと復旧アクションが決まります。
- コンディションを生成するのは誰ですか:Node Problem Detector、デバイスプラグイン、CNI エージェント、それともカスタム DaemonSet ですか? 信頼できないソースには、ID と最新性のチェックが必要です。
- すべてのコンディションが True である必要がありますか、それともいずれか1つのコンディションがパスすればよいですか? GPU、ネットワーク、ストレージでは通常、異なるノードプールとルールが必要になります。
- コンディションが False の場合、新しい Pod をブロックしますか、それとも既存の Pod を退避(Evict)させますか? 前者はリバーシブルですが、後者はより強い根拠と個別の Eviction ポリシーが必要です。
30秒の回答フレームワーク
「コンディションのレポート、ルールの評価、テイントの適用を分離します。NodeReadinessRule でノードを選択し、True でなければならないコンディションをリスト化します。コントローラーは、ブートストラップ専用または継続的適用に応じて NoSchedule テイントを追加または削除します。書き込みは冪等で、オブザーバブルであり、ドライランに対応している必要があります。再起動後に状態を再構築できるよう、ルールとコンディションの状態は API サーバに保持します。まずは影響をログに記録し、1つのノードプールで有効化し、障害発生時はクラスタ全体をスケジューリング不能にするのではなく、書き込みを停止するかルールをロールバックします。」
ステップバイステップの回答
境界の定義から始めます。コントローラー自体は GPU やネットワークをプローブしません。Node Conditions を消費します。Node Problem Detector、デバイスプラグイン、またはカスタムエージェントが事実を報告します。これにより、既存のプローブエコシステムを再利用し、「チェックが失敗したこと」と「スケジューリングが許可されること」を分離します。コンディションには、type、status、更新時刻、ソース、および観測世代(observation generation)を含めるべきです。古いコンディションによってワークロードの承認が継続されてはなりません。
ルールモデルには、ノードセレクタ、コンディションのセット、対象のテイント、および適用モードが含まれます。公式プロジェクトでは、テイントを削除する前にリストされたすべてのコンディションが満たされている必要があり、ラベルによる異種ノードの選択をサポートしています。bootstrap-only は、初期化が成功した後にそのルールの評価を停止します。continuous は、重要なコンディションが後で False になったときにテイントを再付与します。前者のモードはイメージの事前プルやハードウェアのセットアップに適しており、後者は健全性を維持し続ける必要がある依存関係に適しています。
コントロールループは、ルール、ノード、コンディションを読み取り、期待されるテイントを計算し、リソースバージョンを使用して更新を適用します。書き込みは冪等であり、コンフリクト時には再試行される必要があります。このコントローラーが所有するテイントのみを管理し、管理者や他のコントローラーのテイントは絶対に削除しないでください。2つのルールが異なる意味で同じテイントキーを要求する場合、アドミッションバリデーションで競合を拒否する必要があります。そうしないと、あるルールが誤って別のルールの保護を解除してしまう可能性があります。
障害経路を明確にします。レポーターの更新が停止した場合、フェイルクローズにするかフェイルオープンにするかはプロダクトの判断です。重要なセキュリティ依存関係はフェイルクローズとし、低リスクのブートストラップは短い TTL でフェイルオープンにできます。切断されたコントローラーは既存のテイントをクリアしてはならず、復旧後に調整(reconcile)を行います。ノードが削除されたりラベルが変更されたりした場合は、古い状態が代替ノードをブロックしないように、ルールを適用不可としてマークします。
オブザーバビリティには、ルールごとの一致ノード、欠落または古いコンディション、期待されるテイントと実際のテイントの乖離(ドリフト)、コンディションが True になってからスケジューリング可能になるまでのレイテンシ、およびゲートされた Pod 数を含める必要があります。ステータスには、認証情報や機密性の高いデバイスデータをログ出力することなく、失敗したコンディションと最終評価時刻を公開する必要があります。コントロールプレーンを保護するために、ノードおよびルールごとにキューイングし、急激なコンディション変更を合体(coalesce)させ、毎回のハートビートごとに API サーバへの書き込みが発生しないようにします。
ドライランを使用してロールアウトします。公式プロジェクトのドライランは、テイントを適用することなく、意図されたアクションを記録し、ルールステータスを更新します。影響を受けるノードと Pending Pod を観察し、1つのノードプールで有効化します。ロールバック時は、新しいルールの評価を一時停止し、既存の保護テイントを維持したまま、コンディションソースを修正して再度調整します。1つのコマンドですべての NoSchedule テイントを削除するようなことはしません。
代替案としては、すべてのワークロードに nodeSelector を追加すること、Pod schedulingGates を使用すること、またはブートストラップスクリプトからテイントを追加することが挙げられます。ワークロードセレクタは既知の小規模なコンシューマセットには適していますが、将来の Pod をカバーできません。Pod gate は Pod を保護しますが、この課題はノードを保護するものです。スクリプトには共有状態と復旧メカニズムが不足しています。コントローラーは異種ノードや共有クラスタに適していますが、CRD、コントロールループ、および新たな障害ドメインの追加というコストが伴います。
高品質な回答例
私は「新しい Pod をここにスケジュールしてよいか?」のみを担当する宣言型コントローラーを構築します。Node Problem Detector、デバイスプラグイン、または CNI エージェントがコンディションを書き込み、コントローラーはそれらのプローブを重複して実行しません。ルールはノードを選択し、True であるべきコンディションをリストし、NoSchedule テイントを指定し、モードを選択します。ブートストラップ処理には bootstrap-only を使用し、GPU ドライバとネットワークエージェントの準備が整ったらテイントを削除します。継続的な依存関係には continuous を使用し、後で障害が発生した場合はテイントを再付与しますが、既存の Pod を自動的に退避させることはしません。
コントローラーは自身の所有者マーカーを持つテイントのみを所有および調整し、冪等性のためにリソースバージョンとコンフリクト再試行を使用します。同じテイントキーに対する競合する要求は拒否されます。ルールステータスは、一致したノード、欠落または古いコンディション、テイントの乖離、および評価時間をレポートします。まずドライランを展開し、予測される影響を Pending Pod と比較し、1つのノードプールでカナリアリリースを行います。コントローラーがダウンしている間は既存の保護を維持し、復旧後に調整します。ロールバック時は、クラスタ内のすべてのテイントをクリアするのではなく、評価を一時停止してコンディションソースを修復します。
よくある間違い
- 間違い → コントローラーが各ノードに SSH 接続する;失敗理由 → Kubernetes のコンディションソースをバイパスし、権限境界を拡大してしまう;修正方法 → 特化したエージェントにコンディションを報告させ、ポリシー評価はコントローラー内に保持する。
- 間違い → 1回限りのブートストラップに
continuousを使用する;失敗理由 → 完了したノードがフラッピングを続け、スケジューリング不能のままになる;修正方法 →bootstrap-onlyを使用し、完了を記録する。 - 間違い → 同じキーを持つ任意のテイントを削除する;失敗理由 → 管理者や他のコントローラーによる安全保護が失われる可能性がある;修正方法 → 所有権マーカー、コンフリクトバリデーション、およびフィールドレベルの更新を使用する。
- 間違い → コントローラーの再起動後にすべてのテイントをクリアする;失敗理由 → 復旧ウィンドウ中に準備ができていないノードにワークロードが配置されてしまう;修正方法 → 状態を保持し、リソースバージョンチェックを用いて調整する。
フォローアップの質問と回答
コンディションレポーターの更新が停止した場合、フェイルオープンとフェイルクローズのどちらにすべきですか?
依存関係を分類します。GPU ドライバ、暗号化モジュール、クロスゾーンネットワークなどは、有効期限付きのフェイルクローズを採用し、一時的なキャパシティ低下を受け入れることができます。リスクの低い1回限りのブートストラップは、成功の記録と短い TTL を持たせてフェイルオープンにできます。どちらの場合も、テレメトリの欠落を健全であると誤認しないよう、"unknown" を False とは別に測定します。
2つのルールが1つのノードに一致し、同じテイントキーを要求した場合はどうしますか?
アドミッション時またはルールコンパイル時にセマンティックな競合を拒否し、別個のキーまたは明示的な単一の集約所有者を要求します。コントローラーはルールごとに期待される状態を保持し、すべての所有者が解放条件を満たした場合にのみ集約テイントを削除します。最後に調整されたルールが単独でそれを削除してはなりません。
ドライランによってクラスタのキャパシティが枯渇しないことをどのように証明しますか?
一致したノード、利用可能なスケジューリング可能キャパシティ、Pod リクエスト、およびトポロジスプレッドを1つの影響レポートにまとめます。1つのノードプールをシミュレートし、ゲートされた Pod、オートスケーラーのキュー、スケジューリングレイテンシを観察してから拡張します。クリティカルなワークロードに余裕がない場合は、強制適用前にノードプールまたはルールを変更します。
ノードが継続的な準備状態に失敗したとき、なぜ既存の Pod を退避させないのですか?
「新しい Pod を受け入れないこと」と「既存の Pod が安全でないこと」は別の決定です。NoSchedule は新しい配置をブロックしながら継続性を維持します。退避には独自の PDB、グレースフルなシャットダウン、およびデータ安全性ポリシーが必要です。別個の Eviction コントローラーが、既存のワークロードが安全でないという証拠がある場合にのみ動作すべきです。