Ritolabo
  1. Home
  2. Kubernetes
  3. requests / limits: Kubernetes でコンテナが使う CPU とメモリを決める

requests / limits: Kubernetes でコンテナが使う CPU とメモリを決める

  • 公開日
  • カテゴリ:Kubernetes
  • タグ:Kubernetes,学習メモ
requests / limits: Kubernetes でコンテナが使う CPU とメモリを決める

Deployment の YAML に、CPU やメモリのことを何も書かなくても Pod は動く。ただし、書いていないと Kubernetes は、そのコンテナを動かすのにどれだけの CPU とメモリが必要か、どれだけまで使わせてよいかを知らない。Node の空きを見ずに Pod を置き、使う量にも上限がかからない。

requests / limits は、コンテナが使う CPU とメモリの量を Deployment の YAML に書く設定。requests は「最低これだけ必要」、limits は「これ以上は使わせない」。

この記事では、requests / limits を書かない Pod と書いた Pod を比べたうえで、requests が Node に収まらないとき、メモリの limits を超えたとき、CPU の limits を超えようとしたときに何が起きるかを確認する。

この記事でやること:

  • Node が持っている CPU とメモリの量と、すでに予約されている量の確認
  • requests / limits なしの Pod と、付けた Pod の QoS Class と Node の Allocated resources の比較
  • limits の実体(Node の cgroup)の確認
  • requests が Node に収まらないときの Pending の確認
  • メモリの limits を超えたときの OOMKilled の確認
  • CPU の limits を超えようとしたときのスロットリングの確認
  • metrics-server を入れて、使用量を kubectl top で見る

contents

  1. 環境
  2. requests と limits
    1. requests / limits はどこにあるか
  3. Node の大きさを見る
    1. メモリの単位
    2. kind では 3 つの Node が同じ数字を出す
    3. すでに予約されている量を見る
  4. requests / limits なしで動かす
  5. requests / limits を付ける
    1. CPU の単位
    2. apply する
  6. limits の実体を Node の中で見る
  7. Node に載らない requests(Pending)
    1. 戻す
  8. メモリの limits を超える(OOMKilled)
    1. requests は自動で入る
    2. Deployment なら起動し直される
  9. CPU の limits を超えようとすると待たされる
  10. 使用量を kubectl top で見る
  11. QoS Class
  12. 消す
  13. 用語メモ
  14. 参考 URL

環境

kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)

  • 環境: macOS(Apple Silicon)、Docker Desktop 29.8.0、kind v0.33.0、Kubernetes v1.37.0、kubectl v1.36.1

requests と limits

requestslimits
意味最低これだけ必要これ以上は使わせない
誰が使うかkube-scheduler(Control Plane)。Pod を置く Node を決めるときkubelet とコンテナランタイム(Node)。コンテナを動かすとき
超えたら超えてよい。Node に空きがあれば、requests より多く使えるメモリ: コンテナが止められる(OOMKilled)。CPU: 待たされる。止められない

requests / limits はどこにあるか

requests / limits は、動いているプログラムではなく、Deployment の YAML に書く設定。Deployment の中の Pod の定義のコンテナごとに書き、Control Plane で動いている etcd に保存される。(「ConfigMap: Kubernetes で Pod に渡す設定値」の「ConfigMap はどこにあるか」を参照)

この設定を読んで動くプログラムが2つある。requests を読むのは Control Plane の kube-scheduler で、Pod を置く Node を決めるときの材料にする。limits を読むのは Pod が置かれた Node の kubelet で、コンテナランタイム(containerd)に渡す。containerd は Linux カーネルの cgroup にその上限を設定する。

図を描画しています…
  • 角が二重の箱(kube-scheduler、kubelet)は動いているプログラム。それ以外は etcd に保存されたデータと、Node の上の Pod
  • kube-scheduler は、置き先の決まっていない Pod を見つけて、requests が Node の空きに収まる Node を選ぶ
  • kubelet は、自分の Node に置かれた Pod のコンテナを起動するときに、limits をコンテナランタイムに渡す。上限を守らせるのは Linux カーネル

Node の大きさを見る

Pod を動かす前に、Node が CPU とメモリをどれだけ持っているかを見る。

