在最近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_uscpu.cfs_quota_uscpu.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_uscpu.shares,可以按照第6节《重学容器06: 容器资源限制背后的技术cgroups》中手动体验cgroup的方式具体测试一下,测试过程这里省略了。

k8s如何使用cpu cgroup限制容器cpu的猜想和验证

最后根据结合Kubernetes中对容器的资源限制requests.cpulimits.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_uscpu.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,与我们猜想的2102.4是一致的。

参考