- 物理機リージョン/AZ
- ハイパーバイザVM exit で捕まえる
- VMゲストOS
- コンテナnamespace / cgroup今ここ
- プロセスアプリ / DB
宣言は望む状態の記録にすぎず、誰かが世界と比べたときにだけ効きます。今回は resources の2つの数を、それぞれ誰がいつ比べるのかを見ます。
まず結論
マニフェストの resources には requests と limits が並んで書かれますが、読む者も読む時点も違います。requests はスケジューラが Pod を置くときに合計で足し合わせる申告で、使用量ではありません。limits はカーネルが、動いている間に cgroup を通して強制します。
だから CPU をほとんど使わないコンテナでも、申告の合計が入りきらなければ Pending のまま置かれません。宣言が同じ1ファイルでも、それを世界と比べる者は1人ではありません。
割り当て可能な CPU が 10 のノード1台に、`requests.cpu: "4"` の Pod を3つ持つ Deployment を apply します。中身は sleep するだけで、CPU はほとんど使いません。どうなるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
申告を足すのはスケジューラで、置くときだけ
resources の2つの数は、同じ行に並んでいるので同じ種類の数に見えます。公式文書の冒頭は、読む者を分けて書いています。
When you specify the resource request for containers in a Pod, the kube-scheduler uses this information to decide which node to place the Pod on. When you specify a resource limit for a container, the kubelet enforces those limits so that the running container is not allowed to use more of that resource than the limit you set.
requests を使うのは kube-scheduler で、使う場面は「どのノードに置くか」を決めるとき。スケジューラが比べるのは使用量ではなく申告の合計です。同じ文書はこの直後に、kubelet も request の分をそのコンテナのために確保する(The kubelet also reserves at least the request amount)と続けています。置いた後も requests は無関係になるわけではありません。
The scheduler ensures that, for each resource type, the sum of the resource requests of the scheduled containers is less than the capacity of the node.
しかも、ノードが暇でも断ると明記されています。
Note that although actual memory or CPU resource usage on nodes is very low, the scheduler still refuses to place a Pod on a node if the capacity check fails.
理由は、後で使用量が増えたとき(文書の例では日次のピーク)に足りなくならないように、です。requests は予約で、予約は使っていなくても席を取ります。 逆に、置かれた後は it's possible (and allowed) for a container to use more resource than its request とも書かれていて、requests は上限でもありません。
一方 limits は、別の者が別の時点で使います。
Limits are a different story. Both cpu and memory limits are applied by the kubelet (and container runtime), and are ultimately enforced by the kernel.
- ・スケジューラは使用量を見ない。カーネルは requests の合計を見ない
- ・入らなければ Pending のまま、カーネルの出番は来ない
使っていないのに、置けない
実演は k3s rancher/k3s:v1.37.0-k3s1 の単一ノード(kernel 6.10.14-linuxkit arm64、cgroup v2)です。ノードの allocatable は cpu 10。ここに requests.cpu: "4"、replicas 3、中身は sleep 3600 だけの Deployment big を apply しました。
deployment.apps/big createdNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESbig-7bbcd886b4-7b9xn 0/1 Pending 0 27s <none> <none> <none> <none>big-7bbcd886b4-hwtwt 1/1 Running 0 27s 10.42.0.28 a8a2e58ff591 <none> <none>big-7bbcd886b4-xlhfc 1/1 Running 0 27s 10.42.0.27 a8a2e58ff591 <none> <none>--- pod/big-7bbcd886b4-7b9xnLAST REASON MSG<nil> FailedScheduling 0/1 nodes are available: 1 Insufficient cpu. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
apply は created で返っています。記録はされたが、3つ目はどのノードにも置かれていません(NODE が <none>)。理由は Insufficient cpu。同じ時点のノードの帳簿がこれです。
# 実演前Resource Requests Limitscpu 100m (1%) 0 (0%)# 実演中Resource Requests Limitscpu 8200m (82%) 100m (1%)
8200m は 4000m×2 と、実演前から載っていた 100m と、同時に動かした cpu-demo(後で使う、limits.cpu 100m だけを書いた Pod)の 100m の和です。cpu-demo は requests を書いていませんが、limit だけを書き、受け付け時に既定の request を入れる仕組みも無いときは、Kubernetes が limit を request に写すと公式文書にあります。3つ目の 4000m を足すと 12200m で 10000m を超えるので、容量の確認に落ちます。
metrics-server を外した環境なので kubectl top node は error: Metrics API not available で、使用量の数値は取れていません。ただ Running の2つは sleep するだけのコンテナです。82% は予約の割合で、CPU が 82% 忙しいという意味ではありません。
実演の3つ目の Pod に出た `0/1 nodes are available: 1 Insufficient cpu.` は、何が足りなかったことを示していますか。
上限を守らせるのはカーネルで、動いている間ずっと
limits の側は、スケジューラが Pod を置いた後に効き始めます。kubelet とランタイムが cgroup に数を書き、あとはカーネルの仕事です。文書は CPU とメモリで書き分けています。
cpu limits are enforced by CPU throttling. When a container approaches its cpu limit, the kernel will restrict access to the CPU corresponding to the container's limit.
memory limits are enforced by the kernel with out of memory (OOM) kills. When a container uses more than its memory limit, the kernel may terminate it. However, terminations only happen when the kernel detects memory pressure. Thus, a container that over allocates memory may not be immediately killed.
メモリ側の may not be immediately killed を落とすと「超えた瞬間に殺される」と言い過ぎになります。cgroup のファイルの読み方と、絞られることと殺されることの違いは第8期『cgroup で資源を配る』で扱いました。ここで足すのは「Kubernetes の宣言が、その cgroup の数に翻訳される」ことです。
もう1人、比べる者がいます。ノードが逼迫したときに Pod を追い出す kubelet です。ここで公式文書の2ページは字面が食い違います。
100m は cgroup の数になり、32Mi は再起動の繰り返しになる
同じ実行で、limits.cpu: 100m の cpu-demo(シェルの無限ループ)と、limits.memory: 32Mi の mem-demo(約1億バイトをシェル変数に読み込む)を動かしました。
=== cpu-demo: cgroup files as seen inside the container10000 100000nr_periods 201nr_throttled 193nr_periods 255nr_throttled 246=== mem-demoNAME READY STATUS RESTARTS AGEmem-demo 0/1 OOMKilled 2 (24s ago) 26s
limits.cpu: 100m が cpu.max の 10000 100000 になっています。周期 100000 のうち 10000、つまり 0.1 CPU です。nr_throttled は nr_periods に迫り、ほぼ毎周期で絞られています(この数は実行ごとに変わり、別の回では 197 と 188 でした)。
mem-demo は OOMKilled で、RESTARTS 2。記録では 2 回とも exitCode 137、reason OOMKilled でした。殺したのはカーネルで、起こし直したのは kubelet です。文書は Unlike pod eviction, if a container is OOM killed, the kubelet can restart it based on its restartPolicy. と書き分けています。Pod は同じノードにいて、スケジューラは一度も呼ばれていません。
`limits.memory: 32Mi` の mem-demo は `OOMKilled` と表示され、RESTARTS が 2 でした。殺した者と起こし直した者の組み合わせとして正しいのはどれですか。
requests.cpu: 4 の Pod が3つ
スケジューラが置くときに、ノードの requests の合計と容量を比べます。中身は sleep だけでも、3つ目は Insufficient cpu で Pending のまま。カーネルの出番は来ません。
比べた者 kube-scheduler時点 置くとき比べた数 requests の合計 12200m と容量 10000m結果 3つ目は Pending、コンテナは作られない
limits.cpu: 100m と limits.memory: 32Mi
Pod は置かれた後、kubelet とランタイムが cgroup に数を書き、カーネルが動いている間に比べます。CPU は周期ごとに絞られ、メモリは OOM kill と kubelet による再起動を繰り返しました。
比べた者 カーネル(cgroup)時点 動いている間ずっと比べた数 cpu.max 10000 100000 / メモリの上限結果 CPU は絞られ、mem-demo は OOMKilled と再起動
片方は置けずに止まり、片方は動いてから絞られたり殺されたりしました。同じ resources の数なのに、何が違ったのでしょうか。
それぞれの数を「誰が」「いつ」読んだかを、2例で並べてみてください。
この章のまとめ
- requests は kube-scheduler が置くときに、ノードごとの合計として 容量と比べる申告。使用量ではない。使用量が低くても、確認に落ちれば置かない。
- 実演: allocatable cpu 10 のノードで
requests.cpu: "4"を3つ。sleep だけでも3つ目はInsufficient cpuで Pending。帳簿は 8200m (82%) で、これは予約の割合。apply はcreatedで返っていた。 - limits は kubelet とランタイムが cgroup に書き、カーネルが動いている間に強制する。
limits.cpu: 100mはcpu.maxの10000 100000になった。 - メモリの limit 超過は
may not be immediately killed。OOM kill したのはカーネル、再起動したのは kubelet。 - 追い出しの順は、eviction のページ(QoS を使わない、鍵は requests 超過・Priority・超過量)と Pod QoS のページ(BestEffort から順に)の両方が現行。requests はここでも読まれる。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
`limits.cpu: 100m` だけを書いた Pod で、コンテナ内の `/sys/fs/cgroup/cpu.max` を読むと何が見えましたか。
用語集(4語)
- requests
- コンテナが必要とする資源量の申告。kube-scheduler が配置先を決めるときに使い、ノードごとの合計を容量未満に保つ。使用量の上限ではない。
- limits
- コンテナが使える資源量の上限。kubelet とランタイムが cgroup に書き、最終的にカーネルが強制する。CPU は絞られ、メモリは OOM kill になりうる。
- Pending
- Pod は受け付けられたが、コンテナがまだ動く準備を終えていない段階。置き先を待つ時間とイメージの取得時間を含む。requests の合計が入らない Pod はここに留まる。
- eviction(追い出し)
- ノードが逼迫したとき kubelet が Pod を終わらせること。カーネルの OOM kill とは別の者による別の仕組み。