$ kubectl describe node k8s-study-worker | grep -A 17 '^Capacity:'

Capacity:
  cpu:                10
  ephemeral-storage:  62671097856
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  hugepages-32Mi:     0
  hugepages-64Ki:     0
  memory:             8022316Ki
  pods:               110
Allocatable:
  cpu:                10
  ephemeral-storage:  62671097856
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  hugepages-32Mi:     0
  hugepages-64Ki:     0
  memory:             8022316Ki
  pods:               110
部分意味
Capacityこの Node が持っている量
Allocatableそのうち、Pod に割り当ててよい量。kind では Capacity と同じ
cpu: 10CPU 10 個分
memory: 8022316Kiメモリ約 7.65Gi

この記事で見るのは cpu と memory の2行。

メモリの単位

8022316Ki の Ki はキビバイト(1024 バイト)。Kubernetes のメモリの量は、1024 倍ずつの Ki、Mi、Gi で書く。

単位読み方大きさ
Kiキビバイト1024 バイト
Miメビバイト1024 Ki = 1,048,576 バイト
Giギビバイト1024 Mi = 1,073,741,824 バイト

小文字の i がない K、M、G は 1000 倍ずつで、これも書ける。この記事では Mi / Gi だけを使う。

kind では 3 つの Node が同じ数字を出す

$ kubectl get nodes -o custom-columns='NAME:.metadata.name,CPU:.status.capacity.cpu,MEMORY:.status.capacity.memory'

NAME                      CPU   MEMORY
k8s-study-control-plane   10    8022316Ki
k8s-study-worker          10    8022316Ki
k8s-study-worker2         10    8022316Ki

kind の Node は、Docker の仮想マシン 1 台の上の Docker コンテナ 3 つ。それぞれが CPU 10 個、メモリ 7.65Gi と報告するが、実体は同じ 1 台の仮想マシンで、3 台分の合計があるわけではない。

すでに予約されている量を見る

同じ describe node の出力に、この Node に置かれている Pod と、その requests / limits の合計がある。

$ kubectl describe node k8s-study-worker | grep -A 12 'Non-terminated Pods:'

Non-terminated Pods:          (2 in total)
  Namespace                   Name                CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                ------------  ----------  ---------------  -------------  ---
  kube-system                 kindnet-m7w6c       100m (1%)     0 (0%)      50Mi (0%)        0 (0%)         7d22h
  kube-system                 kube-proxy-qx648    0 (0%)        0 (0%)      0 (0%)           0 (0%)         7d22h
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests   Limits
  --------           --------   ------
  cpu                100m (1%)  0 (0%)
  memory             50Mi (0%)  0 (0%)
  ephemeral-storage  0 (0%)     0 (0%)
  hugepages-1Gi      0 (0%)     0 (0%)
部分意味
Non-terminated Podsこの Node に置かれていて、終了していない Pod の一覧。kube-system の Pod 2 つだけ
CPU Requests から Memory LimitsPod ごとの requests / limits。括弧の % は Allocatable に対する割合
Allocated resources上の表の合計。この Node で、すでに宣言されている量
  • kindnet-m7w6c(Pod 同士の通信を担当する kind の部品)は、requests に CPU 100m、メモリ 50Mi を宣言している。limits はなし
  • kube-proxy-qx648 は何も宣言していない
  • Allocated resources は、実際に使っている量ではない。Pod の YAML に書かれた requests / limits の足し算

requests / limits なしで動かす

CPU やメモリのことを何も書いていない Deployment を動かす。(Deployment は「Deployment: Kubernetes で Pod の数と更新を管理する」を参照)

# demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      containers:
        - name: nginx
          image: nginx:1.16.1
          ports:
            - containerPort: 80
$ kubectl apply -f demo-deployment.yaml

# => deployment.apps/demo-app created

$ kubectl get pods -o wide

NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE                NOMINATED NODE   READINESS GATES
demo-app-6d69c7cc8c-wsf5j   1/1     Running   0          5s    10.244.2.7   k8s-study-worker2   <none>           <none>

Pod は k8s-study-worker2 に置かれた。describe pod で Limits / Requests / QoS Class の行を見る。

