Certified Kubernetes Application Developer (CKAD) #16 リソース管理: requests/limits、QoS class、LimitRange

#15 SecurityContext と Capabilities でコンテナが どんな権限で 動くかを押さえたなら、今回はコンテナが どれだけのリソースを 使えるかを扱います。Pod がノードの CPU と memory を無制限に取っていくと、1 つのワークロードがノード全体を麻痺させかねません。Kubernetes はコンテナごとに「これだけは保証してほしい (requests)」と「これ以上は使えない (limits)」を宣言させて、スケジューリングと安定性を同時に確保します。

このトピックは CKAD の最大ドメインである Application Environment, Configuration and Security (25%) に属し、試験では requests/limits を正確な単位で付ける作業とともに、「この Pod の QoS class は何か」「なぜ OOMKilled になったか」といった判別問題としてよく出ます。単位と挙動を手に馴染ませておくと素早く得点できます。

requests と limits: 何が違うか #

リソース宣言はコンテナ単位で spec.containers[].resources の下に入ります。2 つのキーの意味は異なります。

  • requests: このコンテナが 最低限保証されるべき 量です。スケジューラはノードの割り当て可能リソースからこの値を引きながら Pod を配置します。つまり requests は スケジューリングの基準 です。
  • limits: このコンテナが 使ってよい上限 です。ランタイムがこの値を超えないように強制します。

requests だけあって limits がなければ上限なしにノードの余裕分まで使え、limits だけあって requests がなければ Kubernetes は requests を limits と同じ値とみなします。

単位: CPU と memory を正確に書く #

CKAD でよく間違える部分が単位です。CPU と memory は表記体系が異なります。

リソース単位意味
CPU10.5500m1 は vCPU 1 個、1000m (millicore) が 1 コア。500m は 0.5 コア
memory128Mi1Gi512MMi/Gi は 2 進 (1Mi = 1024Ki)、M/G は 10 進 (1M = 1000K)

CPU の m は millicore で、500m は 0.5 コアと同じです。memory では Mi (mebibyte) と M (megabyte) は異なる値なので、試験で Mi を要求されたら M で書いてはいけません。通常は MiGi を標準として使います。

apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
    - name: app
      image: nginx
      resources:
        requests:
          cpu: "250m"
          memory: "128Mi"
        limits:
          cpu: "500m"
          memory: "256Mi"

この Pod は 0.25 コアと 128Mi を保証され、最大で 0.5 コアと 256Mi まで使えます。スケジューラは requests の合計が入る空きのあるノードを探して配置します。

上限を超えるとどうなるか #

CPU と memory は limits を超過したときの挙動が決定的に異なります。この違いが試験の定番です。

  • CPU 超過: CPU は圧縮可能 (compressible) なリソースです。limits を超えるとコンテナは死なずに スロットル (throttle)、つまり割り当てられた時間だけ実行されるよう速度が制限されます。遅くなるだけで終了はしません。
  • memory 超過: memory は圧縮不可能 (incompressible) なリソースです。limits を超えるとカーネルの OOM killer がコンテナプロセスを終了し、コンテナの状態が OOMKilled と表示されます。restartPolicy に従って再起動され、超過が続くと CrashLoopBackOff につながります。

OOMKilled が見えたら、原因はほぼ常に「memory limits が実際の使用量より低い」ことです。limits を上げるか、アプリケーションの memory 使用を減らすかの 2 方向でアプローチします。

# 終了原因の確認
k describe pod resource-demo | grep -A3 "Last State"
# Last State: Terminated
#   Reason:   OOMKilled

QoS class: 3 等級と eviction #

Kubernetes は requests と limits をどう指定したかに応じて、Pod に Quality of Service (QoS) class を自動的に付与します。この等級は、ノードのリソースが不足したときに 誰を先に追い出すか (eviction) を決めます。

QoS class条件eviction 優先順位
Guaranteedすべてのコンテナで cpu/memory を両方指定 + requests == limits最も遅く追い出される
Burstablerequests か limits の一部だけ指定 (Guaranteed 条件を満たさない)中間
BestEffortrequests も limits もまったくない最も先に追い出される

要点は次のとおりです。

  • Guaranteed: すべてのコンテナが cpu と memory の両方を指定し、それぞれ requests と limits が同じ値のときです。memory が不足したときに最後まで保護されます。
  • Burstable: requests はあるが limits の方が大きい、または一部のリソースだけ指定した場合です。requests を超えて使っていた部分が回収対象になります。
  • BestEffort: リソース宣言がまったくない Pod です。ノード圧迫時に 最も先に eviction 対象になります。

Guaranteed になるマニフェスト #

requests と limits を cpu、memory ともに同じ値にすると Guaranteed になります。試験で「この Pod を Guaranteed にせよ」という作業が出たら、このパターンを使います。

apiVersion: v1
kind: Pod
metadata:
  name: guaranteed-demo
spec:
  containers:
    - name: app
      image: nginx
      resources:
        requests:
          cpu: "500m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "256Mi"

limits だけ指定しても Kubernetes が requests を同じ値で埋めてくれるので、結果は Guaranteed になります。ただし試験では意図を明確にするため、requests と limits を両方明示する方が安全です。

QoS class の確認 #

付与された QoS class は describe ですぐに見えます。

k describe pod guaranteed-demo | grep "QoS Class"
# QoS Class:  Guaranteed

