クラウドとインフラ

Kubernetes の有効なメカニズムがコントロールプレーンの障害を誘発する仕組み

Kubernetes コントロールプレーンには、インフォーマー、スキーマ集約、ライブネスプローブといった標準的なメカニズムを通じて局所的な欠陥がシステム全体の障害へと増幅される、隠れた障害経路が存在します。

Kubernetes コントロールプレーンの複雑さを表現する、光る回路層を持つガラス立方体
この記事用に生成されたイラスト

英語の原文から自動翻訳されました。

Kubernetes コントロールプレーンは個々のコンポーネントの障害を隔離するように設計されていますが、重要なパス上のコンポーネントが負荷下でどのように振る舞うかによって、全体の可用性は依然として脆弱です。最近の分析により、大規模なクラスタ環境では、コンポーネントがクラッシュしなくても障害が発生することが明らかになりました。代わりに、標準的なメカニズムが共有メタデータや同期された起動処理を通じて、局所的な欠陥を増幅させるのです。

何が起きたのか

3つの具体的な事例から、Kubernetes の標準機能が意図せずコントロールプレーンの不安定さを引き起こす様子が浮き彫りになります。1つ目の事例では、依存関係のアップグレード後に拡張 API サーバーが無効なスキーマを公開しました。このエラーにより、その拡張 API と一度もやり取りしたことがないクライアントまでブロックされました。障害は共有メタデータ、具体的にはクラスターに登録されたすべての API の定義を組み合わせたスキーマドキュメントを介して伝播しました。ユーザーが Helm を使用して標準的な Kubernetes リソースのみを含むチャートをデプロイしようとした際、故障した拡張機能とは何の関係もないにもかかわらず、リクエストが API サーバーに到達する前に検証エラーが発生しました。

2つ目の事例では、大規模なノードプール全体で DaemonSet エージェントが再起動されました。これは回復措置として意図されたものですが、ローリング再起動により、通常の起動処理が API サーバーが吸収できないほどのリクエストストームへと変わりました。新しいエージェントインスタンスごとに、すべての Pod をリスト表示することでクラスター状態のローカルビューを再構築していました。多くのエージェントが同時に起動したため、リスト操作の同期的な急増が発生しました。大規模なクラスタでは、これらのリクエストは数万件のオブジェクトを返し、数千の同時クライアントに対して巨大なレスポンスを構築する際に API サーバーのメモリと CPU を枯渇させました。

3番目のシナリオでは、API リクエストパス内のコンポーネントに対する詳細なライブネスチェックが、下流の依存関係の障害をプロセスの障害として扱いました。外部依存関係が利用できないまま、プローブは繰り返しコンポーネントを再起動しました。このサイクルは根本的な問題を解決することなく再起動を繰り返すことになり、API トラフィックを中断させ、サービス能力の一部を維持するために有用だったキャッシュ済み状態を破棄しました。

仕組みの詳細

これらの障害は、Kubernetes が API メタデータを公開し、状態同期を管理する方法に起因しています。Kubernetes はコアを変更せずに機能を拡張するためにアグリゲート API サーバーを使用します。クライアントにとって、これらの拡張機能はネイティブに見えます。システムは OpenAPI v2 と v3 の2つの形式でスキーマ契約を公開します。OpenAPI v2 は、コアおよびすべての拡張 API の定義を含む単一の結合ドキュメントを提供します。これにより、ある拡張機能からの無効な定義がクライアントによるドキュメント全体の解析を防ぎ、無関係なリソースへのアクセスをブロックする「共有障害ドメイン」が作成されます。OpenAPI v3 は API グループごとにスキーマを分離するため、影響範囲が狭まります。

インフォーマーは、API サーバーへの反復クエリを減らすために、コントローラーにクラスター状態のローカルビューを提供します。しかし、多くのコントローラーが同時に再起動する場合、これらのビューの再構築はコントロールプレーンに大きな負荷をかけます。インフォーマーはしばしば resourceVersion=0 を使用して、etcd ではなく API サーバーのウォッチキャッシュから読み取ります。歴史的に、このパスはページング制限を無視しており、完全なオブジェクトコレクションを返していました。回復イベント中に数千のエージェントがこれらの完全なリストを同時に要求すると、API サーバーはリクエストごとに数百メガバイトのデータをシリアライズして送信するのに苦戦します。

