Kubernetes资源管理之--资源预留

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liukuan73/article/details/82024085

1. 概述

1.1 问题

系统资源可分为两类:可压缩资源(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 的优先级如下:

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

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

1.2 解决方案

针对这种问题,主要有两种解决方案(两种也可以结合使用):

本篇文章主要介绍如何正确配置资源预留,Pod的驱逐以后介绍。

2. 资源预留简介

2.1 Node Allocatable简介

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

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

 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使用资源总量的计算公式如下:

allocatable = NodeCapacity - [kube-reserved] - [system-reserved] - [eviction-threshold]

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

扫描二维码关注公众号,回复: 2963814 查看本文章

2.2 配置参数

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

  • --cgroups-per-qos
  • --cgroup-driver
  • --cgroup-root
  • --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

2.2.1 参数详解

--cgroups-per-qos

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

--cgroup-driver

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

--cgroup-root

可选。指定给pod使用的根cgroup,容器运行时会尽量将pod的资源限制在这个根cgroup下面。默认为空,即使用容器运行时作为根cgroup。

--enforce-node-allocatable

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

这个参数开启并指定pods后kubelet会为所有pod的总cgroup做资源限制(通过cgroup中的kubepods.limit_in_bytes),限制为公式计算出的allocatable的大小。

之所以调度器可以限制节点上所有创建的pod的request量不会超过allocatable,已可以保证不会有超过allocatable的pod跑在该节点上,这里还要用cgroup再做硬限制的意义可能是:pod可使用的资源是可以大于request的,所以,虽然在调度阶段限制了所有request的总量不会超过allocatable的值,但不能保证真正运行起来后所有pod的资源使用量不会超过allocatable;而用cgroup做了硬限制后,当所有pod使用量达到allocatable后,会有pod被OOM killer机制杀掉,以保证实际使用量不会超过allocatable。(后面会有实验验证)

假如想为系统进程和k8s进程也做cgroup级别的硬限制,还可以在限制列表中再加system-reservedkube-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进行驱逐。

3 实践

根据是否对system和kube做cgroup上的硬限制进行划分,资源预留主要有两种方式:
- 只对所有pod使用的资源总量做cgroup级别的限制,对system和kube不做cgroup级别限制
- 对pod、system、kube均分别做cgroup级别限制

3.1 方式1—只限制pod资源总量

3.1.1 配置

1.将以下内容添加到kubelet的启动参数中:

--enforce-node-allocatable=pods \
--cgroup-driver=cgroupfs \
--kube-reserved=cpu=1,memory=1Gi,ephemeral-storage=10Gi \
--system-reserved=cpu=1,memory=2Gi,ephemeral-storage=10Gi \
--eviction-hard=memory.available<500Mi,nodefs.available<10%

按以上设置,

  • 节点上可供Pod所request的资源总和allocatable计算如下:
    allocatable = capacity - kube-reserved - system-reserved - eviction-hard

  • 节点上所有Pod实际使用的资源总和不会超过:
    capacity - kube-reserved - system-reserved

2.重启kubelet

3.1.2 验证

1.验证公式计算的allocatable与实际一致

通过kubectl describe node查看节点实际capacity及allocatable的值

Capacity:
 cpu:     8
 memory:  32930152Ki(约31.4G)
 pods:    110
Allocatable:
 cpu:     6
 memory:  29272424Ki(约27.9G)
 pods:    110

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

2.验证公式计算的总使用量限制与实际值一致

查看kubepods控制组中对内存的限制值memory.limit_in_bytes(memory.limit_in_bytes值决定了Node上所有的Pod实际能使用的内存上限)

$ cat /sys/fs/cgroup/memory/kubepods/memory.limit_in_bytes 
30499250176(约28.4G)

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

3.2 方式2—同时限制pod、k8s系统组件、linux系统守护进程资源

3.2.1 配置

1.将以下内容添加到kubelet的启动参数中:

--enforce-node-allocatable=pods,kube-reserved,system-reserved \
--cgroup-driver=cgroupfs \
--kube-reserved=cpu=1,memory=1Gi,ephemeral-storage=10Gi \
--kube-reserved-cgroup=/system.slice/kubelet.service \
--system-reserved cpu=1,memory=2Gi,ephemeral-storage=10Gi \
--system-reserved-cgroup=/system.slice \
--eviction-hard=memory.available<500Mi,nodefs.available<10%

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

2.为system.slice创建cpuset子系统:

$ sudo mkdir -p /sys/fs/cgroup/cpuset/system.slice

备注:
可以看到未创建前system.slice这个cgroup是没有cpuset子系统的:

find /sys/fs/cgroup -name system.slice
    /sys/fs/cgroup/devices/system.slice
    /sys/fs/cgroup/memory/system.slice
    /sys/fs/cgroup/blkio/system.slice
    /sys/fs/cgroup/cpu,cpuacct/system.slice
    /sys/fs/cgroup/systemd/system.slice

而kubelet(1.11)启动时会去查看如下图所示这些cgroup子系统是否存在,会报找不到相应cgroup的错误。
这里写图片描述
所以这一步需要手工创建相应cpuset子系统。

3.同样的也需要为kubelet.service创建cpuset子系统:

$ sudo mkdir -p /sys/fs/cgroup/cpuset/system.slice/kubelet.service

4.重启kubelet

3.2.2 验证

这种情况下pod可分配的资源和实际可使用资源理论上与方法一的计算方式和结果是一样的,实际实验中也是一样的,在这里不做赘述。重点验证此情况下是否对k8s系统组件和linux系统守护进程做了cgroup硬限制。

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

$ cat /sys/fs/cgroup/memory/system.slice/memory.limit_in_bytes
2147483648(2G)

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

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

可以看到,方法2这种预留方式,对k8s组件和系统进程也做了cgroup硬限制,当k8s组件和系统组件资源使用量超过这个限制时,会出现这些进程被杀掉的情况。


更多精彩内容,请订阅本人微信公众号:K8SPractice
这里写图片描述

猜你喜欢

转载自blog.csdn.net/liukuan73/article/details/82024085