Kube-apiserver がまた OOM ですか?

Kube-apiserver がまた OOM ですか?

起源

前回の記事では、kube-apiserver へのリストおよび監視リクエストを開始する Informer の実装を紹介しました。大規模なクラスターでは、特にメモリの面で kube-apiserver がボトルネックになることがわかっています。 kube-apiserver OOM などの問題に遭遇した人も多いと思います (偶然にも、kube-apiserver OOM の問題は最近オンラインで 2 回発生しました)。この記事では主に、kube-apiserver の Informer に必要な 2 つのインターフェース list と watch の実装について説明します。

インターネットで検索すると、ソースコード分析に関する記事がたくさん見つかります。ここではコードについてあまり詳しく説明しません。主に原則とプロセスについてお話しし、最後に理論と実践を組み合わせて現在の問題を簡単に紹介します。この記事では主に現在の実装について説明します。現在の実装と問題が発生する理由を理解することによってのみ、問題を解決する方法がわかります。次の記事では、これらの問題をどのように解決するかを詳しく分析します。

原理

キャッシュ読み込み

写真

コアコンポーネント: Cacher、watchCache、cacheWatcher、reflector。このうち、watchCache はリフレクタのストアとして使用され、Etcd は listerWatcher のストレージとして使用され、store と listerWatcher はリフレクタを構築するためのパラメータとして使用されます。データフローはおおよそ次のようになります。

  1. kube-apiserver が起動し、各リソース タイプに対して、対応する cacher の startCaching を呼び出し、次に reflector.ListAndWatch を呼び出して、Etcd リストに対応する listerWatcher のリストと監視をトリガーし、次に監視します。監視時にwatchChanが作成され、Etcdから読み取られた結果はまずwatchChanのincomingEventChanに入り、その後リフレクタ消費のための変換処理を経てwatchChanのresultChanに送信されます。
  2. Reflector は、上記の resultChan、つまり watch.Event オブジェクトのデータを使用し、イベント タイプに応じてストアの add、delete、および modify メソッドを呼び出します。ここでのストアはwatchCacheです。 watchCache.processEvent によって処理された後、watchCacheEvent オブジェクトが組み立てられ、watchCache のキャッシュ (適応サイズで履歴イベントを保持するウェイクアップ バッファー) とストア (完全なデータ) が更新され、最後に eventHandler を通じてキャッシャーの受信チャネルに送信されます。
  3. cacher.dispatchEvents は、受信した chan のデータを消費し、処理後に各 cacheWatcher の入力 chan に送信します。
  4. kube-apiserver 監視リクエストが外部から呼び出されると、対応する cacheWachter オブジェクトが作成され、最終的に cacheWatcher の監視プロセッサに渡されます。監視プロセッサは入力チャネルを消費し、イベント配信のために watchCacheEvent を呼び出します。

写真

キャッシュデータフロー

データをキャッシュするために使用されるコア構造は watchCache です。この中には、cache (循環バッファ) と store (スレッドセーフ ストア) という 2 つの主要な構造があり、それぞれ履歴 watchCacheEvent と実際のリソース オブジェクトを格納するために使用されます。ストアにはすべてのオブジェクトが格納されます。キャッシュのサイズは適応的ですが、最大容量の制限があるため、格納される watchCacheEvent によって表されるオブジェクト セットは、必ずしもストア内のすべてのデータをカバーするわけではありません。

歴史的問題

kube-apiserver は、独自のメモリ使用量の最適化において多くの改善を行いましたが、まだ完全に解決されていない問題がいくつかあります。

kube-apiserver OOM

メモリ消費源

kube-apiserver のメモリ消費は、主に次の 2 つの原因で発生します。

  1. その理由の一部は、すべてのクラスター データ (k8s リソース タイプである Event を除く) をキャッシュし、各リソースの履歴 watchCacheEvent や、一部の内部データ構造や chan などをキャッシュするからです。この部分は避けられず、適切に最適化することはできますが、あまり効果はありません。
  2. 残りの部分はクライアントからのリクエスト、特にリストリクエストから発生します。 kube-apiserver は、メモリ内のデータのディープコピーとシリアル化を実行する必要があります。必要なメモリの量は、データおよびリクエストの量と正の相関関係にあります。データとリクエストの量が増加すると、より多くのメモリが必要になります。さらに、このメモリ部分は Golang GC では完全に回復できません。リスト要求の主なソースは Informer です。

