Kubernetesリソースリクエストと制限を極めて最高のパフォーマンスを実現する方法

KubernetesにおけるCPUとメモリのリソースリクエストと制限の重要な違いを学びます。このガイドでは、これらの設定がQuality of Service(QoS)クラス(Guaranteed、Burstable、BestEffort)をどのように決定し、ノードの不安定性を防ぎ、クラスターのスケジューリング効率を最適化するかを説明します。実践的なYAML例とパフォーマンスチューニングのベストプラクティスを含みます。

Kubernetesリソースリクエストと制限を極めて最高のパフォーマンスを実現する方法

Kubernetesのリソースリクエストと制限はYAML上ではシンプルに見えますが、クラスターのほぼすべての運用上の動作を形作ります:ポッドがどこに配置されるか、どのワークロードが最初に退避されるか、アプリケーションが負荷時にCPUスロットリングを受けるかどうか、そしてどれだけの余剰容量があると思い込んでいるか。設定が悪いと、健全なアプリケーションが壊れているように見えることがあります。設定が欠けていると、スケジューラーが最初のトラフィックスパイクがノイジーネイバー問題に変わるまでポッドをノードに詰め込む可能性があります。

チームが引っかかるのは、リクエストと制限が異なるシステムで使用されるという点です。リクエストは主にスケジューリングの約束です。制限は強制の境界です。これらを同じものとして扱うと、特にCPUにおいて奇妙な結果を招きます。

コアコンセプトの理解:リクエストと制限

Kubernetesでは、コンテナはresources.requestsresources.limitsを使用して期待されるリソース消費を定義できます。これらは、クラスターがLimitRangeやアドミッションコントロールなどのポリシーを使用しない限り技術的に必須ではありませんが、本番ワークロードは通常、少なくともCPUとメモリのリクエストを定義すべきです。リクエストがないと、スケジューラーは有用な情報をほとんど持たず、ポッドは弱いサービス品質の状態に陥ります。

