前幾天我們看了 node 如何管理 CPU, memory 等硬體資源,接著來看看 pods 如何分配這些資源吧
在 nodes 上的硬體資源,預設情況下 pods 都可以無限使用,但這樣若是有個 pods 佔用了 node 上所有的資源的話,會導致整個 node 上面的服務資源吃緊,因此 k8s 設計了一套機制可以避免此情況發生
分別是配置 request/limit (在 container 層),看看官方提供的範例:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: qos-demo-ctr
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
request
(請求資源量):當 pods 被部署時,會檢查 nodes 是否有足夠的 request 資源,若足夠才會排程到該 nodes 上,且會把 nodes 上可分配的資源吃掉一部分limit
(上限量):顧名思義,就是配置此 container 最多能吃到多少資源,當觸發上限時會依照資源的不同有相應的動作
memory
:kubelet 會直接觸發 OOMKill 導致 container 重啟除了基本的 CPU, memory 之外,若 nodes 上有定義其他資源,也可透過此機制限制 container 的用量 (e.g. GPU)
在執行 kubectl describe node
指令時,也可以看到 nodes 上相對應還可以使用的資源量,且要注意的是 request 無法被超出使用 (最高為 100%),但 limit 是可以超過 100% 的 (但不建議)
❯ k describe no k8s-master1
...
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 950m (47%) 0 (0%)
memory 250Mi (8%) 340Mi (12%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
當某個 pods 持續卡在 pending 狀態時,有可能就是因為 request 的值任一的 node 都無法滿足導致,通常都可以透過
kubectl describe pods
的 event 欄位看到原因。
https://kubernetes.io/docs/tasks/administer-cluster/extended-resource-node/
因為 k8s 本身並無法感知到自訂資源的多寡,因此目前有以下限制:
假如說有某個 worker node 具有較高讀寫效能的磁碟,就可以透過自定義的方式加在 nodes 上,如下:
# 會占用 terminal 並開啟 8081 port
kubectl proxy
# 開啟另一個視窗
# 在 k8s-master3 上加上 example.com/special-storage 的資源
# ~1 是 / 的符號寫法
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1special-storage", "value": "40Gi"}]' \
http://localhost:8001/api/v1/nodes/k8s-master3/status
加上之後,使用 kubectl describe 查看:
> k describe no k8s-master3
...
Capacity:
cpu: 2
ephemeral-storage: 18596Mi
example.com/special-storage: 40Gi
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3960992Ki
pods: 110
Allocatable:
cpu: 2
ephemeral-storage: 17549387338
example.com/special-storage: 40Gi
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3858592Ki
pods: 110
...
接著建立一個 pods 測試,先故意使用超過 40Gi:
# test.yaml
apiVersion: v1
kind: Pod
metadata:
name: special-storage
namespace: default
spec:
containers:
- name: special-storage
image: nginx
resources:
requests:
example.com/special-storage: 100Gi
limits:
example.com/special-storage: 100Gi
❯ k apply -f test.yaml
❯ k get po
special-storage 0/1 Pending 0 2s
❯ k describe po special-storage
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 118s default-scheduler 0/3 nodes are available: 3 Insufficient example.com/special-storage. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod
因為資源不夠的關係,因此無法被排程上任何的 nodes
修改成為 20Gi 之後,再重新部署:
...
requests:
example.com/special-storage: 20Gi
limits:
example.com/special-storage: 20Gi
...
❯ k apply -f test.yaml --force
❯ k get po
special-storage 1/1 Running 0 44s
接著檢查 k8s-master3 上的資源,確實被佔用了 20Gi 的資源:
❯ k describe no k8s-master3
...
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 750m (37%) 0 (0%)
memory 110Mi (2%) 0 (0%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
example.com/special-storage 20Gi 20Gi
刪除自訂資源:
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "remove", "path": "/status/capacity/example.com~1special-storage"}]' \
http://localhost:8001/api/v1/nodes/k8s-master3/status
pods 會根據 request/limit 的配置排列組合,分成三種 QoS classes,由優先度高到低分別為:
Guaranteed
:pods 內每個 containers 都有配置 CPU/memory limit + request,且兩者一致
Burstable
:不符合 Guaranteed 條件,且至少一個 container 有配置 CPU or memory limit/requestBestEffort
:pods 內完全沒有 container 有配置 CPU/memory limits + requests這三個條件會直接影響到前幾天討論的驅逐優先度,當資源不足出現徵用時,kubelet 會優先挑 BestEffort
的 pods 開刀
這個狀態是 k8s 自行會去以照上述的情況加入到 .status.qosClass
中的,如 apiserevr 的例子:
k get po -n kube-system kube-apiserver-k8s-master1 -o yaml | grep qos
qosClass: Burstable
當 yaml 內的 container 僅定義了 limit 但沒有配置 request 時,k8s 將會自動的把 limit 的值套用於 request,CPU 或 memory 都一樣。
在 container 中控制能使用多少 memory 是透過 cgroup 機制做到的:
memory.max
值,若該 container 使用超過時就會觸發 OOMkill
關於
memory.min
的配置,本來依照上方 request/limit 的相對關係,似乎 request 的值就會被配置為memory.min
相當合理
但實際上測試時,會發現無論 request 配置多少,memory.min
都會被配置為 0
因此稍微查找了一下資料,此功能是在 2020/3 月提案的,編號為 Keps-2570,且此功能在 v1.22 時進入 alpha 階段,但直到目前的 v1.31 版本仍然不是預設開啟功能
在 github 討論上會發現前幾周此問題才有人發布討論,說目前啟用此功能的情況下,可能會導致 container 的限制行為,會比不限制還糟糕,因此此功能目前似乎短期內不會有什麼進展 ~~
ref: https://github.com/kubernetes/enhancements/issues/2570
cgroupv2 的相關檔案會放在 /sys/fs/cgroup
目錄上,而 kubelet 會在此目錄內建立一個 kubepods.slice,裡面又根據 besteffort 與 burstable 分成不同目錄,會根據不同的 pods UID 分別放
先根據官方的 memory 範例建立兩個 pods,一個有配置 limit/request 一個沒有:
# memory.yaml
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
requests:
memory: "100Mi"
limits:
memory: "200Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
---
apiVersion: v1
kind: Pod
metadata:
name: memory-demo-no-limit-request
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources: {}
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
透過此 yaml 建立 pods 並確認位置:
❯ k apply -f memory.yaml
❯ k get po -n mem-example -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
memory-demo 1/1 Running 0 6h53m 10.0.1.150 k8s-master2 <none> <none>
memory-demo-no-limit-request 1/1 Running 0 6h37m 10.0.1.110 k8s-master2 <none> <none>
❯ k get po -n mem-example -o yaml | grep uid
uid: 1be9ad71-0451-4d1e-bfd4-585a761e134c
uid: 63da18ec-47b3-4761-bfa5-5dc1066fed4d
依順序來看,uid: 1be9 為有配置 request/limit 的 pods, 而 63da 開頭的則沒有特別配置
接著到 k8s-master2 主機上查看 cgroupv2 的相關設定:
# 查看有限制的情況,根據 uid 1be9 去搜尋
➜ ~ cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod1be9ad71_0451_4d1e_bfd4_585a761e134c.slice/memory.max
209715200
➜ ~ cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod1be9ad71_0451_4d1e_bfd4_585a761e134c.slice/memory.min
0
# 查看未限制的情況
➜ ~ cat /sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod63da18ec_47b3_4761_bfa5_5dc1066fed4d.slice/memory.max
max
➜ ~ cat /sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod63da18ec_47b3_4761_bfa5_5dc1066fed4d.slice/memory.min
0
可發現當 memory limit 配置為 200Mi 時,memory.max 配置為 209715200 (/1024/1024 = 200),也就是 200 MiB
當未配置 limit 時,則被配置為 max,意即不做限制
某些行為是與 Qos class 無關的,比如:
preempt… 明天來看看好了,有點不是很理解這個行為 ~
透過定義 CPU/memory 的 request/limit,k8s 就會大致上自動分配 pods 的等級,讓內部的機制知道哪些 pods 可以優先被排除
而透過自定義的資源,則可以針對 nodes 的特性打上一些特殊的標籤,讓有特定需求的 pods 使用
最後關於 kube-scheduler 的 preempt 機制還不是很了解,明天再來看看吧 ~~
https://kubernetes.io/docs/tasks/administer-cluster/extended-resource-node/
https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/
https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#besteffort
https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2570-memory-qos/#readme