Kubernetes 资源预留

  系统资源可分为两类:可压缩资源(CPU)和不可压缩资源(memory、storage)。可压缩资源比如CPU超配后,在系统满负荷时会划分时间片分时运行进程,系统整体会变慢(一般不会导致太大的问题)。但不可压缩资源如Memory,当系统内存不足时,就有可能触发系统 OOM;这时候根据 oom score 来确定优先杀死哪个进程,而 oom_score_adj 又是影响 oom score 的重要参数,其值越低,表示 oom 的优先级越低。在计算节点中,进程的 oom_score_adj 如下:

Name Score
sshd等(sshd/dmevented / systemd-udevd) -1000
K8S 管理进程(kubelet / docker / journalctl) -999
Guaranteed Pod -998
其它进程(内核 init 进程等) 0
Burstable Pod min(max(2, 1000 – (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
BestEffort Pod 1000

所以,OOM 的优先级如下:

1
BestEffort Pod > Burstable Pod > 其它进程 > Guaranteed Pod > kubelet/docker 等 > sshd 等

  在Kubernetes平台,默认情况下Pod能够使用节点全部可用资源。如果节点上的Pod负载较大,那么这些Pod可能会与节点上的系统守护进程和k8s组件争夺资源并导致节点资源短缺,甚至引发系统OOM,导致某些进程被Linux系统的OOM killer机制杀掉,假如被杀掉的进程是系统进程或K8S组件,会导致比较严重的问题。

Node Allocatable简介

  kubelet的启动配置中有一个Node Allocatable特性,来为系统守护进程和k8s组件预留计算资源,使得即使节点满负载运行时,也不至于出现pod去和系统守护进程以及k8s组件争抢资源,导致节点挂掉的情况。目前支持对CPU, memory, ephemeral-storage三种资源进行预留。kubernetes官方建议根据各个节点的负载情况来具体配置相关参数。

节点计算资源的分配如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 Node Capacity
---------------------------
| kube-reserved |
|-------------------------|
| system-reserved |
|-------------------------|
| eviction-threshold |
|-------------------------|
| |
| allocatable |
| (available for pods) |
| |
| |
---------------------------

其中各个部分的含义如下:

  • Node Capacity:Node的硬件资源总量
  • kube-reserved:给k8s系统进程预留的资源(包括kubelet、container runtime、node problem detector等,但不会给以pod形式起的k8s系统进程预留资源)
  • system-reserved:给linux系统守护进程预留的资源
  • eviction-threshold:通过--eviction-hard参数为节点预留内存,当节点可用内存值低于此值时,kubelet会进行pod的驱逐
  • allocatable:是真正可供节点上Pod使用的容量,kube-scheduler调度Pod时的参考此值(kubectl describe node可以看到,Node上所有Pods的request量不超过Allocatable)

节点可供Pod使用资源总量的计算公式如下:

1
allocatable = [Node Capacity] - [kube-reserved] - [system-reserved] - [eviction-threshold]

从公式可以看出,默认情况下(不设置kube-reserved、system-reserved、eviction-threshold)节点上默认可以让Pod使用的资源总量等于节点的总容量,会导致Pod与系统进程和k8s组件争抢资源的情况发生。

示例场景

下面是一个例子来说明节点Allocatable计算:

  • 节点拥有32Gi memory16 CPUs100Gi 存储。
  • --kube-reserved 设置为 cpu=1,memory=2Gi,ephemeral-storage=1Gi
  • --system-reserved 设置为 cpu=500m,memory=1Gi,ephemeral-storage=1Gi
  • --eviction-hard 设置为 memory.available<500Mi,nodefs.available<10%

在此场景下, Allocatable14.5 CPUs28.5Gi 内存, 88Gi 本地存储。调度器确保该节点上所有pod上的总内存请求不超过28.5Gi,存储也不超过88Gi。当跨pod的总体内存使用量超过28.5Gi时,或者如果整个磁盘使用量超过88Gi时,Kubelet 将驱逐pod。如果节点上的所有进程都尽可能多地消耗CPU,那么pod一起消耗的CPU不能超过 14.5 CPUs

如果不强制执行kube-reserved和/或system-reserved,并且系统守护进程超出了它们的保留,那么当节点内存使用总量大于31.5Gi或存储容量大于90Gi时,kubelet就会驱逐pod。

配置参数

kubelet的启动参数中涉及资源预留的主要有如下几个:

  • --cgroups-per-qos
  • --cgroup-driver
  • --enforce-node-allocatable=pods[,][system-reserved][,][kube-reserved]
  • --kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi]
  • --kube-reserved-cgroup
  • --system-reserved=[cpu=100mi][,][memory=100Mi][,][ephemeral-storage=1Gi]
  • --system-reserved-cgroup
  • --eviction-hard

参数详解

--cgroups-per-qos
可选,默认开启。开启这个参数后,kubelet会将所有的pod创建在kubelet管理的cgroup层次结构下(这样才有了限制所有Pod使用资源总量的基础)。要想启用Node Allocatable特性,这个参数必须开启。

--cgroup-driver
可选。指定kubelet使用的cgroup driver。默认为cgroupfs,还可以是systemd,但是这个值需要和docker runtime所使用的cgroup driver保持一致。

--enforce-node-allocatable
指定kubelet为哪些进程做硬限制,可选的值有:pods,kube-reserved,system-reserve。

这个参数默认开启并指定pods,此时kubelet会为所有pod的总cgroup做资源限制(通过cgroup中的kubepods.limit_in_bytes),限制为公式计算出的allocatable的大小。而用cgroup做了硬限制后,当所有pod使用量达到allocatable后,会有pod被OOM killer机制杀掉,以保证实际使用量不会超过allocatable。(后面会有实验验证)

假如想为系统进程和k8s进程也做cgroup级别的硬限制,还可以在限制列表中再加system-reserved和kube-reserved,同时还要分别加上--kube-reserved-cgroup--system-reserved-cgroup以指定分别限制在哪个cgroup里。

--kube-reserved
指定为k8s系统组件(kubelet、kube-proxy、dockerd等)预留的资源量,如:--kube-reserved=cpu=1,memory=2Gi,ephemeral-storage=1Gi

这里需要注意一点的是这里的kube-reserved只为非pod形式启动的kube组件预留资源,假如组件要是以static pod形式启动的,那并不在这个kube-reserved管理并限制的cgroup中,而是在kubepod这个cgroup中。

--kube-reserved-cgroup
这个参数用来指定k8s系统组件所使用的cgroup。注意,这里指定的cgroup及其子系统需要预先创建好,kubelet并不会为你自动创建好。

--system-reserved
为系统守护进程(sshd, udev等)预留的资源量,如:--system-reserved=cpu=500m,memory=1Gi,ephemeral-storage=1Gi。注意,除了考虑为系统进程预留的量之外,还应该为kernel和用户登录会话预留一些内存。

--system-reserved-cgroup
这个参数用来指定系统守护进程所使用的cgroup。注意,这里指定的cgroup及其子系统需要预先创建好,kubelet并不会为你自动创建好。

--eviction-hard
设置进行pod驱逐的阈值,这个参数只支持内存和磁盘。通过--eviction-hard标志预留一些内存后,当节点上的可用内存降至保留值以下时,kubelet 将会对pod进行驱逐。

实践

限制pod、k8s组件资源

1.将以下内容添加到kubelet的 /var/lib/kubelet/config.yaml 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
....
evictionHard: # 配置硬驱逐阈值
memory.available: "500Mi"
nodefs.available: "10%"
enforceNodeAllocatable:
- pods
- kube-reserved # 开启 kube 资源预留
kubeReserved:
cpu: 500m
memory: 1Gi
ephemeral-storage: 10Gi
kubeReservedCgroup: /kubelet.slice # 指定 kube 资源预留的 cgroup
....

修改完成后,如果 --kube-reserved-cgroup 不存在,Kubelet 不会创建它,启动 Kubelet 将会失败。由于子系统较多,具体是哪一个子系统不存在不好定位,我们可以将 kubelet 的日志级别调整为 --v=4,就可以看到具体丢失的 cgroup 路径:

1
2
$ vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--v=4 --logtostderr=false --log-dir=/var/log/kubernetes/kubelet --cgroup-driver=systemd"

现在可以看到具体的 cgroup 不存在的路径信息了:(kubelet.INFO日志中)

1
The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/hugetlb/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice]

至于如何设置cgroup结构,请参考官方建议

2.为kubelet.slice创建子系统:

1
2
3
4
5
6
7
$ 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
$ mkdir -p /sys/fs/cgroup/hugetlb/kubelet.slice

注意:systemd 的 cgroup 驱动对应的 cgroup 名称是以 .slice 结尾的,比如如果你把 cgroup 名称配置成 kubelet.service,那么对应的创建的 cgroup 名称应该为 kubelet.service.slice。如果你配置的是 cgroupfs 的驱动,则用配置的值即可。无论哪种方式,通过查看错误日志都是排查问题最好的方式。

我们也可以通过查看代码知道需要创建那些子系统:

1
https://github.com/kubernetes/kubernetes/blob/v1.18.10/pkg/kubelet/cm/cgroup_manager_linux.go

3.重启kubelet

验证

1.验证公式计算的allocatable与实际一致
按以上设置节点上所有Pod实际使用的资源总和不会超过:

1
allocatable = capacity - kube-reserved - eviction-hard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Capacity:
cpu: 4
ephemeral-storage: 32488964Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 7976520Ki (约7.6G)
pods: 110
Allocatable:
cpu: 3500m
ephemeral-storage: 28868087349
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 6415944Ki (约6.1G)
pods: 110

根据公式 capacity - kube-reserved - eviction-hard,memory的allocatable的值为 7.6G - 1G - 0.5G = 6.1G ,与Allocatable的值一致。

查看kubelet.service控制组中对内存的限制值memory.limit_in_bytes:

1
2
$ cat /sys/fs/cgroup/memory/kubelet.slice/memory.limit_in_bytes
1073741824(1G)

2.验证公式计算的总使用量限制与实际值一致
查看kubepods控制组中对内存的限制值memory.limit_in_bytes(memory.limit_in_bytes值决定了Node上所有的Pod实际能使用的内存上限)

1
2
$ cat /sys/fs/cgroup/memory/kubepods.slice/memory.limit_in_bytes 
7094214656

根据公式 capacity - kube-reserved,Node上Pod能实际使用的资源上限值为:7.6G - 1G = 6.6G,与实际一致。

限制system系统资源

&emsp;&emsp;我们也可以用同样的方式为系统配置预留值,system-reserved 用于为诸如 sshd、udev 等系统守护进程争取资源预留,system-reserved 也应该为 kernel 预留 内存,因为目前 kernel 使用的内存并不记在 Kubernetes 的 pod 上。但是在执行 system-reserved 预留操作时请加倍小心,因为它可能导致节点上的关键系统服务 CPU 资源短缺或因为内存不足而被终止,所以如果不是自己非常清楚如何配置,可以不用配置系统预留值。
&emsp;&emsp;同样通过 kubelet 的参数 --system-reserved 配置系统预留值,但是也需要配置 --system-reserved-cgroup 参数为系统进程设置 cgroup。
&emsp;&emsp;请注意,如果 --system-reserved-cgroup 不存在,kubelet 不会创建它,kubelet 会启动失败。

建议优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 节点资源预留
enforce-node-allocatable: 'pods'
system-reserved: 'cpu=0.25,memory=200Mi'
kube-reserved: 'cpu=0.25,memory=1500Mi'

# POD驱逐,这个参数只支持内存和磁盘。

## 硬驱逐阈值
### 当节点上的可用资源降至保留值以下时,就会触发强制驱逐。强制驱逐会强制kill掉POD,不会等POD自动退出。
eviction-hard: 'memory.available<300Mi,nodefs.available<10%,imagefs.available<15%,nodefs.inodesFree<5%'

## 软驱逐阈值
### 以下四个参数配套使用,当节点上的可用资源少于这个值时但大于硬驱逐阈值时候,会等待eviction-soft-grace-period设置的时长;
### 等待中每10s检查一次,当最后一次检查还触发了软驱逐阈值就会开始驱逐,驱逐不会直接Kill POD,先发送停止信号给POD,然后等待eviction-max-pod-grace-period设置的时长;
### 在eviction-max-pod-grace-period时长之后,如果POD还未退出则发送强制kill POD"
eviction-soft: 'memory.available<500Mi,nodefs.available<50%,imagefs.available<50%,nodefs.inodesFree<10%'
eviction-soft-grace-period: 'memory.available=1m30s'
eviction-max-pod-grace-period: '30'
eviction-pressure-transition-period: '30s'

Kubernetes 资源预留
https://system51.github.io/2020/01/13/reserve-compute-resources-for-system-daemons/
作者
Mr.Ye
发布于
2020年1月13日
许可协议