容器本质上是进程,既然是进程就会消耗掉系统资源,比如:CPU、内存、磁盘、网络带宽等,如果不加以限制,容器在某些情况下就会无限制地吃掉宿主机的系统资源,显然这不是我们期望发生的,另外当我们的环境中运行了很多容器,且系统资源一定的情况下,我们有优先保证主要容器应用的需求,如何既能够解决此问题同时又能够满足我们的需求呢?答案就是:Linux Cgroup(全程Linux Control Group),在前面的文章中,介绍了namespace为容器这类进程提供了隔离,而Cgroup可以为容器这类进程提供资源使用上限,两者黄金搭档,共同为容器应用保驾护航。

二、Cgroup的原理和实践

CPU的周期控制

Cgroup可以为容器进程使用的CPU、内存、磁盘、网络带宽资源进行限制,具体是如何实现的呢?接下来我们一起来实操下,在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 这个路径下,我们先去此目录查看下

[root@k8s-master /]# cd sys/fs/cgroup/
[root@k8s-master cgroup]# ls
blkio  cpuacct      cpuset   freezer  memory   net_cls,net_prio  perf_event  rdma
cpu    cpu,cpuacct  devices  hugetlb  net_cls  net_prio          pids        systemd

可以看到在cgroup的这个目录下存在很多子目录,这些都是cgroup可以限制地资源种类,我们在进一步进入到CPU的子目录查看下,里面有限制资源种类的详细的限制地指标,比如

1、cpu.cfs_period_us:指定容器对CPU的使用多长时间重新做一次分配

2、cpu.cfs_quota_us:指在cpu.cfs_period_us周期内给分配多少时间给容器


这两个指标需要一起配合使用来实现CPU的周期控制,我们先手动模拟容器创建的时候,如何完成利用cgroup来实现资源限制,以CPU周期控制为例子,先在/sys/fs/cgroup/cpu目录下创建1个container_


test的目录,如下所示我已经创建好(红色字体)。

[root@k8s-master cgroup]# cd cpu
[root@k8s-master cpu]# ls
cgroup.clone_children  cpuacct.usage_all          cpu.cfs_period_us  docker
cgroup.procs           cpuacct.usage_percpu       cpu.cfs_quota_us   kubepods
cgroup.sane_behavior   cpuacct.usage_percpu_sys   cpu.rt_period_us   notify_on_release
container_test         cpuacct.usage_percpu_user  cpu.rt_runtime_us  release_agent
cpuacct.stat           cpuacct.usage_sys          cpu.shares         system.slice
cpuacct.usage          cpuacct.usage_user         cpu.stat           tasks

 然后进入到此目录下,ls查看下,这里出现了一个神奇的形象,此目录下自动生成了很多CPU子系统控制的指标,这些指标我们并未进行新增,也就是说在/sys/fs/cgroup/cpu目录下会给新建的目录默认配置CPU子系统资源限制的指标

[root@k8s-master cpu]# cd container_test/
[root@k8s-master container_test]# ls
cgroup.clone_children  cpuacct.usage_percpu       cpu.cfs_period_us  cpu.stat
cgroup.procs           cpuacct.usage_percpu_sys   cpu.cfs_quota_us   notify_on_release
cpuacct.stat           cpuacct.usage_percpu_user  cpu.rt_period_us   tasks
cpuacct.usage          cpuacct.usage_sys          cpu.rt_runtime_us
cpuacct.usage_all      cpuacct.usage_user         cpu.shares

这些指标如何作用呢?为了体现资源的使用情况,我们先写一个程序来模拟来吃掉系统资源的情况,然后再来查看指标

[root@k8s-master sh]# cat while.sh 
#!/bin/bash
while : ; do : ; done &

[root@k8s-master sh]# sh while.sh

通过如上程序,写了一个while无限循环的shell脚本,默认情况下,这个程序之后的进程会占据掉系统所剩集群的所有资源,可通过top命令查看下

[root@k8s-master sh]# ps -ef |grep while
root      14975      1 97 20:29 pts/1    00:02:48 sh while.sh

 【原创】探索云计算容器底层之Cgroup

如上图所示,while循环的进程占据掉了96.3%的CPU资源,在实际的应用中若进程这样无限制的使用资源,将会给操作系统带来很大的负担,那么如何控制进程资源的使用呢?回到我们之前创建在container_test目录下

[root@k8s-master container_test]# cat cpu.cfs_quota_us 
-1
[root@k8s-master container_test]# cat cpu.cfs_period_us
100000

 默认创建的目录下cfs_quota_us 若为-1,则表示还未启用quota,即还未实行资源限制,cfs_period_us默认为100000us=100ms=0.1s(秒),接下来我们向cpu.cfs_quota_us 输入30ms=30000us,cfs_period_us值维持不变还是为100ms,在前面关于这2个概念有介绍,cpu.cfs_quota_us表示的是cfs_period_us的周期内,分配30/100的时间,即30%,接下来验证下