$ kubectl describe pod -l app=demo-app | grep -E 'Limits|Requests|QoS Class'

QoS Class:                   BestEffort
  • Limits / Requests の行は出てこない。何も書いていないので、表示する項目がない
  • QoS Class は BestEffort。QoS Class は、requests / limits の書き方で Pod を 3 種類に分けたもの。BestEffort は何も宣言していない Pod

Pod が置かれた k8s-study-worker2 の Allocated resources を見る。

$ kubectl describe node k8s-study-worker2 | grep -A 12 'Non-terminated Pods:'

Non-terminated Pods:          (3 in total)
  Namespace                   Name                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                         ------------  ----------  ---------------  -------------  ---
  default                     demo-app-6d69c7cc8c-wsf5j    0 (0%)        0 (0%)      0 (0%)           0 (0%)         5m40s
  kube-system                 kindnet-ds92d                100m (1%)     0 (0%)      50Mi (0%)        0 (0%)         7d22h
  kube-system                 kube-proxy-vfdhm             0 (0%)        0 (0%)      0 (0%)           0 (0%)         7d22h
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests   Limits
  --------           --------   ------
  cpu                100m (1%)  0 (0%)
  memory             50Mi (0%)  0 (0%)
  ephemeral-storage  0 (0%)     0 (0%)
  • Non-terminated Pods に demo-app-6d69c7cc8c-wsf5j が増えたが、すべて 0 (0%)
  • Allocated resources の合計は、kindnet の分(CPU 100m、メモリ 50Mi)から変わっていない

requests / limits を書かない Pod は、Node に何も予約しない。Kubernetes から見ると CPU もメモリも 0 でよい Pod なので、どの Node にも置ける。nginx が実際に使っているメモリは、この帳簿には載らない。

requests / limits を付ける

Deployment の YAML のコンテナに resources を足す。

# demo-deployment-resources.yaml(containers の部分のみ)
      containers:
        - name: nginx
          image: nginx:1.16.1
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
場所意味
resourcesCPU とメモリの量の設定。コンテナごとに書く
requests.cpu / requests.memory最低これだけ必要、と宣言する量。kube-scheduler が Node を選ぶときに使う
limits.cpu / limits.memoryこれ以上は使わせない、という上限。Node の kubelet がコンテナランタイムに渡す

requests は limits 以下にする。limits のほうを小さく書くとエラーになる。

CPU の単位

100m の m はミリ(1/1000)。1000m が CPU 1 個分。

書き方意味
1CPU 1 個分(1 コア。仮想マシンなら仮想コア 1 つ)
500m または 0.5CPU 半分
100m または 0.1CPU 0.1 個分。100 ミリ CPU、100 ミリコアと読む

Node の cpu: 10 は 10000m。requests.cpu: 100m はその 1%。

メモリの 64Mi は 64 × 1024 × 1024 = 67,108,864 バイト。大文字と小文字で意味が変わり、メモリに 400m と書くと 0.4 バイトの意味になる。

apply する

$ kubectl apply -f demo-deployment-resources.yaml

# => deployment.apps/demo-app configured

$ kubectl get pods -o wide

NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE               NOMINATED NODE   READINESS GATES
demo-app-86547cd9f8-w2sq8   1/1     Running   0          7s    10.244.1.7   k8s-study-worker   <none>           <none>

Pod の定義が変わったので、Pod が入れ替わった(demo-app-6d69c7cc8c-wsf5j から demo-app-86547cd9f8-w2sq8 に)。置かれた Node も k8s-study-worker2 から k8s-study-worker に変わっている。

$ kubectl describe pod -l app=demo-app | grep -A 5 'Limits:'

    Limits:
      cpu:     200m
      memory:  128Mi
    Requests:
      cpu:        100m
      memory:     64Mi

$ kubectl describe pod -l app=demo-app | grep 'QoS Class'

QoS Class:                   Burstable
  • Limits: / Requests: に、YAML に書いた値がそのまま出ている
  • QoS Class が BestEffort から Burstable に変わった。Burstable は、requests / limits を書いているが、requests と limits が同じ値ではない Pod

Pod が置かれた k8s-study-worker の Allocated resources を見る。

$ kubectl describe node k8s-study-worker | grep -A 12 'Non-terminated Pods:'

