ブログ一覧

KubernetesのOOMKilledとCPUスロットリング:requests・limits・HPAの調べ方

· 約10分で読めます

Podが再起動する、CPU平均は低いのに遅い、HPAで増やしても改善しない。Linuxコンテナを対象に、requestsとlimitsの違い、OOMと退避の切り分け、HPAの分母、DB接続予算まで順に調べます。

requestsとlimitsは何が違うのか

対象はLinuxノード上のコンテナごとのCPU・メモリ設定です。Pod単位のリソース設定やMemoryQoS、ランタイムによる違いがあるため、まず実際に適用された設定を確認します。説明用の次の値は、サービスへの推奨値ではありません。

コンテナ設定の抜粋:CPUのmは1コアの1,000分の1、Miはメビバイト
# Excerpt from one container's spec; hypothetical values, not defaults
resources:
  requests:
    cpu: "200m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"
設定主な役割取り違えやすい点
CPU requests配置判断と、CPU競合時の相対的な配分の入力200mを指定しても、使用量を200mに制限しない
CPU limitsCPU時間の割り当てを制限する500mは実行時間のどの瞬間も半コアだけ動くという意味ではない
memory requests配置に必要なメモリ量の入力指定量がアプリ専用に確保済みという意味ではない
memory limitsメモリ使用の制約。圧迫時にはOOMによる終了があり得る設定値を越えた瞬間に必ず終了するとは限らない

例えばスケジューラーは、ノードの現在のメモリ使用量が低いという理由だけで、新しいPodのrequestsを無視して配置しません。反対に、requestsを下げて置けるようにしても、実際に必要なメモリが減るわけではありません。配置密度と実行時の余力を別々に確認します。

変更する前に、終了理由と直前の状態を集める

最初にPodの状態、対象コンテナの直近の終了理由、再起動回数、イベント、前回実行のログを確認します。次のコマンドは調査用の例です。実環境での実行結果や原因を示すものではありません。topは現在のサンプルなので、既に終了した直前のピークは別の時系列監視で補います。

kubectl:状態とログを読む調査コマンドの例
# Replace namespace, Pod, container, and HPA names for your environment
kubectl describe pod <pod> -n <namespace>
kubectl logs <pod> -c <container> -n <namespace> --previous
kubectl top pods -n <namespace> --containers
kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp
kubectl get hpa -n <namespace>
kubectl describe hpa <hpa> -n <namespace>
症状確認する証拠主な次の手順
コンテナが再起動するlastStateの理由、前回ログ、メモリ時系列、ノード状態OOM、アプリ異常終了、probe失敗などを切り分ける
PodがEvictedになるPodの理由・メッセージ、ノードのMemoryPressureやディスク状態ノード全体の余力と退避イベントを調べる
遅いが再起動しないリクエスト遅延、CPU制限、cgroupのスロットリング統計CPU制約とDB待ち・ロック待ちを比較する
HPAの希望台数は増えたがPendingFailedSchedulingイベントと要求量ノードの配置余力や制約、クォータを調べる

OOMKilledとノードの退避を切り分ける

OOMKilledはメモリ不足で終了した手掛かりですが、コンテナのlimit到達とノード全体のOOMを区別して調べます。終了コード137だけではOOMと断定できません。SIGKILLによる終了でも現れるため、終了理由、ノード側の記録、メモリの推移を合わせます。ノード圧迫を検知したkubeletによるPodの退避も、コンテナのOOM終了とは別の経路です。

メモリはヒープだけで説明できません。ネイティブ領域、スレッド、バッファ、キャッシュ、計測上含まれるファイル関連メモリを含めて調べます。並列数やバッチサイズに比例するのか、処理が落ち着いても増え続けるのか、起動時だけ高いのかで、上限調整・同時実行制御・リーク修正の選択が変わります。

CPU平均が低くてもスロットリングを調べる理由

LinuxのCPU帯域制御では、一定期間に使えるCPU時間の枠を使い切ると、次の枠まで実行を待つことがあります。短い並列バーストと長いアイドルが混ざれば、長い窓の平均CPUが低くても、短い時間の制限と遅延が同居し得ます。平均が低いことだけでCPU制約を除外せず、同時刻の遅延とスロットリングの増分を比べます。

ただしスロットリング回数の増加だけで、p99悪化の原因とも断定できません。DB待ち、GC、ロック、ノード競合も確認し、同じ流量と処理内容でlimitや並列数を変えて比較します。自身のCPU limitを外しても、上位cgroupの制限やCPU競合は残り得ます。ランタイムの並列実行設定は、使用している版とコンテナ認識の挙動を確認します。

HPAのCPU使用率はlimitsではなくrequestsで割る

CPUのResourceメトリクスをUtilizationで使うHPAを考えます。単一コンテナのPodでCPU使用量100m、requestが200mなら使用率は50%。同じ使用量でもrequestを100mへ下げると100%です。requestの変更は、配置の計算だけでなくスケール判定も変えます。必要なrequestがないコンテナを含む場合は、メトリクスが定義できないこともあります。

4 Podの平均が100%、目標が50%なら、基本式 ceil(4 * 100 / 50) は8 Podです。これは全Podのメトリクスが有効という単純化で、実際の判断には許容幅、未準備Pod、欠損メトリクス、min/maxReplicas、スケール速度などが関わります。希望台数が8になっても、8台がすぐ処理へ参加できるわけではありません。

希望台数を増やしてから処理能力へ反映されるまで。各段階の待ち時間を測る。

HPAが希望台数を更新 → 配置可能なノードを確保 → イメージ取得とプロセス起動 → 初期化・readiness通過 → 新Podが処理へ参加

Pendingなら配置イベント、起動済みでReadyにならないなら初期化やprobe、Readyでも遅いなら実際の処理能力と下流待ちを調べます。起動時間を短くする、通常時の余力を持つ、流入を制御する対策を組み合わせ、HPAが追いつくまで無制限に待機処理をためないようにします。

Podを増やす前にDB接続の総数を計算する

独自の計算例として、1 Podに1プロセス、各プール上限20接続なら、20 Podで最大400接続、30 Podで600接続です。DB上限500接続の環境なら、後者は他の利用者を考慮する前に超えます。ローリング更新や終了待ちによる一時的な追加プロセス、バッチ、管理用の接続も予算へ入れます。

確認する上限数え方判断
DB接続同時に存在する全プロセスのプール上限を合計他の利用者と管理用の余力を引いて配分する
外部API通常処理・再試行を含む全Podの呼び出し全体のレートと同時実行を制限する
待機処理プール待ち・アプリ待ち・キュー内の件数と年齢長さと期限を制限し、拒否や延期の経路を決める

プール上限は潜在的な接続数で、常時その数が開くとは限りません。一方、DBの接続上限以下でもロックやI/Oで飽和します。接続数だけでなくクエリ遅延、プール待ち、タイムアウトを負荷試験で確認し、Pod数の上限と接続配分を一緒に決めます。

変更後は、同じ負荷と起動条件で確かめる

変更前後で流量、並列数、ペイロード、試験時間をそろえ、通常時と起動直後の両方を比較します。再起動・退避、メモリピーク、スロットリング、p99遅延、Readyまでの時間、DB待ちを記録します。CPU使用率が下がっただけで改善とせず、業務の成功率と完了時間まで追うことで、設定変更が別のボトルネックへ問題を移していないか判断できます。

この記事で深掘りしたTips