[root@k8s-master container_test]# echo 30000 > /sys/fs/cgroup/cpu/container_test/cpu.cfs_quota_us

  [root@k8s-master container_test]# cat cpu.cfs_quota_us
  30000

设置已完成,但是此时还不会立即生效,还需要将进程ID输入到资源限制地task里

[root@k8s-master container_test]# echo 14975 > /sys/fs/cgroup/cpu/container_test/tasks

接下来我们在通过top查看下资源使用情况,如下图所示,可以看到CPU的资源使用上限由原来的96.3%已经降到29.9%了,表明此while进程的CPU的资源使用上限已经设置成功。

【原创】探索云计算容器底层之Cgroup

 以上整个过程为手动设置模拟容器创建的过程中CPU份额控制的过程,实际上在容器创建的过程中,并不需要上面这般步骤,我们只需要在run容器的时候指定指标参数即可,如下所示

[root@k8s-master container_test]# docker run -it -d --cpu-period=100000 --cpu-quota=30000 nginx /bin/bash

上面的命令是后台守护进程的方式运行了1个nginx的容器,且指定CPU的每隔100000us=100ms做一次分配,且每次分配给容器的时间为30ms,可以看到这个分配和前面手动分配是一致的,值得注意的是这里需要加上-d来创建容器,若不加上的话会进入到终端交互界面,一旦提出终端交互界面后,容器这个进程也将会退出,而我们希望容器进程保持后台运行,因此需要加上-d,容器运行成功后,将会在docker目录下新建一个以容器ID命名的目录,这个目录和前面手动创建的目录以上,系统会默认配置资源限制的参数,我们可以如下看下:

[root@k8s-master container_test]# docker run -it -d --cpu-period=100000 --cpu-quota=30000 nginx /bin/bash
16f51f6780685be9c83b1684515005f30aed91916fdd6573b28eaf56be201e4a
[root@k8s-master docker]# ls
01a0fd62d2110e54b0c3635b2897e7c18e6b78f026fa57b4214d7662dd3b38ba  cpuacct.usage_sys
16f51f6780685be9c83b1684515005f30aed91916fdd6573b28eaf56be201e4a  cpuacct.usage_user
cgroup.clone_children                                             cpu.cfs_period_us
cgroup.procs                                                      cpu.cfs_quota_us
cpuacct.stat                                                      cpu.rt_period_us
cpuacct.usage                                                     cpu.rt_runtime_us
cpuacct.usage_all                                                 cpu.shares
cpuacct.usage_percpu                                              cpu.stat
cpuacct.usage_percpu_sys                                          notify_on_release
cpuacct.usage_percpu_user                                         tasks

如上红色部分为docker目录下依据容器的名称默认创建的目录,我们进入到这个目录,然后输出下之前我们在创建的时候指定的cpu.cfs_quota_us和cfs_period_us值

[root@k8s-master 16f51f6780685be9c83b1684515005f30aed91916fdd6573b28eaf56be201e4a]# cat cpu.cfs_period_us 
100000
[root@k8s-master 16f51f6780685be9c83b1684515005f30aed91916fdd6573b28eaf56be201e4a]# cat cpu.cfs_quota_us
30000

可以看到我们之前设置的值已经生效了,也就是说这个nginx的容器最多可以支持使用到30%左右的CPU带宽。

相类似的我们可以对容器获取CPU的资源的优先级进行设置,通过--cpu-share这个参数,其指定的值并非是给容器具体的份额,其实是个权重,在需要对容器资源进行限制时才会生效,权重大的,可以优先得到CPU的资源;另外还可以对使用的核数进行限制,针对多核的服务器,可以控制容器运行限定使用哪些CPU内核和内存节点,即使用-cpuset-cpus和-cpuset-mens参数,比如:我们可以指定创建的容器只能用0、1、2三核。

 

三、总结

本文以CPU中周期控制限制某进程的CPU资源使用为例子,介绍了其手动设置参数和容器自动设置参数,每新建1个容器,在/sys/fs/cgroup/cpu/docker目录下都会自动以容器的ID为名字创建1个目录,且在此目录下支持对CPU、内存、网络带宽、磁盘的资源使用进行限制,而其限制地处理与CPU的周期控制是类似的,这里就未做过多介绍,感兴趣的读者可以自己实践验证下,感谢您的阅读!


作者简介:云计算容器\Docker\K8s\Serverless方向产品经理,学点技术,为更好地设计产品。