Kubernetes下pod控制组管理解析
开始之前我们先了解下
Kubernetes QoS概念
在Kubernetes里面,将资源分成不同的QoS类别,并且通过pod里面的资源定义来区分pod对于平台提供的资源保障的SLA等级:
Guaranteed:pod中每个容器都为CPU和内存设置了相同的requests和limits,此类pod具有最高优先级Burstable: 至少有一个容器设置了CPU或内存的requests属性,但不满足Guaranteed类别要求的pod,它们具有中等优先级BestEffort: 未为任何一个容器设置requests和limits属性的pod,它们的优先级为最低级别
针对不同的优先级的业务,在资源紧张或者超出的时候会有不同的处理策略。
同时,针对CPU和内存两类不同类型的资源,一种是可压缩的,一种是不可压缩的,所以资源的分配和调控策略也会有很大区别。
CPU资源紧缺时,如果节点处于超卖状态,则会根据各自的requests配置,按比例分配CPU时间片,而内存资源紧缺时需要内核的oom killer进行管控,
Kubernetes负责为OOM killer提供管控依据:
BestEfford类容器由于没有要求系统供任何级别的资源保证,将最先被终止;但是在资源不紧张时,它们能尽可能多地占用资源,实现资源的复用和部署密度的提高- 如果
BestEfford类容器都已经终止,Burstable中等优先级的的pod将被终止 Guaranteed类容器拥有最高优先级,只有在内存资源使用超出limits的时候或者节点OOM时得分最高,才会被终止;
OOM得分主要根据QoS类和容器的requests内存占机器总内存比来计算:

