インフラ・DevOps
KubernetesでCPU制限の使用を今すぐやめてください
For the love of god stop using CPU limits in Kubernetes (github.com)
要約
この記事は、KubernetesにおけるCPU制限の使用がアプリケーションのパフォーマンス低下とコスト増加を招くという主張を展開しています。CPU制限は、ノードにCPUリソースの空きがある場合でも、アプリケーションを頻繁に一時停止(スロットリング)させ、レイテンシの増加や起動時間の遅延を引き起こします。著者は、CPU制限を削除し、CPUリクエストとメモリ制限のみを維持することを推奨しており、これによりパフォーマンスが向上し、ハードウェアコストが削減されると述べています。
全文翻訳
KubernetesのCPU制限は、あなたのアプリを遅く、そして高価にします。
プラットフォームエンジニアリング・分析
私たちは、CPU制限ありとなしの同じアプリをテストしました。同じコード、同じCPUリクエスト、同じ負荷です。唯一の違いは制限でした。測定した結果と、ご自身のクラスターでそれを確認する方法を以下に示します。
TL;DR CPU制限を削除してください。ノードにCPUの空きがある場合でも、アプリを毎秒何度もフリーズさせます。CPUリクエストは維持してください。リクエストこそが真の保護です。それらは各アプリにそのシェアを保証します。メモリ制限は維持してください。メモリは異なります。メモリ制限は依然としてノードを保護します。
高速化: トラフィックピーク時にテールレイテンシが崩壊する代わりに持ちこたえ、CPUバウンドな起動作業が約2倍早く完了します(セクション5、10)。
ハードウェア削減: ほとんどのクラスターは、ピーク時に使用するよりもはるかに多くのCPUを予約しています。制限をドロップした後のリクエストの適切なサイジングにより、ノードのかなりのシェアを解放できます(セクション10)。
低コスト化: 動作する、説明的なコストモデルは、クラスターあたり年間数万ドルのコスト削減を示しています(セクション10)—ご自身の価格を代入して実際の数値を把握してください。
この分析では:
1. リクエスト対制限
2. スロットリングの仕組み
3. CFSの公平な共有
4. 最悪のケース
5. 測定結果
6. ノイジーネイバー
7. 問題が発生する場合
8. .NETのための1ステップ
9. プラン
10. 結果
11. Q&A
12. 用語集
さらに深く。
このページは議論の場です。そこでの各主張は、読んでいる間にインラインでリンクされ、ここに集められた長いドキュメントによって裏付けられています:
docs/01-theory.md
cgroupの仕組み:cpu.max対cpu.weight、マルチスレッドクォータ消費、そしてCPU制限が実際に役立つ唯一の設定。
docs/02-dotnet.md
CPU制限が.NETランタイムに与える影響:ProcessorCount、ThreadPoolの枯渇、GCヒープ数、そしてCPU制限がメモリキルを引き起こす方法。
docs/03-postgres.md
Postgresの具体例:なぜそれがサイズ設定時にクォータを無視するのか、そしてこのPoCが測定した効果と単に推測された効果の比較。
docs/04-objections.md
私たちが遭遇したすべての反論に回答:ノイジーネイバー、HPA/KEDA、QoSクラス、マルチテナンシー、そして制限が適切な場合。
docs/05-cost.md
完全なコストモデル、その仮定、そして解放されたコアが解放されたお金にならないようにするメモリフロア。
docs/06-rollout.md
段階的なロールアウト:6つのステップ、何が何をゲートするか、そして1行のロールバック。
results/
ベンチマーク実行からの生の出力、および以下の数値の元となるシナリオごとのレポート。
1. 2つの設定、2つの非常に異なるジョブ
CPUリクエスト
CPU制限
それは何ですか?
CPUの保証されたスライス
ハードシーリング、壁
他のアプリを保護しますか?
はい。CPUはリクエストサイズで共有されます。
いいえ。それは自身のアプリをブロックするだけです。
ノードのアイドルCPU
アプリはそれを無料で借りることができます
無駄。制限がそれをブロックします。
同じアプリを2回実行。唯一の違いはノードのアイドルCPUへのアクセスです。リクエスト(実線)は両方の行で同一です—アイドルCPUの借用は誰からも何も奪いません。
さらに深く:これらは1つのノブの2つの設定ではなく、2つの異なるcgroupファイルです。docs/01-theory.mdはcpu.max(制限)とcpu.weight(リクエスト)を、それぞれが実際に何を制御しているかを説明しています。
2. 制限が実際にアプリを停止させる仕組み
カーネル(オペレーティングシステムのコア部分)は、100ミリ秒のウィンドウで制限を強制します。500m(500ミリコア、半コア)の制限は、ウィンドウあたり50ミリ秒のCPU時間を意味します。予算が尽きると、カーネルは次のウィンドウまでアプリ全体をフリーズさせます。これがスロットリングです。
典型的な.NETサービスは多くのスレッドを実行します:HTTPハンドラ、バックグラウンドコンシューマー、そしてGC(ガベージコレクタ)です。すべてスレッドが1つの予算を共有します。私たちのテストノードは4コアなので、同時に最大4つのスレッドが実行できます。8つのビジーなスレッドでも、約12.5ミリ秒の実時間で50ミリ秒の予算を使い果たします。
フリーズは毎秒最大10回繰り返されます。次のチャートは、あなたのダッシュボードがそれらを決して見ない理由を示しています。オレンジ色の各秒は、アプリが制限に達し、カーネルがそれをフリーズさせたことを意味します。1分間の平均は制限に決して近づかないため、すべてのグラフは健全に見えます。これが、コンテナが一日中スロットリングされていても、そのダッシュボードが緑色のままでいられる理由です。
平均CPUしか見ない場合、これは見ることができません—代わりにcontainer_cpu_cfs_throttled_periods_totalを確認してください。
私たちはこれを証明しました。私たちは、平均で320mしか必要としない負荷を、500mの制限を持つアプリに送信しました。平均は制限に触れることさえありませんでした。アプリはまだ停止しました:
同じアプリ、同じ負荷(毎秒8リクエスト、8つの並列タスク、各5ミリ秒)。制限により、遅いリクエストが2.4倍遅くなり、平均CPUは制限をはるかに下回ったままです。
さらに深く:並列処理がなぜこれほど見かけよりも悪いのか—16スレッドが壁時間2ミリ秒未満で300mのクォータを消費する—そして結果が減速ではなく停止である理由。これは、平均が良好に見える間に、テールレイテンシを破壊します。
3. 制限なしで、誰がCPUを共有するのか?
CFSに出会う
Linuxには組み込みのレフェリーがいます:CFS、Completely Fair Schedulerです。各ポッドには重みがあります。KubernetesはポッドのCPUリクエストから重みを設定します。ルールはシンプルです:ノードが完全にビジーな場合、ポッドはそれらの重みに基づいてCPUを共有します。ポッドがアイドル状態の場合、そのシェアの使用を停止します。アイドル状態のポッドは、作業が再び発生するとすぐにそのシェアを取り戻します。
1つのノード上の3つのアプリ、リクエストからの重み:A = 100m、B = 200m、C = 700m。
このレフェリーは常に、すべてのLinuxサーバーで実行されています。機能するにはCPU制限を必要としません。制限は追加で1つのことしか行いません:セクション2のフリーズです。
さらに深く:制限がリクエストがすでに提供していない保護を何も提供しないという完全な議論、そして唯一の実際の例外:静的CPUマネージャーによる保証されたQoS、ここで「制限」はクォータをスロットリングするのではなくコアを固定することです。
4. 「しかし、すべてのポッドが同時に100%のCPUを使用したら、ノードは死んでしまいますか?」
いいえ。この恐れはCPUとメモリを同じ問題として扱います。ノード上のCPUの最悪の瞬間は次のとおりです:4コアノード、完全にビジー。CFSはリクエストの重みによってCPUを分割します。各ポッドは保証されたリクエストシェアにドロップバックします—アプリは遅くなりますが、何も壊れません。
スケジューラはすでに、すべてのポッドのリクエストがノード容量内に収まるようにしています。3つの保護機能により、ノード障害は問題なく、すべてCPU制限とは無関係です:
1. 予約されたシステムCPU
OSとkubelet(ノード上で実行され、ポッドを監視するエージェント)は、ポッドプール外に独自の予約済みCPUスライスを持っています。ビジーなポッドはそれに触れることができません。ノードは常に応答性を保ちます。
2. スケジューラ計算
ポッドはリクエストによって配置され、合計リクエストはノード容量を超えることはありません。完全な競合(すべてのポッドが同時にCPUを要求する)でも、誰もがリクエストを受け取ります。これは制限が与えるのと同じ保証であり、アイドルCPUを無駄にしません。
3. CPUは圧縮可能
CPUは圧縮可能です:少なすぎるとアプリが遅くなるだけです。遅延した作業を後で完了します。クラッシュしません。
メモリは異なります:メモリが少なすぎるとアプリがクラッシュします。まさにメモリ制限を維持する理由です。
リソース
不足している場合
結果
CPU
アプリは一時的に遅くなる
制限を削除し、リクエストを維持する
メモリ
アプリがクラッシュする(OOMキル、メモリ不足キル)
制限を維持する
そしてほとんどのクラスターでは、ノードのCPU使用率は容量をはるかに下回っています—しばしば一桁または二桁の低い数字です。「全員が100%」の瞬間は、ほとんど理論的です。一方、実際の日常的な問題は反対のものです:スロットリングされたアプリが触れることを許可されていないアイドルCPUです。
ノードが完全にビジーになった場合でも、上記の2つの保護機能は依然として有効です:予約されたシステムCPUはノードとkubeletの応答性を維持し、CFSの重みベースの共有は引き続き各ポッドにリクエストされたシェアを保証します。ノードレベルのCPUダッシュボードは、これが懸念事項になる前に、持続的な高使用率を示すでしょう。
「しかし、まだResourceQuotaやLimitRangeがありません」
これはもっともな反論であり、その背後にある懸念は現実です:すべてのアプリケーションがResourceQuotaやLimitRangeのない単一の名前空間を共有している場合、チームに正確なリクエストを設定させるものは何もありません。一般的な中間ステップは、制限として何かを設定することです。cpu: 1 を「天井ではなくサーキットブレーカー」として使用します—実際のスロットリングを停止するのに十分な高さです。高い制限を設定することは、そのサービスに適用されるポッドのスロットリングを終了させる、良い緊急修正です。しかし、そのサービス周辺のポッドの安全メカニズムとしては、CPUの制限は...