リスト要求がより多くのメモリを消費する理由は次のとおりです。

  1. デフォルトでは (resourceversion を指定しない場合)、etcd から直接データを取得すると、データ ストアの完全な応答サイズよりも数倍多い大量のメモリが必要になる場合があります。
  2. キャッシュからデータを取得するために ResourceVersion パラメータを明示的に指定するリクエスト (たとえば、ResourceVersinotallow="0") は、実際にはパフォーマンス上の理由から、ほとんどのクライアント Go ベースのコントローラで使用されるアプローチです。メモリ使用量は最初のケースよりもはるかに少なくなります。しかし、これは完璧ではありません。シリアル化されたオブジェクトを保存し、送信されるまで完全な応答を保存するためのスペースがまだ必要だからです。

一般的なシナリオ

kube-apiserver OOM を簡単に引き起こす可能性のある一般的なシナリオが 2 つあります。

  1. Informer は DaemonSet などの一部のプログラムで使用されます。変更を加えたり、障害により再起動したりすると、クラスターのサイズが大きくなるため、リクエストの数も増え、kube-apiserver のメモリへの負荷も増加します。保護対策(電流制限)がない場合、kube-apiserver の OOM が発生しやすくなります。 OOM 後、異常な接続が他のマスター ノードに転送され、雪崩が発生します。理論的にはこれも容量の問題であり、対策としては容量拡張(マスターノードの追加)と電流制限(サーバー側電流制限:APF、MaxInflightRequestなど、クライアント側電流制限)が挙げられます。
  2. 特定の種類のリソースのデータ量が大きく、kube-apiserver で設定されたタイムアウト パラメータがリスト要求をサポートするには小さすぎる場合、Informer はリスト操作の実行を試行し続けます。この状況は、コントロール プレーン コンポーネントで頻繁に発生します。これは、コントロール プレーン コンポーネントが全量のデータを取得する必要があることが多いためです。

リソースバージョンが古すぎる

原理

厳密に言えば、これは問題ではありません。仕組みはこのようになっており、理論的には1台のマシンのリソースが無制限であればこの現象は回避できる。説明の便宜上、リソースバージョンを表すために RV が使用されます。

その本質は、クライアントがウォッチ API を呼び出すときにゼロ以外の RV を運ぶことです。サーバーがキャッシャーのウォッチ実装ロジックに到達すると、渡された RV に基づいて、RV より大きいすべての watchCacheEvents を循環バッファーでバイナリ検索し、それらを初期イベントとしてクライアントに返す必要があります。循環バッファの最小 RV が受信 RV より大きい場合、つまり、サーバーによってキャッシュされたイベントの最小 RV がクライアントによって送信されたイベントの最小 RV より大きい場合、キャッシュされた履歴イベントが不完全であることを意味します。これは、イベントが多すぎてキャッシュ サイズが制限されているため、古い watchCacheEvent が上書きされたことが原因である可能性があります。

一般的なシナリオ

  1. この状況は、kubelet が apiserver に接続されている場合、またはウォッチに labelselector または fieldselector がある場合によく発生します。各 kubelet は自身のノードの Pod のみを考慮するため、自身のノードの Pod が変更されておらず、他のノードの Pod が頻繁に変更されている場合、kubelet のローカル Informer によって記録された最後の RV は、循環バッファー内の最小の RV よりも小さくなる可能性があります。このとき、再接続が発生すると (ネットワークが切断されるか、Informer 自体がタイムアウトして再接続する)、kube-apiserver ログに「リソース バージョンが古すぎます」という文字が表示されます。
  2. kube-apiserver が再起動されるシナリオでは、クラスター内の一部のリソース タイプが頻繁に変更され、一部のリソース タイプが頻繁に変更されない場合、頻繁に変更されないリソース タイプを監視するインフォーマーのローカルの最後の RV は、最新の RV よりも小さくなるか、大幅に小さくなります。 kube-apiserver が再起動されると、小さなローカル RV で監視されますが、それでもこの問題が発生する可能性があります。

