重学容器29: 容器资源限制之限制容器的CPU
2021-07-28
在最近25~28节,学习了容器镜像构建的一些实践和技巧,使用Containerd替代Docker后,使用buildkit作为容器镜构建工具,到此对容器镜像构建的学习先告一段落。
在前面第6节学习了容器资源限制背后的技术cgroups的基本概念,并以cgroups cpu子系统为例体验一下手动设置cgroup。 先简单复习一下第6节的内容。使用CGroups可以控制的资源有: CPU、内存、网络、IO、文件设备等,因为计算资源(CPU)是cgroups资源限制中最重要的一种资源之一,本节将详细介绍一下如何使用cpu cgroup限制容器CPU的使用。
linux的cpu cgroups介绍 #
CGroups为每种可以控制的资源定义了一个子系统,支持的子系统包含: cpuset,cpu,cpuacct,memory,devices,freezer,net_cls,blkio,perf_event,hugetlb,pids,net_prio。其中cpu子系统用限制cgroup中进程的CPU使用份额
在使用cgroups时需要先挂载,例如在centos下被挂载到了/sys/fs/cgroup
。cpu cgroup子系统被挂载到了/sys/fs/cgroup/cpu
,在这个目录下是各个控制组control group目录,每个控制组目录下还可以有子目录,因此各个控制组形成了一个树状的层级关系。
例如在/sys/fs/cgroup/cpu
下创建一个名为foo的控制组目录,控制组目录中有如下内容:
1cd /sys/fs/cgroup/cpu
2mkdir foo
3
4cd foo && ls
5cgroup.clone_children cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
6cgroup.event_control cpuacct.usage cpu.cfs_quota_us cpu.shares tasks
7cgroup.procs cpuacct.usage_percpu cpu.rt_period_us cpu.stat
重点关注下面3个参数 cpu.cfs_period_us
、cpu.cfs_quota_us
和cpu.shares
。cfs表示Completely Fair Scheduler完全公平调度器,是Linux内核的一部分,负责进程调度。
cpu.cfs_period_us
: 用来设置一个CFS调度时间周期长度,默认值是100000us(100ms),一般cpu.cfs_period_us
作为系统默认值我们不会去修改它。cpu.cfs_quota_us
: 用来设置在一个CFS调度时间周期(cfs_period_us)内,允许此控制组执行的时间。默认值为-1表示限制时间。cpu.shares
: 用来设置cpu cgroup子系统对于控制组之间的cpu分配比例。默认值是1000。
使用
cfs_quota_us
/cfs_period_us
,例如20000us/100000us=0.2,表示允许这个控制组使用的CPU最大是0.2个CPU,即限制使用20%CPU。 如果cfs_quota_us/cfs_period_us=2
,就表示允许控制组使用的CPU资源配置是2个。
对于cpu分配比例的使用,例如有两个cpu控制组foo和bar,foo的cpu.shares是1024,bar的cpu.shares是3072,它们的比例就是1:3。 在一台8个CPU的主机上,如果foo和bar设置的都是需要4个CPU的话(
cfs_quota_us/cfs_period_us=4
),根据它们的CPU分配比例,控制组foo得到的是2个,而bar控制组得到的是6个。需要注意cpu.shares
是在多个cpu控制组之间的分配比例,且只有到整个主机的所有CPU都打满时才会起作用。 例如刚才这个例子,如果是在一个4CPU的机器上,foo和bar的比例是1:3,如果foo控制组的程序满负载跑,而bar控制组程序没有运行或空负载,此时foo仍然能获得4个CPU,但如果foo和bar都满负载跑,因为它们都设置的需要4个CPU,而主机的CPU不够,只能按比例给它们分配1:3,则foo分配得到1个,而bar得到3个。
也就是说cpu.cfs_quota_us/cpu.cfs_period_us
决定cpu控制组中所有进程所能使用CPU资源的最大值,而cpu.shares
决定了cpu控制组间可用CPU的相对比例,这个比例只有当主机上的CPU完全被打满时才会起作用。
有了上面对cpu.cfs_quota_us/cpu.cfs_period_us
和cpu.shares
,可以按照第6节《重学容器06: 容器资源限制背后的技术cgroups》中手动体验cgroup的方式具体测试一下,测试过程这里省略了。
k8s如何使用cpu cgroup限制容器cpu的猜想和验证 #
最后根据结合Kubernetes中对容器的资源限制requests.cpu
和limits.cpu
,k8s中的容器是如何实现的呢,大胆的猜想一下:
limits.cpu
是容器所能使用cpu的上限,是由cpu.cfs_quota_us/cpu.cfs_period_us
来决定的requests.cpu
是为容器保留cpu数,如果当前已经调度到k8s节点上的requests.cpu
总和超过节点cpu数,将不会在有新的容器调度到此节点上。因为cpu.share
默认值是1024,可以认为是一个CPU的比例,则假设我们要设置某个容器的requests.cpu
是2的话,就是把它的cpu控制组的cpu.shares
设置成2*1024即2048。例如一个只有4个CPU的节点,上面跑3个容器request的CPU分别是1,1,2,则cpu.shares分别要设置成1024,1024,2048。也就是说如果节点CPU被打满的话,则他们都将获得他们请求的1,1,2个cpu。
下面我们到一个K8S集群中去验证我们的猜想,这个k8s集群的版本是1.21,容器运行时使用的是containerd。 在这个集群中部署了Kubernetes的Dashboard,设置了dashboard容器的资源限制情况如下:
1resources:
2 limits:
3 cpu: '2'
4 memory: 200Mi
5 requests:
6 cpu: 100m
7 memory: 200Mi
即limits.cpu为2,requests.cpu为0.1。按照我们的猜想,则k8s dashboard容器cpu控制组的cpu.cfs_quota_us/cpu.cfs_period_us
应该是2,cpu.shares
应该是0.1*1024=102.4。
首先确认k8s dashboard的pod在哪个Node上:
1kubectl get pod --all-namespaces -o wide | grep kubernetes-dashboard
2kube-system kubernetes-dashboard-664696fdff-54sbx 2/2 Running 7 3d9h 10.244.104.39 node2 <none> <none>
其在node2节点上,而且这个pod的QoS Class是Burstable,可以到node2的/sys/fs/cgroup/cpu/kubepods.slice/kubepods-burstable.slice
目录去寻找。
1ls /sys/fs/cgroup/cpu/kubepods.slice/kubepods-burstable.slice/ | grep kubepods
2kubepods-burstable-pod48059b85_d562_41b4_a52e_1c52cdd1a701.slice
3kubepods-burstable-pod769775b4_a6a0_4c9e_b177_94a11c11e81c.slice
4kubepods-burstable-pod97a6d223_6382_452b_8591_ee62c9ab1dd8.slice
5kubepods-burstable-podfa28bfd5_64ce_43a6_8e50_2a0840fbf207.slice
可以看到在这个目录下找到了4个pod目录,那么哪个是k8s dashboard的pod呢,实际上目录名里有pod的metadata.uuid
,这里k8s dashboard pod的uuid是97a6d223-6382-452b-8591-ee62c9ab1dd8
, 因此定位到了kubepods-burstable-pod97a6d223_6382_452b_8591_ee62c9ab1dd8.slice
目录。
1cd kubepods-burstable-pod97a6d223_6382_452b_8591_ee62c9ab1dd8.slice
2
3ls | grep cri-containerd
4cri-containerd-1854819f0376bca40d7409c0fc0c31180cd8ffc59953af0de140f92e6f6a7711.scope
5cri-containerd-6035d1032b8f9ca2728eb2fc12632c3bf6608fb6099be891559a98a33aa23483.scope
6cri-containerd-967ad69fa34123adf46c91b57134eb232e1db3fb30a06cca5bc79d3d04cbd319.scope
pod的目录里有上面3个cri-containerd目录,哪个是dashboard容器的目录呢。使用crictl查看一下容器id:
1crictl ps | grep dashboard
26035d1032b8f9 e1482a24335a6 12 hours ago Running kubernetes-dashboard 6 967ad69fa3412
31854819f0376b 48d79e554db69 30 hours ago Running dashboard-metrics-scraper 1 967ad69fa3412
找到了容器id是6035d1032b8f9
,确定了目录是cri-containerd-6035d1032b8f9ca2728eb2fc12632c3bf6608fb6099be891559a98a33aa23483.scope
,再进入到这个目录中:
1cd cri-containerd-6035d1032b8f9ca2728eb2fc12632c3bf6608fb6099be891559a98a33aa23483.scope
2
3ls
4cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
5cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
查看cpu.cfs_quota_us
、cpu.cfs_period_us
, cpu.shares
的值:
1cat cpu.cfs_quota_us
2200000
3
4cat cpu.cfs_period_us
5100000
6
7cat cpu.shares
8102
即容器cpu控制组中设置是cpu.cfs_quota_us/cpu.cfs_period_us=200000/100000
=2
, cpu.shares
=102
,与我们猜想的2
和102.4
是一致的。