KUBERNETES资源管理之--资源预留
Kubernetes 的节点可以按照 Capacity
调度。node节点本身除了运行不少驱动 OS 和 Kubernetes 的系统守护进程,默认情况下 pod 能够使用节点全部可用容量, 除非为这些系统守护进程留出资源,否则它们将与 pod 争夺资源并导致节点资源短缺问题。由于某些Pod没有对内存及CPU进行限制,导致Pod在运行过程中所需的内存超过了节点本身的内存(OOM),导致某些进程被Linux系统的OOM killer
机制杀掉,假如被杀掉的进程是系统进程或K8S组件,会导致比较严重的问题,例如导致节点崩溃,使得运行在该节点上的所有Pod状态出现异常。
为了解决上述问题,可以采取如下方法
- 每个节点为系统守护进程预留计算资源(CPU、内存、磁盘空间)
- Pod驱逐:节点资源到达一定使用量,开始驱逐 pod,减轻本节点的压力
- 每个Pod需指定所需资源,限制其可使用的资源上限。
一、资源问题概述
系统资源可分为两类:可压缩资源 (CPU)和不可压缩资源 (memory、storage)。可压缩资源比如CPU超配后,在系统满负荷时会划分时间片分时运行进程,系统整体会变慢(一般不会导致太大的问题)。但不可压缩资源如Memory,当系统内存不足时,就有可能触发系统 OOM;这时候根据 oom score 来确定优先杀死哪个进程,而 oom_score_adj 又是影响 oom score 的重要参数,其值越低,表示 oom 的优先级越低。在worker节点中,进程的 oom_score_adj 如下:
所以,OOM 的优先级如下:
BestEffort Pod > Burstable Pod > 其它进程 > Guaranteed Pod > kubelet/docker 等 > sshd 等
二、资源预留
kubelet的启动配置中有一个Node Allocatable
特性,来为系统守护进程和k8s组件预留计算资源,使得即使节点满负载运行时,也不至于出现pod去和系统守护进程以及k8s组件争抢资源,导致节点挂掉的情况。目前支持对CPU, memory, ephemeral-storage三种资源进行预留。
可分配的节点暴露为 API 中 v1.Node
对象的一部分,也是 CLI 中 kubectl describe node
的一部分。
在 kubelet
中,可以为两类系统守护进程预留资源。
节点可供Pod使用资源总量的计算公式如下:
allocatable = NodeCapacity - [kube-reserved] - [system-reserved] - [eviction-threshold]
参考目前真实的物理环境,如下:
[root@node1 ~]# kubectl describe node node1
Capacity:
cpu: 128
ephemeral-storage: 256378368Ki
hugepages-1Gi: 36Gi
intel.com/intel_sriov_switchdev_xxv710_vf: 64
memory: 527832708Ki
pods: 110
Allocatable:
cpu: 112
ephemeral-storage: 236278303558
hugepages-1Gi: 36Gi
intel.com/intel_sriov_switchdev_xxv710_vf: 64
memory: 448140932Ki
pods: 110
这是我真实环境中的物理总量,cpu120C,内存504G,容器使用存储资源单独在/var目录下,未对存储资源进行限制,使用的默认值。系统中还开启了大页内存以及使用的sriov等资源(此处不做具体讲解,只说cpu,内存的限制使用)
三、kubelet常用配置参数
-
kube预留值
Kubelet 标志: --kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi][,][pid=1000]
Kubelet 标志: --kube-reserved-cgroup=#################
kube-reserved 用来给诸如 kubelet、容器运行时、节点问题监测器等 Kubernetes 系统守护进程记述其资源预留值。 该配置并非用来给以 Pod 形式运行的系统守护进程预留资源。假如组件要是以static pod形式启动的,那并不在这个kube-reserved管理并限制的cgroup中,而是在kubepod这个cgroup中。除了 cpu、内存 和 ephemeral-storage之外,pid 可用来指定为 kubernetes 系统守护进程预留指定数量的进程 ID。kube-reserved-cgroup这个参数用来指定k8s系统组件所使用的cgroup。注意,从 Kubernetes 1.18 版本开始,默认情况下,Kubernetes 已经为每个组件自动分配了一个保留的 cgroup。如果你的版本小于1.18,需要提前把指定的cgroup及其子系统需要预先创建好,kubelet并不会为你自动创建好。
-
system预留值
Kubelet 标志: --system-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi][,][pid=1000]
Kubelet 标志: --system-reserved-cgroup=########################
system-reserved用于为系统守护进程记述其资源预留值例如sshd等服务。system-reserved也应该为kernel 预留内存,因为目前kernel使用的内存并不记在 Kubernetes 的 Pod 上。同时还推荐为用户登录会话预留资源(systemd 体系中的user.slice)。除了cpu、内存和ephemeral-storage之外,pid可用来指定为系统守护进程预留指定数量的进程 ID。如果你的版本小于1.18,需要提前创建system cgroup,否则kubelet启动失败。
-
驱逐阈值
Kubelet 标志:--eviction-hard=[memory.available<500Mi]
######################################
kubelet 的 --eviction-hard 标志用于设置 Kubernetes 节点上的资源耗尽情况下的强制驱逐策略。它支持以下资源阈值:1:memory.available:可用内存的百分比。例如,--eviction-hard=memory.available<100Mi 表示当可用内存小于 100 MiB 时触发驱逐。
2:nodefs.available:可用节点文件系统的百分比。例如,--eviction-hard=nodefs.available<10% 表示当可用节点文件系统空间小于 10% 时触发驱逐。
3:imagefs.available:可用容器镜像文件系统的百分比。例如,--eviction-hard=imagefs.available<15% 表示当可用容器镜像文件系统空间小于 15% 时触发驱逐。
可以使用 <、<=、>、>= 符号来设置不同的阈值条件。############################
请注意,--eviction-hard 的设置可能因 Kubernetes 版本而有所不同。确保参考您所使用的具体版本的文档来了解更多关于资源阈值以及其他相关设置和选项的详细信息。 -
显示保留的cpu列表
Kubelet 标志:--reserved-cpus=0-3
############################33
reserved-cpus旨在为操作系统守护程序和 kubernetes 系统守护程序保留一组明确指定编号的 CPU。
除了上述参数之外还有其他的一些参数可供参考
1:配置 cgroup 驱动 ,驱动通过--cgroup-driver标志配置。
cgroupfs是默认的驱动,在主机上直接操作 cgroup 文件系统以对 cgroup 沙箱进行管理。
systemd是可选的驱动,使用 init 系统支持的资源的瞬时切片管理 cgroup 沙箱。
2:实施节点可分配约束 --enforce-node-allocatable=pods[,][system-reserved][,][kube-reserved]标志配置,调度器将 'Allocatable' 视为 Pod 可用的 capacity(资源容量)。kubelet 默认对 Pod 执行 'Allocatable' 约束。 无论何时,如果所有 Pod 的总用量超过了 'Allocatable',驱逐 Pod 的措施将被执行。可通过设置 kubelet --enforce-node-allocatable 标志值为 pods 控制这个措施。
四、cpu管理策略
默认情况下,kubelet 使用 CFS 配额 来执行 Pod 的 CPU 约束。 当节点上运行了很多 CPU 密集的 Pod 时,工作负载可能会迁移到不同的 CPU 核, 这取决于调度时 Pod 是否被扼制,以及哪些 CPU 核是可用的。 许多工作负载对这种迁移不敏感,因此无需任何干预即可正常工作。
然而,有些工作负载的性能明显地受到 CPU 缓存亲和性以及调度延迟的影响。 对此,kubelet 提供了可选的 CPU 管理策略,来确定节点上的一些分配偏好。
CPU 管理策略通过 kubelet 参数 --cpu-manager-policy
来指定,有以下两种策略可供使用
-
none
: 默认策略,表示现有的调度行为。none策略为默认的CPU亲和方案,不提供操作系统调度器默认行为之外的亲和性策略。 通过 CFS 配额来实现 Guaranteed Pods 和 Burstable Pods 的 CPU 使用限制。
-
static
: 允许为节点上具有某些资源特征的 Pod 赋予增强的 CPU 亲和性和独占性。static策略针对具有整数型 CPU requests的 Guaranteed Pod ,它允许该Pod中的容器独占物理机的CPU资源。这种独占性是使用cpuset cgroup控制器来实现的。
####################
注意: CPU管理器不支持运行时下线和上线 CPUs。如果节点上的在线CPUs集合发生变化,则必须驱逐节点上的Pod,并通过删除kubelet根目录中的状态文件cpu_manager_state来手动重置CPU管理器。#########################
此策略管理一个CPU共享池,该共享池最初包含节点上所有的CPU资源。可独占性CPU资源数量等于节点的CPU总量减去通过kubelet --kube-reserved和--system-reserved参数中预留的CPU资源。从1.17版本开始,可以通过 kubelet --reserved-cpus参数显式地指定CPU预留列表。由--reserved-cpus指定的显式CPU列表优先于由 --kube-reserved和--system-reserved指定的CPU预留。通过这些参数预留的CPU是以'整数'方式,按物理核心 ID升序从初始共享池获取的。 共享池是BestEffort和Burstable Pod运行的CPU集合。 Guaranteed Pod 中的容器,如果声明了'非整数值'的CPU requests,也将运行在共享池的CPU上。只有Guaranteed Pod中,指定了整数型CPU requests 的容器,才会被分配独占CPU资源。########################################
spec:
containers:
- name: nginx
image: nginx该Pod属于BestEffort QoS类型,因为其未指定requests或limits值。所以该容器运行在共享CPU池中。
###################################
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
该Pod属于Burstable QoS类型,因为其资源requests不等于limits,且未指定cpu数量。所以该容器运行在共享CPU池中。###################################
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "100Mi"
cpu: "1"
该Pod属于Burstable QoS类型,因为其资源requests 不等于limits。所以该容器运行在共享 CPU池中。#######################################
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "200Mi"
cpu: "2"
该Pod属于Guaranteed QoS类型,因为其requests值与limits相等。同时容器对CPU资源的限制值是一个大于或等于1的整数值。所以该nginx容器被赋予2个独占CPU。#################################
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "1.5"
requests:
memory: "200Mi"
cpu: "1.5"
该Pod属于Guaranteed QoS类型,因为其requests值与limits相等。但是容器对CPU资源的限制值是一个小数。所以该容器运行在共享CPU池中。#####################################
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"该Pod属于Guaranteed QoS类型,因其指定了limits值,同时当未显式指定时,requests值被设置为与limits值相等。同时容器对CPU资源的限制值是一个大于或等于1的整数值。所以该 nginx容器被赋予2个独占CPU。
CPU 管理器定期通过 CRI 写入资源更新,以保证内存中 CPU 分配与 cgroupfs 一致。 同步频率通过新增的 Kubelet 配置参数 --cpu-manager-reconcile-period
来设置。 如果不指定,默认与 --node-status-update-frequency
的周期相同。
更改 CPU 管理器策略
CPU 管理器策略只能在 kubelet 生成新 Pod 时应用,所以简单地从 "none" 更改为 "static" 将不会对现有的 Pod 起作用。更改节点上的 CPU 管理器策略步骤如下:
1:删除旧的CPU管理器状态文件。文件的路径默认为/var/lib/kubelet/cpu_manager_state。这将清除CPUManager维护的状态,以便新策略设置的 cpu-sets 不会与之冲突。
2:修改kubelet的启动参数
3:重启kubelet
五、NUMA 感知的内存管理器
Kubernetes 内存管理器(Memory Manager)为 Guaranteed
QoS 类 的 Pods 提供可保证的内存(或者内存大页)分配能力。内存管理器使用提示生成协议来为 Pod 生成最合适的 NUMA 亲和性配置。 内存管理器将这类亲和性提示输入给中央管理器(即 Topology Manager)。 基于所给的提示和 Topology Manager(拓扑管理器)的策略设置,Pod 或者会被某节点接受,或者被该节点拒绝。此外,内存管理器还确保 Pod 所请求的内存是从尽量少的 NUMA 节点分配而来。
使用内存管理器的前提条件
- CPU 管理器应该被启用,并且在节点(Node)上要配置合适的 CPU 管理器策略。
- 拓扑管理器要被启用,并且要在节点上配置合适的拓扑管理器策略。
我的环境是1.23.6。默认开启MemoryManager
内存管理策略
内存管理器支持两种策略。通过 kubelet
参数 --memory-manager-policy
来选择哪种策略,如下:
-
none(默认)
默认的策略,并且不会以任何方式影响内存分配。None策略返回默认的拓扑提示信息
-
static
对Guaranteed Pod而言,Static内存管理器策略会返回拓扑提示信息,该信息与内存分配有保障的NUMA节点集合有关,并且内存管理器还通过更新内部的'节点映射'对象来完成内存预留。
对BestEffort或Burstable Pod而言,因为不存在对有保障的内存资源的请求,Static内存管理器策略会返回默认的拓扑提示,并且不会通过内部的'节点映射对象'来预留内存。
内存管理策略配置
前面提到的参数包括 --kube-reserved
、--system-reserved
和 --eviction-threshold
。 这些参数值的综合计作预留内存的总量。为内存管理器而新增加的 --reserved-memory
参数可以将总的预留内存进行划分, 并完成跨 NUMA 节点的预留操作。
标志设置的值是一个按 NUMA 节点的不同内存类型所给的内存预留的值的列表,用逗号分开。内存管理器不会使用这些预留的内存来为容器负载分配内存。
配置方式如下:
--reserved-memory N:memory-type1=value1,memory-type2=value2,...
1:N(整数)- NUMA 节点索引,例如,0,1,2,3,4
2:memory-type(字符串)- 代表内存类型:
memory 表示常规内存
hugepages-2Mi或hugepages-1Gi 表示内存大页
3:value(字符串) 表示预留内存的量,例如 1Gi
######################
举例如下:
--reserved-memory 0:memory=1Gi,hugepages-1Gi=2Gi 在numa0上预留了1G的普通内存和2G的内存大页
如果有多个numa,可以写成如下配置
--reserved-memory 0:memory=1Gi --reserved-memory 1:memory=2Gi
#########################
在设置--reserved-memory时,必须遵守如下公式
sum(reserved-memory(i)) = kube-reserved + system-reserved + eviction-threshold
也就是reserved-memory的值等于kube预留、系统预留以及阈值条件(默认的硬性驱逐阈值是 100MiB)的总和
#########################
当系统中有以下设置时
--kube-reserved=cpu=4,memory=16Gi
--system-reserved=cpu=12,memory=16Gi
--eviction-hard=memory.available<8Gi ##如果此值没有设置,则reserved-memory需要加上默认的100Mi
--memory-manager-policy=Static
那么reserved-memory的值就是kube-reserved + system-reserved + eviction-hard
正确设置为--reserved-memory 0:memory=20Gi --reserved-memory 1:memory=20Gi
k8s中,内存管理器文件位于/var/lib/kubelet/memory_manager_state中
六、实践
上面讲述了需要的知识点,接下来我们按照实际的物理环境来进行配置
-
检查节点的cpu拓扑
lscpu查看系统中cpu数量
[root@node1 ~]# lscpu
架构: x86_64
CPU 运行模式: 32-bit, 64-bit
字节序: Little Endian
CPU: 128
在线 CPU 列表: 0-127 ###总共有128C,并开启了超线程
每个核的线程数: 2
每个座的核数: 32
座: 2
NUMA 节点: 8
厂商 ID: HygonGenuine
BIOS Vendor ID: Chengdu Hygon
CPU 系列: 24
型号: 0
型号名称: Hygon C86 7285 32-core Processor
BIOS Model name: Hygon C86 7285 32-core Processor
步进: 2
CPU MHz: 2461.008
CPU 最大 MHz: 2000.0000
CPU 最小 MHz: 1200.0000
BogoMIPS: 4000.07
虚拟化: AMD-V
L1d 缓存: 32K
L1i 缓存: 64K
L2 缓存: 512K
L3 缓存: 8192K
NUMA 节点0 CPU: 0-7,64-71
NUMA 节点1 CPU: 8-15,72-79
NUMA 节点2 CPU: 16-23,80-87
NUMA 节点3 CPU: 24-31,88-95
NUMA 节点4 CPU: 32-39,96-103
NUMA 节点5 CPU: 40-47,104-111
NUMA 节点6 CPU: 48-55,112-119
NUMA 节点7 CPU: 56-63,120-127###################
查看numa架构
[root@node1 ~]# numactl -H
available: 8 nodes (0-7)
node 0 cpus: 0 1 2 3 4 5 6 7 64 65 66 67 68 69 70 71
node 0 size: 63973 MB
node 0 free: 1335 MB
node 1 cpus: 8 9 10 11 12 13 14 15 72 73 74 75 76 77 78 79
node 1 size: 64441 MB
node 1 free: 54748 MB
node 2 cpus: 16 17 18 19 20 21 22 23 80 81 82 83 84 85 86 87
node 2 size: 64507 MB
node 2 free: 50674 MB
node 3 cpus: 24 25 26 27 28 29 30 31 88 89 90 91 92 93 94 95
node 3 size: 64507 MB
node 3 free: 9211 MB
node 4 cpus: 32 33 34 35 36 37 38 39 96 97 98 99 100 101 102 103
node 4 size: 64507 MB
node 4 free: 28655 MB
node 5 cpus: 40 41 42 43 44 45 46 47 104 105 106 107 108 109 110 111
node 5 size: 64507 MB
node 5 free: 26002 MB
node 6 cpus: 48 49 50 51 52 53 54 55 112 113 114 115 116 117 118 119
node 6 size: 64507 MB
node 6 free: 4099 MB
node 7 cpus: 56 57 58 59 60 61 62 63 120 121 122 123 124 125 126 127
node 7 size: 64507 MB
node 7 free: 53190 MB
node distances:
node 0 1 2 3 4 5 6 7
0: 10 16 16 16 28 28 22 28
1: 16 10 16 16 28 28 28 22
2: 16 16 10 16 22 28 28 28
3: 16 16 16 10 28 22 28 28
4: 28 28 22 28 10 16 16 16
5: 28 28 28 22 16 10 16 16
6: 22 28 28 28 16 16 10 16
7: 28 22 28 28 16 16 16 10
[root@node1 ~]# -
设置预留资源
配置方式有两种,可以修改/var/lib/kubelet/config.yaml文件进行修改,不过不建议采用
此处我们采用另外一种方式,修改kubelet启动服务文件,如下:
1:进入kebelet服务配置目录,有默认的10-kubeadm.conf配置文件
[root@node1 ~]# cd /etc/systemd/system/kubelet.service.d/
[root@node1 kubelet.service.d]# ll
总用量 8
-rw-r--r-- 1 root root 1003 9月 10 14:33 10-kubeadm.conf2:编辑新文件11-kubelet.conf,添加如下内容
[Service]
Environment="KUBELET_ECS_ARGS=--kube-reserved=cpu=4,memory=16Gi --system-reserved=cpu=12,memory=16Gi --eviction-hard=memory.available<8Gi,nodefs.available<10% --cpu-manager-policy=static --reserved-cpus=0-3,64-67,32-35,96-99 --memory-manager-policy=Static --reserved-memory 0:memory=20Gi --reserved-memory 4:memory=20Gi"#####################
我们环境中为kube组件预留了4个cpu和16G内存,为系统进程预留12个cpu和16G内存的资源,阈值条件是当时系统内存少于8G时,执行pod疏散任务。预留的cpu列表是0-3,64-67,32-35,96-99 ##共16个cpu,根据上面的numa架构,位于numa0和numa4上,也别需要注意一点,预留cpu时,物理核一定要和其超线程对应,例如cpu0的超线程是64
reserved-memory的值是16G + 16G + 8G = 40G ###因为预留的cpu位于两个numa上,所以reserved-memory的numa也要与之对应,否则共享池中的pod在访问内存时,会出现跨numa的问题,影响性能
-
重启kubelet
1:首先要删除系统的/var/lib/kubelet/cpu_manager_state和/var/lib/kubelet/memory_manager_state,避免与旧的策略产生冲突
2:加载服务 systemctl daemon-reload
3:重启kubelet服务 systemctl restart kubelet
新的策略文件会重新生成 -
启动pod查看资源使用
1:编辑pod 启动yaml,如下;
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "200Mi"
cpu: "2"2:启动pod,如下:
[root@node1 ~]# kubectl apply -f pod.yaml
pod/nginx created
[root@node1 ~]# vim pod.yaml
[root@node1 ~]#
[root@node1 ~]#
[root@node1 ~]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 55s 172.10.30.91 node3 <none> <none>3:以上得知pod位于node3节点,查看该节点的cpu管理策略文件
[root@node3 ~]# cat /var/lib/kubelet/cpu_manager_state
{"policyName":"static","defaultCpuSet":"0-3,21-23,32-67,85-87,96-127","entries":{"28e92e4f-958d-4f9d-8aae-e2f8557dee9d":{"nginx":"20,84"},"66ce7446-8e92-49d7-8c49-06c9ccf46e6a":{"loam-b":"13-14,77-78"},"9f223236-e84e-4169-8139-e103062d54ee":{"llb":"15-16,79-80"},"c25c6025-cfda-4c87-9413-0627187848c0":{"ipds":"7,19,24-31,71,83,88-95"},"c396c532-70d1-41b0-9993-30b2bd36d7b3":{"necc":"4,8-10,68,72-74"},"c4020329-6fc9-41a3-94c8-1d47a08e48e5":{"eems":"5,69"},"d82d23c7-b3a5-43cf-ba77-df4a2de5b225":{"alms":"6,70"},"e14ad8c9-a58c-4cad-a309-b0be2047c7cb":{"loam-a":"17-18,81-82"},"e4a6664a-d38e-4bcb-a5bd-65027645da14":{"llb":"11-12,75-76"}},"checksum":1874914806}以上可以看到nginx使用的cpu是20,84,结合上面的cpu的拓扑,位于numa2上。
4:查看node3节点的内存管理文件
[root@node3 ~]# cat /var/lib/kubelet/memory_manager_state | jq .
"nginx": [
{
"numaAffinity": [
0 ####nginx使用的内存位于numa0上,和cpu不在一个numa上,如果这个是性能型pod,会影响业务的使用
],
"type": "memory",
"size": 2147483648
}
]############################################
如果要解决pod使用cpu和内存numa不一致问题,有以下方法
1:关闭服务器numa功能,使节点只有一个numa0
2:在kubelet中添加配置参数 --topology-manager-policy=single-numa-node
3:重新加载服务配置,重启kubelet。