Non-terminated Pods:          (3 in total)
  Namespace                   Name                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                         ------------  ----------  ---------------  -------------  ---
  default                     demo-app-86547cd9f8-w2sq8    100m (1%)     200m (2%)   64Mi (0%)        128Mi (1%)     2m6s
  kube-system                 kindnet-m7w6c                100m (1%)     0 (0%)      50Mi (0%)        0 (0%)         7d22h
  kube-system                 kube-proxy-qx648             0 (0%)        0 (0%)      0 (0%)           0 (0%)         7d22h
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests    Limits
  --------           --------    ------
  cpu                200m (2%)   200m (2%)
  memory             114Mi (1%)  128Mi (1%)
  ephemeral-storage  0 (0%)      0 (0%)
  • demo-app-86547cd9f8-w2sq8 の行に、YAML の値が載った
  • Allocated resources の合計が増えた。CPU の Requests は kindnet の 100m と足して 200m、メモリの Requests は 50Mi + 64Mi = 114Mi
  • この Node の Allocatable(CPU 10、メモリ 7.65Gi)から見ると、まだ 2% / 1%

kube-scheduler は、この合計が Allocatable に収まる Node を選ぶ。Allocated resources は宣言の合計で、実際の使用量ではない。

limits の実体を Node の中で見る

limits は、Node の中では何になっているのか。コンテナの中から見る。

Linux カーネルには、プロセスをグループに分けて、グループごとに CPU やメモリの使える量を制限したり記録したりする機能がある。これを cgroup(control group)という。コンテナは 1 つの cgroup に入れられたプロセスで、kubelet はコンテナランタイム(containerd)に limits を渡し、containerd がその cgroup に上限を設定する。cgroup の設定と記録は /sys/fs/cgroup/ の下にファイルとして見え、コンテナの中からは自分の cgroup のファイルが見える。

$ kubectl exec deploy/demo-app -- cat /sys/fs/cgroup/memory.max

# => 134217728

$ kubectl exec deploy/demo-app -- cat /sys/fs/cgroup/cpu.max

# => 20000 100000

$ kubectl exec deploy/demo-app -- cat /sys/fs/cgroup/memory.current

# => 9646080
ファイル値意味
memory.max134217728メモリの上限(バイト)。128 × 1024 × 1024 = 128Mi。YAML の limits.memory がそのままカーネルの上限になっている
cpu.max20000 100000CPU の上限。100000 マイクロ秒(0.1 秒)ごとに 20000 マイクロ秒(0.02 秒)まで使える。0.02 ÷ 0.1 = 0.2 = 200m
memory.current9646080今、実際に使っているメモリ(バイト)。約 9.2Mi

CPU の量は時間で数える。CPU 1 個を 1 秒間ずっと使うと CPU 時間 1 秒で、200m は 1 秒のうち 0.2 秒分だけ使えるということ。カーネルはそれを 0.1 秒ごとの区切りで管理している。

ここで読んだファイルは、limits の実体がカーネルの設定であることを見るためのもので、運用で日常的に見る数字ではない。運用では、使用量と上限を人が読める形で並べる kubectl top(後述)や監視ツールで見る。

どこの数字か今回の値
requests.memoryetcd の Deployment。kube-scheduler が Node を選ぶときの宣言64Mi
limits.memoryetcd の Deployment から、Node の cgroup memory.max へ128Mi(134217728)
実際の使用量Node の cgroup memory.current約 9.2Mi

Node に載らない requests(Pending)

requests.memory を、どの Node にも収まらない 100Gi(Node の 7.65Gi の 13 倍)にして apply する。limits は書いていない。

# demo-deployment-toobig.yaml(resources の部分のみ)
          resources:
            requests:
              cpu: 100m
              memory: 100Gi
$ kubectl apply -f demo-deployment-toobig.yaml

# => deployment.apps/demo-app configured

$ kubectl get pods -o wide

NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE               NOMINATED NODE   READINESS GATES
demo-app-64556b67d6-xg9lf   0/1     Pending   0          5s    <none>       <none>             <none>           <none>
demo-app-86547cd9f8-w2sq8   1/1     Running   0          14m   10.244.1.7   k8s-study-worker   <none>           <none>
  • 新しい Pod demo-app-64556b67d6-xg9lf は Pending(置き先が決まっていない)のまま。IP も NODE も <none>
  • 古い Pod demo-app-86547cd9f8-w2sq8 は Running のまま残っている。Deployment は、新しい Pod が動くまで古い Pod を消さない

Pending の Pod の Events: を見る。

$ kubectl describe pod demo-app-64556b67d6-xg9lf | grep -A 4 'Events:'

Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  79s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint(s), 2 Insufficient memory. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

From の default-scheduler は kube-scheduler。kube-scheduler は、Control Plane で動いているプログラムの 1 つで、置き先の決まっていない Pod を見つけて、どの Node に置くかを決める。決めたら Pod に Node 名を書き込み、その Node の kubelet がコンテナを起動する。ここまでの kubectl get pods -o wide で NODE が埋まっていたのは、kube-scheduler が決めていたから。

Message の部分意味
0/3 nodes are available3 つの Node のうち、置ける Node が 0
1 node(s) had untolerated taint(s)1 つ(k8s-study-control-plane)は、普通の Pod は置かないという印(taint)が付いているので除外
2 Insufficient memory残り 2 つ(Worker)はメモリが足りない。requests の 100Gi が Allocatable の 7.65Gi に収まらない
preemption: ... Preemption is not helpful for scheduling他の Pod を追い出して場所を空けることも検討したが、それでも足りない

kube-scheduler は、Pod の requests が、Node の Allocatable から Allocated resources を引いた残りに収まるかを見て Node を選ぶ。収まる Node がなければ、Pod は Pending のまま待つ。

戻す

requests を元に戻した YAML を apply する。

$ kubectl apply -f demo-deployment-resources.yaml

# => deployment.apps/demo-app configured

$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE
demo-app-86547cd9f8-w2sq8   1/1     Running   0          18m

Pending の Pod が消え、動いていた demo-app-86547cd9f8-w2sq8 だけが残った。Pod の定義が元と同じ内容に戻ったので、Deployment は元の Pod をそのまま使う。

メモリの limits を超える(OOMKilled)

メモリの上限を超えて使おうとしたコンテナがどうなるかを見る。ここでは Deployment ではなく Pod 単体を使い、restartPolicy: Never(終了しても起動し直さない)にして、終了したあとの状態を残す。

コンテナは、ここまでの nginx ではなく、busybox の dd コマンド。nginx はサーバーなので、起動したらアクセスを待ち続けて終了しない。dd はコマンドなので、仕事を 1 回やったら終了する。この節では、メモリを決まった量だけ使うプログラムが欲しいので dd を使う。

nginxdd
何かWeb サーバーデータをコピーするコマンド
起動したらアクセスを待ち続ける。終了しない1 回コピーして、終了する
正常なときの kubectl get podsRunning、READY 1/1Completed、READY 0/1
# memory-ok.yaml
apiVersion: v1
kind: Pod
metadata:
  name: memory-ok
spec:
  restartPolicy: Never
  containers:
    - name: dd
      image: busybox:1.36
      command: ["dd", "if=/dev/zero", "of=/dev/null", "bs=50M", "count=1"]
      resources:
        limits:
          memory: 100Mi
部分意味
commandコンテナが起動したときに実行するコマンド
dd if=/dev/zero of=/dev/null bs=50M count=1/dev/zero(読むと 0 が無限に出てくる特殊なファイル)から 50MB を 1 回読んで、/dev/null(書いたものを捨てる特殊なファイル)に書く。読み込むときにメモリを 50MB 使い、終わったら終了する
bs=50Mdd が使うメモリの量を決めているのはここ
limits.memory: 100Mi上限 100Mi。requests は書いていない

memory-over.yaml は、command の bs=200M だけが違う。dd がメモリを 200MB 使おうとする。上限は同じ 100Mi。

# memory-over.yaml(command の部分のみ)
      command: ["dd", "if=/dev/zero", "of=/dev/null", "bs=200M", "count=1"]

使うメモリの量は、resources ではなく command の bs= で決まる。limits は上限を決めるだけで、実際にどれだけ使うかはコンテナの中のプログラム次第。

