Certified Kubernetes Administrator (CKA) #15 リソース管理: requests/limits、QoS、LimitRange、ResourceQuota
#14 Scheduling 2 では taints/tolerations と PriorityClass で どの Pod をどのノードにどんな優先度で置くか を決めました。この記事はもう一歩踏み込み、その Pod がノードの cpu と memory を どれだけ予約し、どこまで使えるか を扱います。requests/limits で個々のコンテナの資源を束ね、QoS クラスでノードが圧迫されたときに誰が先に追い出されるかを理解し、LimitRange と ResourceQuota でネームスペース単位の運用ポリシーを強制します。
このテーマは Workloads and Scheduling ドメインの最後のピースであると同時に、トラブルシューティングでよく出会う OOMKilled と Pending の根本原因でもあります。資源設定を知らないと、#22 Troubleshooting 1 の OOM・Pending 問題を最後まで追跡できません。
requests と limits: 予約と上限 #
Kubernetes でコンテナの資源は 2 つの値で表現されます。requests は予約 で limits は上限 です。この 2 つを明確に区別できないと、スケジューリングとトラブルシューティングの両方が揺らぎます。
- requests: このコンテナが正常動作に最低限必要とする量です。scheduler はノードの 割り当て可能 (allocatable) な資源の中に requests 合計が入る余地 があるノードにだけ Pod を配置します。つまり requests はスケジューリング決定の基準です。
- limits: このコンテナが使える最大量です。ランタイムが cgroup でこの上限を強制します。limits を超えようとすると cpu はスロットルされ、memory はプロセスが死にます。
scheduler は limits ではなく requests だけを見て配置する という点が核心です。limits の合計はノード容量を超えても配置され、これを オーバーコミット (overcommit) と呼びます。
単位表記 #
cpu と memory は単位表記が異なります。
| 資源 | 表記 | 意味 |
|---|---|---|
| cpu | 1、0.5、500m | 1 は vCPU 1 コア、500m は 0.5 コア (millicore) |
| memory | 128Mi、256Mi、1Gi | Mi = メビバイト (1024²、2^20)、Gi = ギビバイト |
cpu は millicore 単位で細かく刻めるので 250m は 0.25 コアを意味します。memory は Mi・Gi (2 進接頭辞) と M・G (10 進接頭辞) が異なるため、運用では混同を避けるために Mi・Gi に統一する方が安全です。
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "250m" # 0.25 コア予約
memory: "128Mi" # 128Mi 予約
limits:
cpu: "500m" # 0.5 コア上限
memory: "256Mi" # 256Mi 上限上のコンテナは 0.25 コアと 128Mi を予約され、負荷が集中すると 0.5 コアと 256Mi まで使えます。requests と limits の間の区間が バースト余裕 です。
CPU スロットル vs memory OOMKilled #
limits を超過したときの動作は cpu と memory で 根本的に異なります。この違いが試験と運用で最も混同しやすいポイントです。
| 資源 | limits 超過時の動作 | 結果 |
|---|---|---|
| cpu | スロットル (throttle)。cgroup が CPU 時間を削る | プロセスは死なず遅くなる |
| memory | OOMKilled。カーネル OOM killer がプロセスを終了 | コンテナ再起動 (restartPolicy に従う) |
cpu は 圧縮可能 (compressible) な資源 です。あるコンテナが cpu limit に達すると、カーネルはそのコンテナに割り当てる CPU 時間を減らすだけで、プロセスを殺しはしません。そのため cpu limit が低すぎると、アプリが死ぬ代わりに 応答が遅くなる 症状として現れます。
memory は 圧縮不可能 (incompressible) な資源 です。すでに割り当てられたメモリを回収する方法はないため、コンテナが memory limit を超えるとカーネル OOM killer がそのプロセスを終了します。このとき kubectl describe pod の状態が OOMKilled と表示され、restartPolicy に従って再起動と CrashLoopBackOff につながります。
# OOMKilled の確認
k describe pod web | grep -A3 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137終了コード 137 (= 128+9、SIGKILL) が見えたらメモリ超過を疑います。解決はメモリ limit を上げるか、アプリのメモリ使用を減らす方向です。このパターンは #22 Troubleshooting 1 で再び扱います。
QoS クラス: 誰が先に追い出されるか #
ノードの memory が不足すると、kubelet は Pod を eviction (追い出し) して資源を回収します。このときどの Pod を先に追い出すかは QoS (Quality of Service) クラス が決めます。QoS はユーザーが直接指定する値ではなく、requests と limits をどう設定したかから自動的に導出 されます。
| QoS クラス | 条件 | eviction 優先度 |
|---|---|---|
| Guaranteed | すべてのコンテナが cpu・memory の requests と limits を持ち、それぞれ requests == limits | 最後 (保護される) |
| Burstable | 1 つ以上のコンテナに requests があるが Guaranteed 条件に未達 | 中間 |
| BestEffort | どのコンテナにも requests/limits が一切ない | 最初 (優先して追い出し) |
ノードが memory 圧迫を受けると、kubelet は BestEffort → Burstable → Guaranteed の順で Pod を追い出します。つまり資源を明確に予約した Pod ほど保護されます。運用上の含意は明確です。重要なワークロードほど requests と limits を明示 して Guaranteed に近づけておけば、ノード圧迫の状況で生き残ります。
Guaranteed を作る方法 #
Guaranteed はすべてのコンテナで requests と limits が完全に同じとき に付与されます。
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m" # requests と同じ
memory: "256Mi" # requests と同じlimits だけ指定して requests を省略すると、Kubernetes が requests を limits と同じ値に自動補正します。そのため limits だけ書いても Guaranteed になれます。QoS クラスは次で確認します。
k get pod web -o jsonpath='{.status.qosClass}'
# GuaranteedLimitRange: ネームスペースのデフォルト値と境界 #
個々の Pod ごとに requests/limits を逐一書かせるのは運用で非現実的で、抜けると BestEffort になって危険です。LimitRange はネームスペース単位で コンテナのデフォルト値と許容範囲 を強制するオブジェクトです。
| フィールド | 役割 |
|---|---|
default | コンテナが limits を省略したら埋める既定 limit |
defaultRequest | コンテナが requests を省略したら埋める既定 request |
min | 許容する最小値 (これより小さいと作成拒否) |
max | 許容する最大値 (これより大きいと作成拒否) |
apiVersion: v1
kind: LimitRange
metadata:
name: container-limits
namespace: dev
spec:
limits:
- type: Container
default: # limits 未指定時に埋める値
cpu: "500m"
memory: "256Mi"
defaultRequest: # requests 未指定時に埋める値
cpu: "250m"
memory: "128Mi"
min: # 許容最小
cpu: "100m"
memory: "64Mi"
max: # 許容最大
cpu: "1"
memory: "1Gi"この LimitRange が適用された dev ネームスペースでは、resources を空にしたまま Pod を作っても自動的に defaultRequest と default が埋まります。また max の 1Gi を超える memory limit を要求すると API server が作成を拒否します。LimitRange は 作る時点で検証する ので、適用前にすでに立っていた Pod には遡及しません。
k apply -f limitrange.yaml
k describe limitrange container-limits -n devResourceQuota: ネームスペース総量の制限 #
LimitRange が コンテナ 1 つ の境界なら、ResourceQuota は ネームスペース全体の総量 を制限します。1 つのチームがクラスター資源を独占しないようにネームスペース単位で上限をかける、マルチテナンシー運用の核心ツールです。
ResourceQuota は大きく 2 つを制限します。
- 資源総合: ネームスペース内のすべての Pod の cpu・memory requests/limits の合計
- オブジェクト数: Pod、Service、ConfigMap、Secret、PVC などの個数
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: dev
spec:
hard:
# 資源総合
requests.cpu: "4" # requests cpu 合計最大 4 コア
requests.memory: "8Gi" # requests memory 合計最大 8Gi
limits.cpu: "8" # limits cpu 合計最大 8 コア
limits.memory: "16Gi" # limits memory 合計最大 16Gi
# オブジェクト数
pods: "20" # Pod 最大 20 個
services: "5"
configmaps: "10"
persistentvolumeclaims: "4"重要な制約が 1 つあります。ResourceQuota が requests.* や limits.* を制限するネームスペースでは、すべてのコンテナが該当する requests/limits を必ず指定しなければなりません。 指定しないとクォータ集計が不可能なので Pod 作成が拒否されます。そのため ResourceQuota は LimitRange とペアで 使うのが定石です。LimitRange がデフォルト値を埋めてくれれば、requests/limits を忘れた Pod もクォータ検証を通過します。
k apply -f resourcequota.yaml
k describe resourcequota team-quota -n dev
# Resource Used Hard
# -------- ---- ----
# requests.cpu 1500m 4
# requests.memory 3Gi 8Gi
# pods 7 20Used が Hard に達すると、その資源をさらに要求する Pod は forbidden エラーで拒否されます。クォータ超過で作成が止まったのに原因が分からないとトラブルシューティングが長引くので、Pending や作成失敗に出会ったら describe resourcequota で先に使用量を確認する習慣が役立ちます。
運用の観点: ネームスペースポリシーセット #
実務で新しいチームネームスペースを作るときは、次の 3 つを 1 まとまりで適用します。
- Namespace: 隔離単位を作ります。
- LimitRange: コンテナのデフォルト値と min/max を強制し、requests 抜けによる BestEffort と過大な単一コンテナを防ぎます。
- ResourceQuota: ネームスペースの総資源とオブジェクト数に上限をかけ、1 つのチームがクラスターを独占しないようにします。
この 3 つのオブジェクトが揃ってこそマルチテナンシーが安定して回ります。LimitRange なしで ResourceQuota だけかけると requests を書かない Pod が全部拒否されて開発者が混乱し、ResourceQuota なしで LimitRange だけかけると個々のコンテナは適正でもネームスペースの総量が無限に増えます。
この資源ポリシーの基礎概念は Kubernetes 中級 #4 で requests/limits と QoS を入門の観点で先に整理してあるので、概念が曖昧なら一緒に見ると役立ちます。
試験ポイント #
CKA でこのテーマは次の形でよく出ます。
- requests/limits の追加: 既存の Deployment や Pod に cpu・memory の requests/limits を明示する作業。
k editやマニフェスト修正で処理し、単位表記 (m、Mi、Gi) を正確に書くのが採点ポイントです。 - OOMKilled の診断: Pod が繰り返し再起動する原因を探すトラブルシューティング。
describeでOOMKilledと終了コード137を確認し、memory limit を上げます。 - LimitRange の作成: ネームスペースに default/defaultRequest/min/max を持つ LimitRange を作る作業。
type: Containerを落とさないようにします。 - ResourceQuota の作成: ネームスペースに資源総合やオブジェクト数のクォータをかける作業。
requests.cpuのようにドット (.) が入ったキー表記を正確に書きます。 - QoS クラスの確認:
k get pod <name> -o jsonpath='{.status.qosClass}'でクラスを確認したり、特定の QoS になるよう requests/limits を合わせる作業。
素早い作成コマンドも一緒に覚えておくと時間を節約できます。
# requests/limits を指定して Pod 作成
k run web --image=nginx \
--requests='cpu=250m,memory=128Mi' \
--limits='cpu=500m,memory=256Mi'
# ネームスペースに ResourceQuota を素早く作成
k create quota team-quota -n dev \
--hard=requests.cpu=4,requests.memory=8Gi,pods=20まとめ #
この記事で押さえたこと:
- requests は予約、limits は上限。scheduler は requests だけを見て配置し、limits の合計がノード容量を超えるオーバーコミットが可能です。
- cpu はスロットル、memory は OOMKilled。cpu は圧縮可能な資源なので遅くなるだけで死なず、memory は圧縮不可能なので limit 超過時に終了 (コード 137) します。
- QoS 3 種。Guaranteed (requests==limits)・Burstable・BestEffort の順で保護の強さが高く、eviction は BestEffort から起こります。
- LimitRange。ネームスペースのコンテナ単位で default・defaultRequest・min・max を強制します。
- ResourceQuota。ネームスペースの総資源とオブジェクト数を制限し、LimitRange とペアで使ってこそ安定します。
次へ: Storage 1 #
資源の cpu と memory を束ねたので、次はデータを入れる ストレージ です。
#16 Storage 1: Volume types、PV、PVC 静的プロビジョニング では、emptyDir・hostPath のような Volume タイプから、PersistentVolume と PersistentVolumeClaim でストレージを宣言的に要求しバインドする静的プロビジョニングまで、そして accessModes と reclaim policy が何の違いを生むかを、自分でマニフェストを書きながら扱います。