クライアント Informer がこのエラーに遭遇すると、ListAndWatch を終了して LIstAndWatch を再起動します。これにより、kube-apiserver のメモリが増加したり、OOM になったりする可能性があります。問題の根本的な原因: RV はグローバルです。シナリオ間の本質的な違いは、シナリオ 1 は 1 つのリソースでのスクリーニングによって発生するのに対し、シナリオ 2 は複数のリソース タイプ間の RV の大きな違いによって発生することです。

最適化

上記の分析の結果、この問題には 2 つの原因が考えられます。

  1. 循環バッファの長さには制限があります。
  2. クライアント Informer が保持している最後の RV が古すぎます。

コミュニティでは、この問題が発生する可能性を減らすために、数バージョン前に最適化も行いました。

問題 1 については、適応型ウィンドウ サイズを採用しました。問題は依然として発生しますが、以前に値をハードコーディングした場合よりも問題が発生する可能性は低くなります。同時に、メモリリソースの無駄を避けるために、必要のない場合には長さを短縮します。

2 番目の問題には、2 つの最適化があります。 BOOKMARK メカニズムは、同じリソースに対する異なるフィルタリング条件によって発生する問題を最適化するために導入されました。 BOOKMARK は、最新の RV を定期的にクライアントに返すイベント タイプです。 ProgressNotify は、さまざまなリソース タイプの RV がまったく異なるという問題を解決するために導入されました。 kube-apiserver を再起動した後、Informer resume によって発生する問題は、基本的に Etcd の clientv3 ProgressNotify メカニズムの使用に起因します。 Etcd を監視する場合、kube-apiserver はこの機能を有効にするための特定のオプションを持ちます。 ProgressNotifyはEtcdの公式ドキュメント[1]を参照しています。

WithProgressNotify を使用すると、受信イベントがない場合でも、監視サーバーは 10 分ごとに定期的な進行状況の更新を送信します。進行状況の更新には、WatchResponse にイベントが 0 個あります。

詳細については、次のKEPを参照してください:956-watch-bookmark[2]および1904-efficient-watch-resumption[3]

古くなった読み物

これは長年の課題です。これは、watchCache の登場以来存在しています。本質的には、以前は Etcd に直接アクセスするときに使用されていた線形一貫性読み取り (Etcd によって提供される機能) は、kube-apiserver キャッシュを読み取るシーケンシャル一貫性にダウングレードされました。

シナリオ

  1. T1: StatefulSet コントローラーは、ノード 1 にスケジュールされた pod-0 (uid 1) を作成します。
  2. T2: ローリングアップグレードの一環として pod-0 が削除されました
  3. node-1はpod-0が削除されたことを確認してクリーンアップし、APIでpodを削除します。
  4. StatefulSetコントローラは、node-2に割り当てられる2番目のポッドpod-0(uid 2)を作成します。
  5. node-2はpod-0がスケジュールされていることを確認し、pod-0を起動します。
  6. ノード 1 上の kubelet がクラッシュして再起動し、マスターから分割された HA セットアップ (複数の API サーバー) 内の API サーバーに対して、スケジュールされたポッドの初期リストを実行します (ウォッチ キャッシュは任意に遅延されます)。ウォッチキャッシュはT2より前のポッドのリストを返します。
  7. ノード1はT2より前のポッドのリストをローカルキャッシュに格納します。
  8. node-1 は pod-0 (uid 1) を起動し、node-2 はすでに pod-0 (uid 2) を実行しています。

詳細については、第59848号[4]を参照してください。

考える

私たちは、さまざまなソースコード解析や原理解析の記事をよく目にしますが、その内容を安易に信じてしまいがちです。ただし、バージョンの反復と一部の詳細の処理により、それらを完全に理解できなかったり、完全に習得できなかったりする場合があります。たとえば、リスト要求で RV=0 が渡された場合、kube-apiserver キャッシュが使用されますか?オンラインで検索すると動作すると表示されますが、コードを見るとそうではないことがわかります。たとえば、kube-apiserver の再起動後にデータが完全にロードされていない場合、RV=0 のリスト要求が発生すると、Etcd に直接アクセスしてデータを取得します。一見重要でない詳細が、問題に対処する際の私たちの考え方に影響を与える可能性があります。たとえば、Etcd の負荷が高い理由を知りたい場合、この詳細がわかっていれば、RV != 0 のリクエストだけでなく、すべてのリスト リクエストを意識的に確認することになります。