$ kubectl apply -f memory-ok.yaml -f memory-over.yaml

pod/memory-ok created
pod/memory-over created

$ kubectl get pods

NAME                        READY   STATUS      RESTARTS   AGE
demo-app-86547cd9f8-w2sq8   1/1     Running     0          20m
memory-ok                   0/1     Completed   0          6s
memory-over                 0/1     OOMKilled   0          6s
  • memory-ok は Completed。50MB 使って、正常に終了した
  • memory-over は OOMKilled。200MB 使おうとして、使用量が上限の 100Mi に達したところで止められた
  • READY は両方 0/1。READY は今動いていて準備できているコンテナの数なので、終了したコンテナは理由が何であれ 0 になる
$ kubectl describe pod memory-over | grep -A 6 'State:'

    State:          Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Sun, 04 Oct 2026 10:26:41 +0900
      Finished:     Sun, 04 Oct 2026 10:26:41 +0900
    Ready:          False
    Restart Count:  0

$ kubectl describe pod memory-ok | grep -A 2 'State:'

    State:          Terminated
      Reason:       Completed
      Exit Code:    0
行意味
State: Terminatedコンテナは終了している
Reason: OOMKilled終了した理由。OOM(Out Of Memory)で Kill された。メモリの上限を超えたので、Linux カーネルがプロセスを強制終了した
Exit Code: 137終了コード。128 + 9(シグナル 9 = SIGKILL、強制終了)

memory-ok は Reason: Completed、Exit Code: 0(正常終了)。

止められたのは、起動前ではなく、起動して動いている最中。memory-over のコンテナも起動して dd が動き始め、メモリを 100Mi まで使ったところで止められている。Pending(requests が Node に収まらず、コンテナが起動しない)とは、起きる場所も判断する側も違う。

いつ誰が見ているもの
Pendingコンテナが起動する前kube-schedulerrequests(宣言)
OOMKilledコンテナが動いている最中Linux カーネル実際に使っている量と limits

requests は自動で入る

$ kubectl describe pod memory-over | grep -A 3 'Limits:'

    Limits:
      memory:  100Mi
    Requests:
      memory:     100Mi

YAML には limits しか書かなかったが、Requests にも 100Mi が入っている。limits だけ書くと、requests には同じ値が自動で入る。逆に requests だけ書いても limits は入らず、上限なしになる。

Deployment なら起動し直される

同じ dd ... bs=200M を Deployment で動かすと、restartPolicy が Always なので起動し直される。

$ kubectl apply -f memory-over-deployment.yaml

# => deployment.apps/memory-over-app created

$ kubectl get pods -w

NAME                               READY   STATUS      RESTARTS     AGE
memory-over-app-57f6d95dd7-f747q   0/1     OOMKilled   1 (4s ago)   4s
memory-over-app-57f6d95dd7-f747q   0/1     CrashLoopBackOff   1 (13s ago)   14s
memory-over-app-57f6d95dd7-f747q   1/1     Running            2 (13s ago)   14s
memory-over-app-57f6d95dd7-f747q   0/1     OOMKilled          2 (13s ago)   14s
memory-over-app-57f6d95dd7-f747q   0/1     CrashLoopBackOff   2 (25s ago)   39s
memory-over-app-57f6d95dd7-f747q   1/1     Running            3 (25s ago)   39s
memory-over-app-57f6d95dd7-f747q   0/1     OOMKilled          3 (25s ago)   39s

OOMKilled → CrashLoopBackOff → Running → OOMKilled を繰り返し、RESTARTS が増えていく。CrashLoopBackOff は、起動し直すまでの待ち時間を延ばしながら待っている状態。

CPU の limits を超えようとすると待たされる

メモリの上限を越えると止められるが、CPU の上限を越えようとしても止められない。待たされるだけ。

CPU を 1 個まるごと使いたがるプログラムを、上限 200m のコンテナで動かす。yes は y を無限に出力し続けるコマンドで、> /dev/null で出力を捨てている。

# cpu-over.yaml
apiVersion: v1
kind: Pod
metadata:
  name: cpu-over
spec:
  containers:
    - name: cpu-hog
      image: busybox:1.36
      command: ["sh", "-c", "yes > /dev/null"]
      resources:
        limits:
          cpu: 200m