ライブネスプローブは、不健全なプロセスを再起動するために設計されています。しかし、プローブがローカル機能ではなく外部依存関係を確認する場合、キャッシュ済み状態を使用してまだ機能しているプロセスを再起動してしまう可能性があります。例えば、KMS プロバイダーは外部キー管理サービスとの接続を失ったとしても、キャッシュされたキーを使用して暗号化リクエストを提供し続けることができます。外部到達可能性をテストするプローブは、不必要にプロバイダーを再起動し、再接続を強制して有用なキャッシュ済み状態を破棄します。

主要なポイント

  • OpenAPI v2 はすべての API スキーマを1つのドキュメントに結合するため、1つの無効な拡張スキーマがすべてのクライアントをブロックできる共有障害ドメインを作成します。
  • OpenAPI v3 は API グループごとにスキーマを分離するため、無効な定義の影響範囲(ブラスト半径)を特定の API ユーザーに限定します。
  • resourceVersion=0 を使用するインフォーマーはウォッチキャッシュから読み取りますが、歴史的にページング制限を無視していたため、巨大なレスポンスサイズにつながりました。
  • DaemonSet エージェントの同期再起動はリクエストストームを発生させ、同時リスト操作によって API サーバーを圧倒する可能性があります。
  • 外部依存関係を確認するライブネスプローブは、キャッシュ済み状態を使用してリクエストを提供できるプロセスを再起動してしまう可能性があります。
  • コンポーネントレベルの正確性は必要ですが、システムの可用性を保証するには不十分です。前提条件が破られた場合の挙動についてもメカニズムを評価する必要があります。

なぜ重要なのか

Kubernetes プラットフォームを構築・運用するチームにとって、これらの知見は、標準的な運用パターンが大規模環境では障害増幅器になり得ることを強調しています。エンジニアはしばしば、ヘルスチェックやローリングアップデートが安全なデフォルト動作であると想定します。しかし、コンポーネントがメタデータを共有したり、起動タスクを同期したりする場合、これらのメカニズムは予期しない方法で相互作用します。依存関係のテストと機能性のテストの違いを理解することは、レジリエントなシステムを設計する上で不可欠です。下流の障害を忠実に報告するプローブであっても、不必要な再起動を引き起こすことで、システムの可用性契約に違反する可能性があります。

これは特に、拡張 API を実行しているチーム、大規模なオブジェクトコレクションを監視しているコントローラー、または API リクエストパスにあるコンポーネントにとって重要です。ピーク負荷時や回復イベント時に、ローカル状態の再構築やスキーマ検証のコストがコントロールプレーンの容量を超える可能性があります。プラットフォームリーダーは、アップグレード手順、リカバリー演習、ヘルスチェック設計を見直し、キャッシュ済み状態を誤って破棄したり、損傷した依存関係に対して同期需要を生み出したりしないようにする必要があります。

対応策

  • アップグレード後に OpenAPI スキーマを検証するため、v2 および v3 ドキュメントを取得し、クライアントライブラリで解析して無効な定義を早期に検出してください。
  • インフォーマースコープを、特定の名前空間やメタデータのみビューなど、必要最小限のリソースコレクションに制限し、起動時の負荷を軽減してください。
  • Kubernetes 1.31+ で WatchList または更新された API Priority and Fairness 機能を使用し、初期リスト操作のコストを緩和してください。
  • ライブネスプローブは、外部依存関係の到達可能性ではなく、ローカル機能性とキャッシュ済み状態の有効性をテストするように設計してください。
  • 依存関係チェックをライブネスプローブから分離し、一時的な上流障害時に自動再起動を引き起こさず、オペレーターにアラートを送るようにしてください。
  • DaemonSet の更新戦略と maxUnavailable 設定を見直し、回復時に同期再起動が API サーバーを圧倒しないようにしてください。
  • #kubernetes
  • #control plane
  • #reliability
  • #openapi
  • #infrastructure

Bytechapストアのツール

続きを読む

すべての記事