Kubernetes實踐技巧:資源預留
ubernetes 的節點可以按照節點的資源容量進行排程,預設情況下 Pod 能夠使用節點全部可用容量。這樣就會造成一個問題,因為節點自己通常運行了不少驅動 OS 和 Kubernetes 的系統守護程序。除非為這些系統守護程序留出資源,否則它們將與 Pod 爭奪資源並導致節點資源短缺問題。
當我們在線上使用 Kubernetes 叢集的時候,如果沒有對節點配置正確的資源預留,我們可以考慮一個場景,由於某個應用無限制的使用節點的 CPU 資源,導致節點上 CPU 使用持續100%執行,而且壓榨到了 kubelet 元件的 CPU 使用,這樣就會導致 kubelet 和 apiserver 的心跳出問題,節點就會出現 Not Ready 狀況了。預設情況下節點 Not Ready 過後,5分鐘後會驅逐應用到其他節點,當這個應用跑到其他節點上的時候同樣100%的使用 CPU,是不是也會把這個節點搞掛掉,同樣的情況繼續下去,也就導致了整個叢集的雪崩,叢集內的節點一個一個的 Not Ready 了,後果是非常嚴重的,或多或少的人遇到過 Kubernetes 叢集雪崩的情況,這個問題也是面試的時候映象詢問的問題。
要解決這個問題就需要為 Kubernetes 叢集配置資源預留,kubelet 暴露了一個名為 Node Allocatable 的特性,有助於為系統守護程序預留計算資源,Kubernetes 也是推薦叢集管理員按照每個節點上的工作負載來配置 Node Allocatable。
本文的操作環境為 Kubernetes V1.17.11 版本,Docker 和 Kubelet 採用的 cgroup 驅動為 systemd。
Node Allocatable
Kubernetes 節點上的 Allocatable 被定義為 Pod 可用計算資源量,排程器不會超額申請 Allocatable,目前支援 CPU, memory 和 ephemeral-storage 這幾個引數。
我們可以通過 kubectl describe node 命令檢視節點可分配資源的資料:
$ kubectl describe node ydzs-node4 ...... Capacity: cpu: 4 ephemeral-storage: 17921Mi hugepages-2Mi: 0 memory: 8008820Ki pods: 110 Allocatable: cpu: 4 ephemeral-storage: 16912377419 hugepages-2Mi: 0 memory: 7906420Ki pods: 110 ......
可以看到其中有 Capacity 與 Allocatable 兩項內容,其中的 Allocatable 就是節點可被分配的資源,我們這裡沒有配置資源預留,所以預設情況下 Capacity 與 Allocatable 的值基本上是一致的。下圖顯示了可分配資源和資源預留之間的關係:
- Kubelet Node Allocatable 用來為 Kube 元件和 System 程序預留資源,從而保證當節點出現滿負荷時也能保證 Kube 和 System 程序有足夠的資源。
- 目前支援 cpu, memory, ephemeral-storage 三種資源預留。
- Node Capacity 是節點的所有硬體資源,kube-reserved 是給 kube 元件預留的資源,system-reserved 是給系統程序預留的資源,eviction-threshold 是 kubelet 驅逐的閾值設定,allocatable 才是真正排程器排程 Pod 時的參考值(保證節點上所有 Pods 的 request 資源不超過Allocatable)。
節點可分配資源的計算方式為:
Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold
配置資源預留
Kube 預留值
首先我們來配置 Kube 預留值,kube-reserved 是為了給諸如 kubelet、容器執行時、node problem detector 等 kubernetes 系統守護程序爭取資源預留。要配置 Kube 預留,需要把 kubelet 的 --kube-reserved-cgroup
標誌的值設定為 kube 守護程序的父控制組。
不過需要注意,如果 --kube-reserved-cgroup
不存在,Kubelet 不會建立它,啟動 Kubelet 將會失敗。
比如我們這裡修改 node-ydzs4 節點的 Kube 資源預留,我們可以直接修改 /var/lib/kubelet/config.yaml
檔案來動態配置 kubelet,新增如下所示的資源預留配置:
apiVersion: kubelet.config.k8s.io/v1beta1
......
enforceNodeAllocatable:
- pods
- kube-reserved # 開啟 kube 資源預留
kubeReserved:
cpu: 500m
memory: 1Gi
ephemeral-storage: 1Gi
kubeReservedCgroup: /kubelet.slice # 指定 kube 資源預留的 cgroup
修改完成後,重啟 kubelet,如果沒有建立上面的 kubelet 的 cgroup,啟動會失敗:
$ systemctl restart kubelet
$ journalctl -u kubelet -f
......
Aug 11 15:04:13 ydzs-node4 kubelet[28843]: F0811 15:04:13.653476 28843 kubelet.go:1380] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": ["kubelet"] cgroup does not exist
上面的提示資訊很明顯,我們指定的 kubelet 這個 cgroup 不存在,但是由於子系統較多,具體是哪一個子系統不存在不好定位,我們可以將 kubelet 的日誌級別調整為 v=4,就可以看到具體丟失的 cgroup 路徑:
$ vi /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--v=4 --cgroup-driver=systemd --network-plugin=cni"
然後再次重啟 kubelet:
$ systemctl daemon-reload
$ systemctl restart kubelet
再次檢視 kubelet 日誌:
$ journalctl -u kubelet -f
......
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.382811 20427 cgroup_manager_linux.go:273] The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice /sys/fs/cgroup/systemd/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice]
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.383002 20427 factory.go:170] Factory "systemd" can handle container "/system.slice/run-docker-netns-db100461211c.mount", but ignoring.
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.383025 20427 manager.go:908] ignoring container "/system.slice/run-docker-netns-db100461211c.mount"
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: F0909 17:57:36.383046 20427 kubelet.go:1381] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": ["kubelet"] cgroup does not exist
注意:systemd 的 cgroup 驅動對應的 cgroup 名稱是以 .slice 結尾的,比如如果你把 cgroup 名稱配置成kubelet.service,那麼對應的建立的 cgroup 名稱應該為kubelet.service.slice`。如果你配置的是 cgroupfs 的驅動,則用配置的值即可。無論哪種方式,通過檢視錯誤日誌都是排查問題最好的方式。
現在可以看到具體的 cgroup 不存在的路徑資訊了:
The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice /sys/fs/cgroup/systemd/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice]
所以要解決這個問題也很簡單,我們只需要建立上面的幾個路徑即可:
$ mkdir -p /sys/fs/cgroup/cpu,cpuacct/kubelet.slice
$ mkdir -p /sys/fs/cgroup/memory/kubelet.slice
$ mkdir -p /sys/fs/cgroup/systemd/kubelet.slice
$ mkdir -p /sys/fs/cgroup/pids/kubelet.slice
$ mkdir -p /sys/fs/cgroup/cpu,cpuacct/kubelet.slice
$ mkdir -p /sys/fs/cgroup/cpuset/kubelet.slice
建立完成後,再次重啟:
$ systemctl restart kubelet
$ journalctl -u kubelet -f
......
Sep 09 17:59:41 ydzs-node4 kubelet[21462]: F0909 17:59:41.291957 21462 kubelet.go:1381] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": failed to set supported cgroup subsystems for cgroup [kubelet]: failed to set config for supported subsystems : failed to write 0 to hugetlb.2MB.limit_in_bytes: open /sys/fs/cgroup/hugetlb/kubelet.slice/hugetlb.2MB.limit_in_bytes: no such file or directory
可以看到還有一個 hugetlb 的 cgroup 路徑不存在,所以繼續建立這個路徑:
$ mkdir -p /sys/fs/cgroup/hugetlb/kubelet.slice
$ systemctl restart kubelet
重啟完成後就可以正常啟動了,啟動完成後我們可以通過檢視 cgroup 裡面的限制資訊校驗是否配置成功,比如我們檢視記憶體的限制資訊:
$ cat /sys/fs/cgroup/memory/kubelet.slice/memory.limit_in_bytes
1073741824 # 1Gi
現在再次檢視節點的資訊:
$ kubectl describe node ydzs-node4
......
Addresses:
InternalIP: 10.151.30.59
Hostname: ydzs-node4
Capacity:
cpu: 4
ephemeral-storage: 17921Mi
hugepages-2Mi: 0
memory: 8008820Ki
pods: 110
Allocatable:
cpu: 3500m
ephemeral-storage: 15838635595
hugepages-2Mi: 0
memory: 6857844Ki
pods: 110
......
可以看到可以分配的 Allocatable 值就變成了 Kube 預留過後的值了,證明我們的 Kube 預留成功了。
系統預留值
我們也可以用同樣的方式為系統配置預留值,system-reserved 用於為諸如 sshd、udev 等系統守護程序爭取資源預留,system-reserved 也應該為 kernel 預留 記憶體,因為目前 kernel 使用的記憶體並不記在 Kubernetes 的 pod 上。但是在執行 system-reserved 預留操作時請加倍小心,因為它可能導致節點上的關鍵系統服務 CPU 資源短缺或因為記憶體不足而被終止,所以如果不是自己非常清楚如何配置,可以不用配置系統預留值。
同樣通過 kubelet 的引數 --system-reserved
配置系統預留值,但是也需要配置 --system-reserved-cgroup
引數為系統程序設定 cgroup。
請注意,如果 --system-reserved-cgroup 不存在,Kubelet 不會建立它,kubelet 會啟動失敗。
驅逐閾值
上面我們還提到可分配的資源還和 kubelet 驅逐的閾值有關。節點級別的記憶體壓力將導致系統記憶體不足,這將影響到整個節點及其上執行的所有 Pod,節點可以暫時離線直到記憶體已經回收為止,我們可以通過配置 kubelet 驅逐閾值來防止系統記憶體不足。驅逐操作只支援記憶體和 ephemeral-storage 兩種不可壓縮資源。當出現記憶體不足時,排程器不會排程新的 Best-Effort QoS Pods 到此節點,當出現磁碟壓力時,排程器不會排程任何新 Pods 到此節點。
我們這裡為 ydzs-node4 節點配置如下所示的硬驅逐閾值:
# /var/lib/kubelet/config.yaml
......
evictionHard: # 配置硬驅逐閾值
memory.available: "300Mi"
nodefs.available: "10%"
enforceNodeAllocatable:
- pods
- kube-reserved
kubeReserved:
cpu: 500m
memory: 1Gi
ephemeral-storage: 1Gi
kubeReservedCgroup: /kubelet.slice
......
我們通過 --eviction-hard
預留一些記憶體後,當節點上的可用記憶體降至保留值以下時,kubelet 將嘗試驅逐 Pod,
$ kubectl describe node ydzs-node4
......
Addresses:
InternalIP: 10.151.30.59
Hostname: ydzs-node4
Capacity:
cpu: 4
ephemeral-storage: 17921Mi
hugepages-2Mi: 0
memory: 8008820Ki
pods: 110
Allocatable:
cpu: 3500m
ephemeral-storage: 15838635595
hugepages-2Mi: 0
memory: 6653044Ki
pods: 110
......
配置生效後再次檢視節點可分配的資源可以看到記憶體減少了,臨時儲存沒有變化是因為硬驅逐的預設值就是 10%。也是符合可分配資源的計算公式的:
Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold
到這裡我們就完成了 Kubernetes 資源預留的配置。