$ kubectl apply -f cpu-over.yaml

# => pod/cpu-over created

$ kubectl exec cpu-over -- cat /sys/fs/cgroup/cpu.stat

usage_usec 1993988
user_usec 1964637
system_usec 29351
nice_usec 0
nr_periods 99
nr_throttled 99
throttled_usec 2466384
nr_bursts 0
burst_usec 0

cpu.stat は、このコンテナの cgroup の CPU の使用記録。単位はすべてマイクロ秒。

行意味上の値
nr_periods0.1 秒の区切りが、起動してから何回あったか99 回。起動して約 9.9 秒
nr_throttledそのうち、上限(0.1 秒あたり 0.02 秒)を使い切って待たされた回数99 回。毎回待たされている
usage_usec実際に使えた CPU 時間の合計1,993,988 ≈ 2.0 秒

9.9 秒のうち、使えたのは 2.0 秒。2.0 ÷ 9.9 ≈ 0.2 = 200m。CPU 1 個を使いたいプログラムが、上限どおり 0.2 個分しか使えていない。それでも kubectl get pods は Running のまま。

メモリの limitsCPU の limits
越えようとすると止められる(OOMKilled)待たされる。止められない
kubectl get podsOOMKilled、RESTARTS が増えるRunning のまま。何も変わらない
気づく方法STATUS を見れば分かるkubectl get pods では分からない。cpu.stat の nr_throttled か、kubectl top や監視ツールで見る

使用量を kubectl top で見る

ここまで、実際の使用量は cgroup のファイルを直接読んで見てきた。Kubernetes で使用量を見るコマンドは kubectl top だが、kind のクラスタではそのままでは使えない。

$ kubectl top pods

# => error: Metrics API not available

kubectl top は、metrics-server(各 Node の kubelet から CPU とメモリの使用量を集めて、Metrics API として答えるプログラム)が必要。kind には最初から入っていないので、kube-system namespace に Deployment として入れる。

$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

kind の Node の kubelet は自分で作った証明書を使っているので、そのままでは metrics-server が kubelet に接続できない。証明書の検証を切る設定を足す。metrics-server の README がテスト用としている設定で、本番では使わない。

$ kubectl patch -n kube-system deployment metrics-server --type=json -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

# => deployment.apps/metrics-server patched

metrics-server の Pod が READY 1/1 になってから、最初の使用量を集めるまで 1 分ほどかかる。その間は error: Metrics API not available のまま。

$ kubectl top nodes

NAME                      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
k8s-study-control-plane   189m         1%       1243Mi          15%
k8s-study-worker          37m          0%       389Mi           4%
k8s-study-worker2         28m          0%       318Mi           4%

Allocated resources(宣言の合計)と違って、こちらは今、実際に使っている量。% は Allocatable に対する割合。Control Plane は kube-apiserver や etcd が動いているので、Worker より多い。

cpu-over と demo-app を動かした状態で、Pod の使用量を見る。

$ kubectl top pods

NAME                        CPU(cores)   MEMORY(bytes)
cpu-over                    200m         0Mi
demo-app-86547cd9f8-hdmdx   0m           1Mi
PodCPU(cores)読み方
cpu-over200mCPU 1 個を使いたい yes が、上限 200m ぴったりで止められている。cpu.stat の数字を、人が読める形にしたのがこれ
demo-app-86547cd9f8-hdmdx0mnginx は、アクセスがなければ CPU をほとんど使わない。limits の 200m に対して余裕がある

kubectl top は、cgroup のファイルから読んだ実際の使用量を、Pod や Node ごとに人が読める単位で出すもの。使用量(kubectl top)と上限(kubectl describe pod の Limits:)を並べると、余裕があるかが分かる。

QoS Class

Kubernetes は、requests / limits の書き方だけで Pod を 3 種類に分けている。

QoS Class条件この記事で見たもの
BestEffortどのコンテナにも requests / limits がないdemo-deployment.yaml
Burstable何か書いてあるが、Guaranteed ではないdemo-deployment-resources.yaml(requests < limits)、memory-ok / memory-over(メモリだけ)
Guaranteedすべてのコンテナで、CPU とメモリの両方に requests と limits があり、それぞれ同じ値下の demo-deployment-guaranteed.yaml

