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

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

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

推薦する

ウェブサイトのための SEO 戦略トップ 10

検索エンジン最適化の概念は少し複雑ですが、ウェブサイトのオーガニック検索ランキングを向上させる基本的...

デッドリンクはウェブサイトの進行に影響します。ウェブマスターはデッドリンクをどのように対処すればよいでしょうか?

インターネット業界に参入する新しいウェブマスターが増えているのを見て、私は彼らを喜ばしく思いますが、...

Baidu スパイダーをあなたのウェブサイトのようにする方法

最適化を行っている多くの友人が、Baidu のインデックス作成が遅くなった、または Baidu の評...

スマートマーケティングのウルトラブック詐欺!

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています最近、多く...

クラウドコンピューティングの導入は単なる紙上の作業ではない

クラウド コンピューティングが初めて人々の目に留まったとき、多くの人は、それは単なる話で実際的な意味...

ライブストリーミングeコマースは「1ムーあたり1万キロ」の時代に突入したのか?

「国民が大胆になればなるほど、土地の生産性は高まる」。この言葉は、インターネット時代の電子商取引ライ...

コンテンツ更新に関する誤解の例

Baidu がウェブサイトのコンテンツの取り締まりを開始して以来、コンテンツの更新は多くのウェブマス...

クラウドのダウンタイムイベントの影響を軽減するにはどうすればよいでしょうか?

クラウド コンピューティングは本質的に信頼性が低いわけではありませんが、あらゆる形態の IT と同様...

ユーザーエクスペリエンス分析: インターフェースデザインにおける構造設計

インターフェースの視覚的な階層を構築する要素には、色の目立ち具合、画像とテキストのサイズ、そして最も...

Discuz! App Center が新しい「需要請求」モデルを開始

最近、Discuz! 公式サイトの「アプリケーション センター」セクションに、ウェブマスターがニーズ...

Kaola.com の完全なクラウドネイティブ移行への道

今年8月末、1年間「アリ動物園」にいたKoala.comが初めて戦略的なアップグレードを発表した。同...

Sina WeiboはIPOに備えて「Sina」ブランドを希薄化するため、社名をWeiboに変更した。

【A5ウェブマスターネットワークニュース】3月27日夜、新浪微博のウェブページのロゴがひっそりと変更...

cambohost: カンボジア専用サーバー、100M 帯域幅、CN2 ネットワーク、東南アジアに最適

カンボジアの現在のネットワーク規制は比較的緩いので、カンボジアのサーバーを好む人が多くいます。ここで...

サーバーホストはどうですか? 「マイアミ」データセンターのVPSの簡単なレビュー

サーバーホストはどうですか? Serverhost Miami の VPS はいかがでしょうか? S...