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

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
- 環境
- requests と limits
- Node の大きさを見る
- requests / limits なしで動かす
- requests / limits を付ける
- limits の実体を Node の中で見る
- Node に載らない requests(Pending)
- メモリの limits を超える(OOMKilled)
- CPU の limits を超えようとすると待たされる
- 使用量を kubectl top で見る
- QoS Class
- 消す
- 用語メモ
- 参考 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
requests | limits | |
|---|---|---|
| 意味 | 最低これだけ必要 | これ以上は使わせない |
| 誰が使うか | 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: 10 | CPU 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 Limits | Pod ごとの requests / limits。括弧の % は Allocatable に対する割合 |
Allocated resources | 上の表の合計。この Node で、すでに宣言されている量 |
kindnet-m7w6c(Pod 同士の通信を担当する kind の部品)は、requestsに CPU100m、メモリ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 の分(CPU100m、メモリ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
| 場所 | 意味 |
|---|---|
resources | CPU とメモリの量の設定。コンテナごとに書く |
requests.cpu / requests.memory | 最低これだけ必要、と宣言する量。kube-scheduler が Node を選ぶときに使う |
limits.cpu / limits.memory | これ以上は使わせない、という上限。Node の kubelet がコンテナランタイムに渡す |
requests は limits 以下にする。limits のほうを小さく書くとエラーになる。
CPU の単位
100m の m はミリ(1/1000)。1000m が CPU 1 個分。
| 書き方 | 意味 |
|---|---|
1 | CPU 1 個分(1 コア。仮想マシンなら仮想コア 1 つ) |
500m または 0.5 | CPU 半分 |
100m または 0.1 | CPU 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.max | 134217728 | メモリの上限(バイト)。128 × 1024 × 1024 = 128Mi。YAML の limits.memory がそのままカーネルの上限になっている |
cpu.max | 20000 100000 | CPU の上限。100000 マイクロ秒(0.1 秒)ごとに 20000 マイクロ秒(0.02 秒)まで使える。0.02 ÷ 0.1 = 0.2 = 200m |
memory.current | 9646080 | 今、実際に使っているメモリ(バイト)。約 9.2Mi |
CPU の量は時間で数える。CPU 1 個を 1 秒間ずっと使うと CPU 時間 1 秒で、200m は 1 秒のうち 0.2 秒分だけ使えるということ。カーネルはそれを 0.1 秒ごとの区切りで管理している。
ここで読んだファイルは、limits の実体がカーネルの設定であることを見るためのもので、運用で日常的に見る数字ではない。運用では、使用量と上限を人が読める形で並べる kubectl top(後述)や監視ツールで見る。
| どこの数字か | 今回の値 | |
|---|---|---|
requests.memory | etcd の Deployment。kube-scheduler が Node を選ぶときの宣言 | 64Mi |
limits.memory | etcd の 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 available | 3 つの 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 を使う。
| nginx | dd | |
|---|---|---|
| 何か | Web サーバー | データをコピーするコマンド |
| 起動したら | アクセスを待ち続ける。終了しない | 1 回コピーして、終了する |
正常なときの kubectl get pods | Running、READY 1/1 | Completed、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=50M | dd が使うメモリの量を決めているのはここ |
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-scheduler | requests(宣言) |
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_periods | 0.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 のまま。
メモリの limits | CPU の limits | |
|---|---|---|
| 越えようとすると | 止められる(OOMKilled) | 待たされる。止められない |
kubectl get pods | OOMKilled、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
| Pod | CPU(cores) | 読み方 |
|---|---|---|
cpu-over | 200m | CPU 1 個を使いたい yes が、上限 200m ぴったりで止められている。cpu.stat の数字を、人が読める形にしたのがこれ |
demo-app-86547cd9f8-hdmdx | 0m | nginx は、アクセスがなければ 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
- [official] コンテナのリソース管理 | Kubernetes
- [official] コンテナおよびPodへのメモリーリソースの割り当て | Kubernetes
- [official] コンテナおよびPodへのCPUリソースの割り当て | Kubernetes
- [official] Pod Quality of Serviceクラス | Kubernetes
- [GitHub] kubernetes-sigs/metrics-server