requests を limits と同じ値(CPU 200m、メモリ 128Mi)にして apply すると Guaranteed になる。

$ kubectl apply -f demo-deployment-guaranteed.yaml

# => deployment.apps/demo-app created

$ kubectl describe pod -l app=demo-app | grep 'QoS Class'

QoS Class:                   Guaranteed

この分類は、Node のメモリが足りなくなったとき、どの Pod から追い出す(evict する)かに使われる。BestEffort が最初、次に Burstable、Guaranteed が最後。何も宣言していない Pod は、いちばん先に追い出される。この記事では、Node のメモリが足りなくなる状況は作っていない。

消す

$ kubectl delete -f demo-deployment-resources.yaml

# => deployment.apps "demo-app" deleted from default namespace

$ kubectl delete -f memory-ok.yaml -f memory-over.yaml

pod "memory-ok" deleted from default namespace
pod "memory-over" deleted from default namespace

memory-over-deployment.yaml、cpu-over.yaml、demo-deployment-guaranteed.yaml で作ったものも、同じく kubectl delete -f で消す。metrics-server を消すときは、入れたときと同じ URL を kubectl delete -f に渡す。

用語メモ

用語読み方意味
resourcesリソーシズコンテナが使う CPU とメモリの量の設定。Deployment の YAML のコンテナごとに書くフィールド
requestsリクエスツ最低これだけ必要、と宣言する量。kube-scheduler が Node を選ぶときに使う
limitsリミッツこれ以上は使わせない、という上限。Node の cgroup に設定される
mミリCPU の単位。1000m = CPU 1 個分
Ki / Mi / Giキビ / メビ / ギビメモリの単位。1024 倍ずつ
CapacityキャパシティNode が持っている CPU とメモリの量
AllocatableアロケータブルNode のうち、Pod に割り当ててよい量
Allocated resourcesアロケーテッド リソーシズNode に置かれている Pod の requests / limits の合計。宣言の合計で、実際の使用量ではない
kube-schedulerキューブ スケジューラControl Plane で動き、Pod を置く Node を決めるプログラム
PendingペンディングPod の置き先が決まっていない状態
taintテイントNode に付ける、この Pod は置かないという印。Control Plane の Node には既定で付いている
cgroupシーグループLinux カーネルの機能。プロセスをグループに分け、グループごとに CPU やメモリの使える量を制限・記録する
OOMKilledオーオーエム キルドOut Of Memory で Kill された。メモリの上限を超えたプロセスを、Linux カーネルが強制終了したこと
スロットリングスロットリングCPU の上限を使い切ったコンテナが、次の区切りまで待たされること
QoS Classキューオーエス クラスrequests / limits の書き方による Pod の分類。BestEffort / Burstable / Guaranteed
metrics-serverメトリクス サーバー各 Node の kubelet から CPU とメモリの使用量を集めて、kubectl top に答えるプログラム

まとめ

  • requests / limits は、Deployment の YAML のコンテナごとに書く設定。requests は最低これだけ必要、limits はこれ以上は使わせない
  • 書かない Pod は QoS Class: BestEffort。Node に何も予約せず、Node のメモリが足りなくなったとき最初に追い出される
  • requests を書くと Node の Allocated resources に積まれる。kube-scheduler は、この合計が Allocatable に収まる Node を選ぶ。収まる Node がないと、Pod は Pending のまま(Insufficient memory)
  • limits は、Node の中では cgroup の上限(memory.max、cpu.max)になっている。設定するのは kubelet とコンテナランタイム、守らせるのは Linux カーネル
  • メモリの limits を超えると、コンテナのプロセスが強制終了される(OOMKilled、Exit Code 137)。CPU の limits は、超えようとすると待たされるだけで止められない
  • Allocated resources は宣言の合計で、実際の使用量ではない。使用量は metrics-server を入れて kubectl top で見る
  • 単位: CPU は 1000m = 1 個分。メモリは Ki / Mi / Gi(1024 倍ずつ)

参考 URL


[Prev] Probe: Kubernetes でコンテナが動いているかを確かめる

Author

rito

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級