OOM得分越高,该进程的优先级越低,越容易被终止;
根据公式,Burstable优先级的pod中,requests内存申请越多,越容易在OOM的时候被终止。
pod的cpu控制组解析
首先我们先看下pod的控制组层级
$ ls -l /sys/fs/cgroup/cpu/kubepods.slice
total 0
-rw-r--r-- 1 root root 0 Aug 23 16:32 cgroup.clone_children
-rw-r--r-- 1 root root 0 Aug 23 16:32 cgroup.procs
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.stat
-rw-r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage_all
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Aug 23 16:32 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Aug 23 16:32 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Aug 23 16:32 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Aug 23 16:32 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Aug 23 16:32 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Sep 1 17:38 cpu.shares
-r--r--r-- 1 root root 0 Aug 23 16:32 cpu.stat
drwxr-xr-x 55 root root 0 Aug 23 16:32 kubepods-besteffort.slice
drwxr-xr-x 51 root root 0 Aug 23 16:32 kubepods-burstable.slice
drwxr-xr-x 4 root root 0 Aug 23 16:54 kubepods-pod934b0aa2_1d1b_4a81_bfcf_89c4beef899e.slice
drwxr-xr-x 4 root root 0 Aug 23 16:39 kubepods-podca849e84_aa86_4402_bf31_e7e73faa77fe.slice
-rw-r--r-- 1 root root 0 Aug 23 16:32 notify_on_release
-rw-r--r-- 1 root root 0 Aug 23 16:32 tasks
其中kubepods-besteffort.slice存放besteffort类型pod配置,kubepods-burstable.slice存放burstable类型pod配置。
kubepods-pod934b0aa2_1d1b_4a81_bfcf_89c4beef899e.slice、kubepods-podca849e84_aa86_4402_bf31_e7e73faa77fe.slice则为Guaranteed类型pod
为了更好的解释说明,我们创建一个新的Guaranteed类型的pod用于测试:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 1
memory: 1Gi
EOF
再次查看
/sys/fs/cgroup/cpu/kubepods.slice下,发现新增了一个kubepods-podf56bf66f_3efb_4c80_8818_37de69ee5b72.slice目录名称解析
kubepods-podf56bf66f_3efb_4c80_8818_37de69ee5b72.slice这个名称是怎么命名的呢?
命名格式为:kubepods-pod<pod uid>.slice,并且会将uid中-转换为_
$ kubectl get pod nginx-demo -o yaml|grep uid
uid: f56bf66f-3efb-4c80-8818-37de69ee5b72
- 目录解析
$ ls -l /sys/fs/cgroup/cpu/kubepods.slice/kubepods-podf56bf66f_3efb_4c80_8818_37de69ee5b72.slice
-rw-r--r-- 1 root root 0 Nov 17 11:23 cgroup.clone_children
-rw-r--r-- 1 root root 0 Nov 17 11:23 cgroup.procs
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.stat
-rw-r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage_all
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Nov 17 11:23 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Nov 17 11:23 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Nov 17 11:23 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Nov 17 11:23 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Nov 17 11:23 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Nov 17 11:23 cpu.shares
-r--r--r-- 1 root root 0 Nov 17 11:23 cpu.stat
drwxr-xr-x 2 root root 0 Nov 17 11:23 docker-08974ffd61043b34e4cd5710d5446eb423c6371afb4c9d106e608f08cc1182a3.scope
drwxr-xr-x 2 root root 0 Nov 17 11:24 docker-d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a.scope
-rw-r--r-- 1 root root 0 Nov 17 11:23 notify_on_release
-rw-r--r-- 1 root root 0 Nov 17 11:23 tasks
我们发现怎么有两个容器呢?(docker-08974ffd61043b34e4cd5710d5446eb423c6371afb4c9d106e608f08cc1182a3.scope、docker-d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a.scope)
其实是业务容器 + infra沙箱容器,并且命名格式遵循:docker-<container id>.scope
$ docker ps|grep nginx
d33dc12340fd nginx "/docker-entrypoint.…" 7 minutes ago Up 7 minutes k8s_nginx_nginx-demo_default_f56bf66f-3efb-4c80-8818-37de69ee5b72_0
08974ffd6104 harbor.chs.neusoft.com/kubesphere/pause:3.2 "/pause" 8 minutes ago Up 8 minutes k8s_POD_nginx-demo_default_f56bf66f-3efb-4c80-8818-37de69ee5b72_0
我们可根据以下命令获取业务容器id:
$ kubectl get pod nginx-demo -o yaml|grep containerID
- containerID: docker://d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a
- 业务容器
cgroup解析
$ cd /sys/fs/cgroup/cpu/kubepods.slice/kubepods-podf56bf66f_3efb_4c80_8818_37de69ee5b72.slice/docker-d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a.scope
$ ls -l
total 0
-rw-r--r-- 1 root root 0 Nov 17 11:24 cgroup.clone_children
-rw-r--r-- 1 root root 0 Nov 17 11:24 cgroup.procs
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.stat
-rw-r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage_all
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Nov 17 11:24 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Nov 17 11:24 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Nov 17 11:24 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Nov 17 11:24 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Nov 17 11:24 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Nov 17 11:24 cpu.shares
-r--r--r-- 1 root root 0 Nov 17 11:24 cpu.stat
-rw-r--r-- 1 root root 0 Nov 17 11:24 notify_on_release
-rw-r--r-- 1 root root 0 Nov 17 11:24 tasks
我们上述对pod配额的定义为:
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 1
memory: 1Gi
其实等同于以以下方式启动docker容器:
$ docker run --rm -dt --cpu-shares=1024 --cpu-quota=1024 --memory=1g nginx
我们可以看下docker容器的配额:
$ docker inspect d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a -f {{.HostConfig.CpuShares}}
1024
$ docker inspect d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a -f {{.HostConfig.CpuQuota}}
100000
$ docker inspect d33dc12340fd32b35148293c21f84dab14f2274046056bbeef9e9666d1d0dc2a -f {{.HostConfig.CpuPeriod}}
100000
.HostConfig.CpuShares对应控制内的cpu.shares文件内容
.HostConfig.CpuPeriod对应控制内的cpu.cpu.cfs_period_us文件内容
.HostConfig.CpuQuota对应控制内的cpu.cfs_quota_us文件内容
并且我们发现k8s基于pod管理控制组(同一pod内的容器所属同一控制组)
"CgroupParent": "kubepods-podf56bf66f_3efb_4c80_8818_37de69ee5b72.slice",
我们可以得出记录:k8s通过控制组的cpu.shares、cpu.cpu.cfs_period_us、cpu.cfs_quota_us配置,达到限制CPU的目的。
那么这三个文件是用来干嘛的?
cpu.shares解析
cpu.shares用来设置CPU的相对值,并且是针对所有的CPU(内核),默认值是1024等同于一个cpu核心。CPU Shares将每个核心划分为1024个片,并保证每个进程将按比例获得这些片的份额。如果有1024个片(即1核),并且两个进程设置cpu.shares均为1024,那么这两个进程中每个进程将获得大约一半的cpu可用时间。
当系统中有两个cgroup,分别是A和B,A的shares值是1024,B 的shares值是512,
那么A将获得1024/(1024+512)=66%的CPU资源,而B将获得33%的CPU资源。shares有两个特点:
- 如果
A不忙,没有使用到66%的CPU时间,那么剩余的CPU时间将会被系统分配给B,即B的CPU使用率可以超过33%。 - 如果添加了一个新的
cgroup C,且它的shares值是1024,那么A的限额变成了1024/(1024+512+1024)=40%,B的变成了20%。
从上面两个特点可以看出:
在闲的时候,shares基本上不起作用,只有在CPU忙的时候起作用,这是一个优点。
由于shares是一个绝对值,需要和其它cgroup的值进行比较才能得到自己的相对限额,而在一个部署很多容器的机器上,cgroup的数量是变化的,所以这个限额也是变化的,自己设置了一个高的值,但别人可能设置了一个更高的值,所以这个功能没法精确的控制CPU使用率。
cpu.shares对应k8s内的resources.requests.cpu字段:
值对应关系为:resources.requests.cpu * 1024 = cpu.shares
如:resources.requests.cpu为3的时候,cpu.shares值为3072;resources.requests.cpu为100m的时候,cpu.shares值为102
cpu.cpu.cfs_period_us、cpu.cfs_quota_us解析
cpu.cfs_period_us用来配置时间周期长度,cpu.cfs_quota_us用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数。 两个文件配合起来设置CPU的使用上限。两个文件的单位都是微秒(us),cfs_period_us的取值范围为1毫秒(ms)到1秒(s),cfs_quota_us的取值大于1ms即可,如果cfs_quota_us的值为-1(默认值),表示不受cpu时间的限制。cpu.cpu.cfs_period_us、cpu.cfs_quota_us对应k8s中的resources.limits.cpu字段:
resources.limits.cpu = cpu.cfs_quota_us/cpu.cfs_period_us
并且k8s下容器控制组的cpu.cpu.cfs_period_us值固定为100000,实际只设置cpu.cfs_quota_us值
例如:
cpu.cpu.cfs_period_us为100000(单位微妙,即0.1秒),cpu.cfs_quota_us为500000(单位微妙,即0.5秒)时,resources.limits.cpu为5,即5个cpu核心。
cpu.cpu.cfs_period_us为100000(单位微妙,即0.1秒),cpu.cfs_quota_us为10000(单位微妙,即0.01秒)时,resources.limits.cpu为0.1(或100m),即0.1个cpu核心。
pod的内存控制组解析
与cpu不同,k8s里pod容器的requests.memory在控制组内没有对应的属性,未起到限制作用,只是协助k8s调度计算。
而pod容器的limits.memory对应控制组里的memory.limit_in_bytes值。
总结
k8s基于pod管理控制组,同一pod内的容器所属同一控制组,并且每个控制组内包含一个infra沙箱容器k8s基于.spec.containers[x].resources对pod划分了三种类型,对应控制组路径如下:
| Pod类型 | 描述 | 控制组 |
|---|---|---|
| Guaranteed | 内存与CPU设置了相同的requests和limits | /sys/fs/cgroup/ |
| Burstable | 至少有一个容器设置了CPU或内存的requests属性 | /sys/fs/cgroup/ |
| BestEffort | 所有容器均未设置requests和limits | /sys/fs/cgroup/ |
- 控制组中
cpu.shares对应k8s内的resources.requests.cpu字段,值对应关系为:
resources.requests.cpu * 1024 = cpu.shares
- 控制组中
cpu.cpu.cfs_period_us、cpu.cfs_quota_us对应k8s中的resources.limits.cpu字段,值对应关系为:
resources.limits.cpu = cpu.cfs_quota_us/cpu.cfs_period_us
- 控制组里的
memory.limit_in_bytes对应k8s中的resources.limits.memory值