1. リソースリクエスト(requests

リクエストは、コンテナがスケジューリング時に保証されるリソースの量を表します。これは、kube-schedulerがポッドを配置するノードを決定する際に使用する最小リソース量です。

  • スケジューリング: 新しいポッドをスケジュールする前に、ノードはすべてのポッドリクエストの合計を満たすのに十分な利用可能な割り当て可能リソースを持っている必要があります。
  • 実行時優先度: リクエストはCPUシェアとメモリ退避決定に影響を与えます。これはすべての速度低下を防ぐ魔法の予約ではありませんが、競合時にKubernetesとカーネルにより良い情報を提供します。

2. リソース制限(limits

制限は、コンテナが消費を許可されるリソースの最大量を定義します。これらの制限を超えると、CPUとメモリに対して特定の定義された動作が発生します。

  • CPU制限: コンテナがその制限を超えてCPUを使用しようとすると、Linuxカーネルのcgroupsがその使用をスロットリングし、それ以上のサイクル消費を防ぎます。
  • メモリ制限: コンテナがメモリ制限を超えると、カーネルはコンテナ内のプロセスを終了できます。Kubernetesは、これが記録された終了理由である場合、これをOOMKilledとして報告します。

CPUとメモリの動作の違い

KubernetesがCPUとメモリの境界をどのように強制するかの質的な違いを理解することが重要です:

リソース 制限超過時の動作 強制メカニズム
CPU スロットリング(速度低下) cgroups(CPU帯域幅制御)
メモリ 終了(OOMKill) カーネルOOM Killer

実践上の注意: CPU制限はノードを暴走プロセスから保護できますが、低く設定しすぎるとレイテンシ問題を引き起こす可能性があります。多くのプラットフォームチームはメモリ制限を一貫して設定し、レイテンシに敏感なサービスに対してはリスク許容度とクラスターポリシーに応じてCPU制限をより選択的に設定します。

ポッド仕様でのリソース定義

リソースはspec.containers[*].resourcesブロック内で定義されます。数量は標準のKubernetesサフィックス(例:CPUにはm(ミリCPU)、メモリにはMi(メビバイト))を使用して指定されます。

CPU単位の定義

  • 1 CPU単位は1フルコア(またはクラウドプロバイダーではvCPU)に相当します。
  • 1000m(ミリコア)は1 CPU単位に相当します。

メモリ単位の定義

  • Mi(メビバイト)またはGi(ギビバイト)が一般的です。
  • 1024Mi = 1Gi

YAML設定例

500mのCPUと256Miのメモリの保証された最小値を必要とするが、1 CPUと512Miを決して超えてはならないコンテナを考えます:

resources:
  requests:
    memory: "256Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "1"

数値は推測ではなく、観測された動作から得るべきです。小さなHTTP APIの場合、開始リクエストはビジネストラフィック中の通常のp50またはp90使用量に基づき、負荷テスト後に調整します。JVMサービスの場合、メモリにはヒープ、メタスペース、ネイティブメモリ、スレッドスタック、ダイレクトバッファ、サイドカーオーバーヘッドを含める必要があります。バッチジョブの場合、最大入力時のピークメモリが平均メモリよりも重要になることがあります。

サービス品質(QoS)クラス

リクエストと制限の関係は、ポッドに割り当てられるサービス品質(QoS)クラスを決定します。このクラスは、リソースが不足しノードがメモリを回収する必要がある場合(退避)のポッドの優先度を決定します。

Kubernetesは3つのQoSクラスを定義しています:

1. Guaranteed

定義: ポッド内のすべてのコンテナが、CPUとメモリの両方に対して同一でゼロ以外のリクエストと制限を持っている必要があります。

  • 利点: これらのポッドはリソース圧力時に最後に退避され、最大の安定性を保証します。
  • ユースケース: 厳格なパフォーマンス分離を必要とする重要なシステムコンポーネントやデータベース。

2. Burstable

定義: ポッド内の少なくとも1つのコンテナがリクエストを定義しているが、すべてのコンテナでリクエストと制限が等しくないか、一部のリソースが制限されていない(ただし制限の設定は強く推奨されます)。

  • 利点: コンテナが定義された制限まで、ノード上の未使用容量を利用してリクエストを超えてバーストできるようにします。
  • 退避優先度: BestEffortポッドの前に退避されますが、Guaranteedポッドの後です。
  • ユースケース: レイテンシのわずかな変動が許容されるほとんどの標準的なステートレスアプリケーション。

3. BestEffort

定義: ポッドがどのコンテに対してもリクエストや制限を定義していません。

  • 利点: シンプルさ以外にはありません。
  • リスク: これらのポッドは、ノードがメモリ圧力を経験したときに最初に退避される候補です。また、リクエストが宣言されていないため、CPUの競合で不利になる可能性があります。
  • ユースケース: 簡単に再起動できる非クリティカルなバッチジョブやログエージェント。

実践的な最適化戦略

効果的なリソース管理には、測定、反復、そして慎重な計画が必要です。

戦略1:リクエストを正確に測定して設定する

リクエストは、アプリケーションがほとんどの場合に許容可能に動作するために必要なリソースの量を反映すべきです。リクエストを高く設定しすぎると、スケジューラーがその容量をすでに予約済みとして扱うため、クラスター容量を無駄にします。低く設定しすぎると、スケジューラーがノードに過剰なポッドを配置し、競合時にワークロードが影響を受けやすくなる可能性があります。

PrometheusやGrafanaなどの監視ツールを使用して、リクエスト値を実際の使用量と比較します。一般的な出発点は、数日間の通常トラフィックを調べ、明らかな一回限りのインシデントを無視し、単一の最高スパイクではなく持続的なパーセンタイル付近にリクエストを設定することです。正確なパーセンタイルはポリシーの選択です。重要なのはデータを使用し、それを再評価することです。

例えば、サービスが通常180m CPUを使用し、デプロイウォームアップ中に約450mにピークし、既知のバッチタスク中にまれに900mにスパイクする場合、900mのリクエストを設定すると一日中容量を無駄にする可能性があります。50mのリクエストを設定すると、ポッドはスケジュールが安くなりますが、競合下で不安定になります。通常の持続範囲付近のリクエストと、バッチパスに対する別個の処理が、より良い議論になることがよくあります。

戦略2:保守的な制限を定義する

制限は安全境界として機能しますが、無料ではありません。メモリの場合、測定されたピーク使用量をわずかに上回る制限は、1つのコンテナがノードを消費するのを防ぐことができます。CPUの場合、制限は暴走プロセスが無制限にCPUを使用するのを防ぎますが、積極的な制限はノードにアイドルコアがある場合でもサービスをスロットリングする可能性があります。

CPU制限に関する警告: CPU制限を実際の需要より低く設定すると、スロットリングによる顕著なレイテンシが発生する可能性があります。Burstable QoSは多くのステートレスサービスに適した選択であり、Guaranteed QoSは分離のトレードオフが意図的なワークロードに適しています。

戦略3:Vertical Pod Autoscaler(VPA)の活用

手動でリソースを調整するのは難しく、時間がかかります。Vertical Pod Autoscaler(VPA) は実行時使用量を監視し、そのモードに応じてリソースリクエストを推奨または更新できます。多くの設定では、VPAは最初に推奨モードで使用され、チームが自動更新を許可する前に提案されたリクエストをレビューできるようにします。

VPAをHorizontal Pod Autoscalerと組み合わせる際は注意してください。HPAは多くの場合、リクエストに対する使用率に基づいてスケーリングするため、リクエストを変更するとスケーリング動作が変わることがあります。うまく機能することもありますが、意図的にテストする必要があります。

戦略4:名前空間のリソースクォータ

チームや環境間でのリソースの占有を防ぐために、管理者は名前空間レベルでリソースクォータを使用する必要があります。ResourceQuotaは、その名前空間内に存在できるCPU/メモリリクエストと制限の総量に集約制限を課し、公平性を確保します。

名前空間クォータの例

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: development
spec:
  hard:
    requests.cpu: "10"
    limits.memory: "20Gi"

これにより、development名前空間内のすべてのポッドの合計リクエストCPUが10コアを超えず、合計メモリ制限が20Giを超えないことが保証されます。

実際のクラスターでの悪い設定の現れ方

リソースのミスは「リソースミス」として通知されることはほとんどありません。通常、アプリケーションインシデントのように見えます。

CPU制限が低すぎる場合、CPU使用率グラフが上限に達しているように見える一方で、高いリクエストレイテンシが発生することがあります。コンテナはより多くのCPUを必要としていますが、cgroupスロットリングがそれを抑制しています。Prometheusベースの設定では、container_cpu_cfs_throttled_periods_totalcontainer_cpu_cfs_periods_totalなどのメトリクスが、スロットリングが原因の一部であるかどうかを示すのに役立ちます。

メモリ制限が低すぎる場合、ポッドはReason: OOMKilledで再起動する可能性があります。プロセスが正常にシャットダウンされなかったため、アプリケーションログが突然終了することがあります。この場合、kubectl describe podは通常、アプリケーションログよりも早く真実を伝えます。

リクエストが高すぎる場合、ダッシュボードで平均ノード使用率が低く表示されていても、ポッドがPending状態になることがあります。スケジューラーは平均実際使用量に基づいてポッドを配置するのではなく、要求されたリソースと割り当て可能なリソースを比較します。これが、クラスターが過小使用に見えても新しいポッドを拒否する理由です。

リクエストがない場合、ワークロードは穏やかな期間は問題なく見えても、ノードがビジーになると最初に圧迫されるものになります。これは使い捨てジョブには許容できるかもしれませんが、ユーザー向けサービスには不適切なデフォルトです。

より安全なチューニングワークフロー

実践的なチューニングループは次のようになります:

  1. サイドカーを含むすべての本番コンテナに対して、明示的なCPUとメモリのリクエストから始めます。
  2. 観測されたピークにヘッドルームを加えたものに基づいてメモリ制限を設定し、OOMKilled再起動を監視します。
  3. CPU制限がポリシーまたはワークロードリスクによって必要かどうかを決定します。使用する場合は、スロットリングを監視します。
  4. 要求されたCPUとメモリを実際の使用量と毎週または毎月比較し、特にメジャーリリース後に行います。
  5. 適正化を変更管理として扱います。リクエストを減らすとビンパッキング密度が向上する可能性がありますが、ノード圧力時の障害動作も変わる可能性があります。

サイドカーには注意が必要です。サービスメッシュプロキシ、ログシッパー、セキュリティエージェントは、ポッドの実際のフットプリントを変えるのに十分なCPUまたはメモリを消費する可能性があります。メインアプリコンテナだけが調整されている場合でも、ポッドはスケジューラーに誤って表現される可能性があります。

例:CPUスロットリングによるレイテンシスパイクの修正

次の設定を持つAPIコンテナを想像してください:

resources:
  requests:
    cpu: "100m"
    memory: "256Mi"
  limits:
    cpu: "200m"
    memory: "512Mi"

セールイベント中にレイテンシが急上昇します。ノードにはまだアイドルCPUがありますが、コンテナは200mに制限されています。レプリカを増やすと役立つかもしれませんが、各ポッドは依然として個別にスロットリングされています。より良い修正は、CPU制限を引き上げるか削除し、リクエストを通常の持続需要に合わせて増やし、HPAを使用してレイテンシが悪化する前にサービスがスケールするようにすることです。

重要な教訓は、グラフでCPU使用率が低いことが必ずしもアプリがアイドル状態であることを意味しないということです。アプリがそれ以上使用することを許可されていない可能性があります。

最終確認

リクエストはKubernetesにポッドを配置し優先順位を付ける方法を指示します。制限はカーネルにどこで停止するかを指示します。適切な値は、実際のメトリクス、負荷テスト、そして各ワークロードにとって密度、分離、レイテンシ、コストのどれがより重要かについての明確な決定から得られます。トラフィックの変更、依存関係の変更、ランタイムのアップグレード後に値を再評価してください。古いリソース設定は、クラスターがパフォーマンス低下に陥る最も静かな方法の1つです。