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とは何だと思いますか?

SEO はコンバージョン率の向上、ランキングの向上、ウェブサイトの重量増加を目的としていると言う人も...

VAICDN: 超高防御\超高帯域幅、統合CDN、香港CN2などの複数のハイエンドノード、グローバル高速アクセス

vaicdn はコア CDN サービスに重点を置いており、ドメイン名への直接アクセスを提供し、「ブロ...

macloud: ロシアのクラウドサーバー、日払い、最低1元/日、AMD EPYC/Intel Gold +NVMe+64Tトラフィック/月、カスタムアップロードISO

ロシアのサーバー業者であるMacloudは、ロシアのモスクワにあるDataproデータセンターで主に...

Baidu Bear アカウントがオリジナルではなく、信頼性が低いというのはどういう意味ですか?

小教室:百度熊張浩の背景に原文リンクを提出した後、原文リソースデータ分析で非原文の理由が「信頼度が低...

Virmach Japanの東京データセンターVPSの簡単なレビューで、Virmach Japanがいかに優れているかがわかります

virmach が日本の VPS を開始しました。サーバーは日本の東京にあり、デフォルトの帯域幅は ...

サイバーセキュリティの知識: クラウドセキュリティについてお話しましょう

概要英国NCSCのクラウドセキュリティへのアプローチクラウド テクノロジーには曖昧な用語がいくつかあ...

タイトルを変更してウェブサイトのランキングを下げるのではなく上げる方法

ウェブサイトのタイトルを変更することは、多くのウェブマスターが行うアクションです。ウェブサイトのタイ...

4年間懸命に努力したにもかかわらず、まだ何も達成されていないウェブサイト構築の私の経験の要約

私はウェブマスターになって4年になりますが、まだ何も達成できていません。これにはさまざまな理由があり...

ferngullygraphics - $15/年/256M メモリ/15g ハードドライブ/500g トラフィック/ダラス

ferngullygraphics.com、これは本当に奇妙なもので、業界にとって恥ずかしいものです...

SEO初心者にとって外部リンクとコンテンツは不可欠

ウェブサイトが百度や Google などの検索エンジンで上位にランクされるために最も重要な要素は、ウ...

velocihost-$5.5/KVM/2コア/512mメモリ/10g SSD/1Tトラフィック/

私の意見では、velocihost は優れたパフォーマンスを備えた VPS プロバイダーです。この ...

ウェブサイト構築においてコンテンツマーケティングを実施し、ウェブサイトのコンテンツを価値あるものにする方法

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

インターネット企業はクラウドコンピューティングに参入し、3カ年計画を開始している

最近、インターネット企業はクラウドプランを頻繁に立ち上げています。 5月には、JD.com、Tenc...

クラウドネットワーク統合市場には幅広い展望があります。事業者と企業は協力して前進すべきである

[[395514]]クラウド開発が成熟するにつれて、クラウドネットワーク統合 (CNI) が業界でホ...

LBXU: 建国記念日をクラウドでお祝いしましょう。ロサンゼルスの CN2/AS9929 回線の VPS は月額 4.4 ドルから、最大 300G の防御力を備えています。

LBXU は、建国記念日を記念して「クラウドで祝う、建国記念日を迎える」というテーマのイベントを開催...