最後に、1 つ考えておきたいことがあります。kube-apiserver のメモリ負荷は、主にリスト リクエストによって発生します。では、リスト機能を実装するために、リスト要求の代わりにストリーミング処理を使用できるでしょうか?この方法では、メモリ消費を一定の空間複雑度に制限できますか?次の記事では、ストリーミングAPIを使用してリストによるメモリ爆発の問題を解決する方法を具体的に分析しますので、お楽しみに〜

参考文献

[1]etcd: https://pkg.go.dev/github.com/coreos/etcd/clientv3#WithProgressNotify

[2]ブックマーク: https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/956-watch-bookmark/README.md

[3]ウォッチ再開: https://github.com/kubernetes/enhancements/blob/c63ac8e05de716370d1e03e298d226dd12ffc3c2/keps/sig-api-machinery/1904-efficient-watch-resumption/README.md

[4]古い記事: https://github.com/kubernetes/kubernetes/issues/59848

<<:  データセンターの進化: ローカルコンピューティングからクラウド、エッジコンピューティングへ

>>:  実証済みの分析戦略でデータの潜在能力を最大限に引き出します

推薦する

Burst-29.95ドルのサーバー/4gのメモリ/500gのハードディスク/5Tのトラフィック/無料のDAパネル/4つのデータセンター

Burst は Green Monday に非常にコスト効率の高い独立サーバーを導入しました。これま...

Xiaomiのデジタルマーケティングの秘密について簡単に解説

Xiaomi が今日のような業績を達成したのは偶然ではありません。Xiaomi は、短期間で会社をこ...

社内SEOトレーニングプラン

5月19日、筆者は「社内SEO研修についてお話しましょう」というタイトルの記事を書きました。この記事...

itnuthosting: バングラデシュ VPS、月額 10 ドルから、2G メモリ/1 コア/40g SSD/1.5T トラフィック

itnuthosting は 2009 年に設立されたバングラデシュのホスティング会社で、主にドメイ...

tragicservers: ハロウィーン VPS 60% オフ、ロサンゼルスとシカゴのデータセンター

ハロウィーンが近づいており、tragicservers はハロウィーン VPS プロモーションを事前...

中小規模のウェブマスターにインターネットページの価値を啓蒙することについて語る

以前、インターネットページの価値に関する記事を読みました。Baidu のエンジニアは、インターネット...

クラウドが AI へと移行する中、業界の主流メーカーはどのようにしてエコシステムをさらに進化させることができるでしょうか?

ラボガイド人工知能とクラウドコンピューティングの統合がますます近づくにつれて、クラウドとAIエコシス...

水曜日は穏やかではない:今週のKステーションの嵐がやってくる

朝早くにウェブサイトのインデックスを見て、おお、金塊だ!と思った。私が持っていた企業ウェブサイトのホ...

アメリカのソーシャルエンターテイメントウェブサイトGetGlueが1200万ドルを調達

新浪科技報、北京時間1月12日朝のニュースによると、米国のソーシャルエンターテイメントウェブサイトG...

予算vm-E3 1230V2/16g メモリ/1T ハードディスク/30T トラフィック/5IP/月額 45 USD

budgetvm は、わずか 45 ドルで非常に強力な構成の特別なサーバーを立ち上げました。もちろん...

アリババクラウドとオリンピック放送サービス会社OBSが共同で「オリンピック放送クラウド」を開始

オリンピック放送サービス(OBS)とアリババクラウドは9月19日、本日開幕した杭州雲奇カンファレンス...

IT サービス管理を DevOps の世界に組み込む方法

IT サービス管理 (ITSM) の元のコード ベースはメインフレーム用に開発され、そのデータ構造は...

Weiboマーケティングは時代遅れですか?

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス業界ではよく知られている...