LimitRange: ネームスペースにデフォルト値と限界をかける #

個々の Pod ごとに requests/limits を抜かすと BestEffort になって危険です。LimitRange は、ネームスペース内のコンテナに デフォルト値を埋め込み、許容範囲の最小/最大を強制するポリシーオブジェクトです。

  • default: コンテナが limits を指定しないときに埋める デフォルト limits です。
  • defaultRequest: requests を指定しないときに埋める デフォルト requests です。
  • min / max: コンテナが指定できる値の下限と上限です。この範囲を外れる Pod は生成が拒否されます。
apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-mem-limits
  namespace: dev
spec:
  limits:
    - type: Container
      default:
        cpu: "500m"
        memory: "256Mi"
      defaultRequest:
        cpu: "250m"
        memory: "128Mi"
      min:
        cpu: "100m"
        memory: "64Mi"
      max:
        cpu: "1"
        memory: "512Mi"

この LimitRange が適用された dev ネームスペースで requests/limits なしに Pod を作ると、コンテナは自動的に requests 250m/128Mi と limits 500m/256Mi を付与されて Burstable になります。逆に max を超える limits を書いて提出すると Pod は拒否されます。

k apply -f limitrange.yaml
k describe limitrange cpu-mem-limits -n dev

ResourceQuota: ネームスペースの総量を抑える #

LimitRange が コンテナ 1 つひとつ のデフォルト値と範囲を扱うのに対し、ResourceQuotaネームスペース全体の合計 を制限します。1 つのチームがクラスタリソースを独占しないようにする用途です。ResourceQuota がかかったネームスペースでは、すべての Pod が requests/limits を必ず指定する必要があり、抜かすと生成が拒否されます。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "2"
    requests.memory: "2Gi"
    limits.cpu: "4"
    limits.memory: "4Gi"
    pods: "10"

この ResourceQuota は dev ネームスペース内のすべての Pod が要求する requests の合計を cpu 2 コアと memory 2Gi に、limits の合計を cpu 4 コアと memory 4Gi に、Pod 数を 10 個に縛ります。LimitRange と ResourceQuota を併用すると「デフォルト値の自動埋め込み + 総量の上限」が同時にかかります。

k apply -f resourcequota.yaml
k get resourcequota dev-quota -n dev
# 現在の使用量と限度を一緒に表示
k describe resourcequota dev-quota -n dev

実使用量の確認: k top #

宣言した値が実際の使用量と合っているかは kubectl top で見ます。このコマンドはクラスタに metrics-server がインストールされていないと動作しません。

# Pod ごとの実際の CPU/memory 使用量
k top pod
k top pod resource-demo -n dev

# ノード単位の使用量
k top node

k top で見た実使用量を基準に、requests は平常時の使用量に、limits はピーク使用量に合わせて調整するのが一般的なチューニングの方向です。

試験ポイント #

  • QoS class の判別が核心 です。requests と limits を cpu/memory 両方指定し、2 つの値が同じなら Guaranteed、一部だけあるか値が異なれば Burstable、両方なければ BestEffort です。describeQoS Class 行で即座に確認します。
  • OOMKilled = memory limits 超過 です。k describe podLast StateReason: OOMKilled を読み、memory limits を上げるか使用量を減らす方向で解決します。CPU 超過は死なずにスロットルだけかかる点と混同しないようにします。
  • 単位を正確に 書きます。CPU は 500m = 0.5 コア、memory は Mi/Gi (2 進) と M/G (10 進) が異なる値です。問題が Mi を要求したら M で書きません。
  • LimitRange の default vs defaultRequest を区別します。default は limits のデフォルト値、defaultRequest は requests のデフォルト値です。
  • dry-run で骨組みを作ってから resources だけ追加 する流れが速いです。k run app --image=nginx $do > pod.yaml の後に resources ブロックを編集します。

リソース管理はマニフェスト 1 ブロックで終わりますが、QoS と OOMKilled の挙動を問う判別問題まで一緒に出るため、単位と等級の条件を手に馴染ませることが得点に直結します。リソース要求とノードスケジューリングのより広い文脈は K8s 中級 #4 でも扱うので、合わせて読むと役立ちます。

まとめ #

この記事で押さえたこと:

  • requests はスケジューリングの基準、limits は上限 です。CPU は m (millicore)、memory は Mi/Gi 単位で表記します。
  • CPU 超過はスロットル、memory 超過は OOMKilled です。CPU は死なずに遅くなりますが、memory は終了します。
  • QoS class 3 種: Guaranteed (requests == limits を全部指定)、Burstable (一部指定)、BestEffort (未指定)。eviction は BestEffort から追い出します。
  • LimitRange はネームスペースに default/defaultRequest と min/max をかけ、ResourceQuota はネームスペースの総量を制限します。
  • 確認は k describe pod (QoS Class・OOMKilled) と k top pod (実使用量) で行います。

次へ: Volumes #

リソースの量を押さえたので、ここからはコンテナがデータをどこに置くかを扱います。

#17 Volumes: emptyDir、PVC、projected、ephemeral では、Pod 内でコンテナ同士がディレクトリを共有する emptyDir、永続ストレージを要求する PersistentVolumeClaim、ConfigMap と Secret を 1 か所にまとめてマウントする projected volume、そして寿命が Pod に縛られる ephemeral volume まで YAML で直接付けながら整理します。

X