1. 程式人生 > 其它 >Kubernetes實踐技巧:資源預留

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 資源預留的配置。