本文演示 CentOS 8.0:Linux VM-8-17-centos 4.18.0-348.7.1.el8_5.x86_64
前言
在Linux cgroups概述中我们简单介绍了什么是cgroups和cgroups中的一些基本概念,本章我们将继续探索cgroups,深入理解cgroups。本章我们将介绍docker目前默认使用的cgroups v1进行介绍。
整体架构
- libcgroup: 一个开源的软件,提供一组cgroups的应用程序和库。
- subsystem:cgroup支持的所有可配置的资源称为子系统,如:cpu、内存、网络等都是子系统。
- task:进程在cgroups中称为task,taskid就是pid。
- hierarchy: cgroups从用户态看,提供了一种组cgroup类型的文件系统(Filesystem),这是一组虚拟的文件系统,通过对这个文件系统的配置,告诉内核,如何希望对哪些进程使用多少资源。文件系统本身是层级的,所以构成了hierarchy。
子系统
到目前为止,Linux支持13种subsystem,比如限制CPU的使用时间,限制使用的内存,统计CPU的使用情况,冻结和恢复一组进程等,我们可以通过如下命令查看系统支持的subsystem。
[root@VM-8-17-centos cgroup]# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 4 1 1
cpu 5 80 1
cpuacct 5 80 1
blkio 12 78 1
memory 2 185 1
devices 6 78 1
freezer 10 1 1
net_cls 3 1 1
perf_event 8 1 1
net_prio 3 1 1
hugetlb 9 1 1
pids 7 93 1
rdma 11 1 1
从左到右,字段的含义分别是:
-
支持的subsystem的名字。
-
subsystem所关联到的cgroup树的ID,如果多个subsystem关联到同一颗cgroup树,那么他们的这个字段将一样,比如这里的cpu和cpuacct就一样,表示他们绑定到了同一颗树。如果出现下面的情况,这个字段将为0:
-
当前subsystem没有和任何cgroup树绑定
-
当前subsystem已经和cgroup v2的树绑定
-
当前subsystem没有被内核开启
-
-
subsystem所关联的cgroup树中进程组的个数,也即树上节点的个数。
-
1表示开启,0表示没有被开启(可以通过设置内核的启动参数“cgroup_disable”来控制subsystem的开启)。
13种subsystem列表:
subsystem | 内核版本 | 配置 | 作用描述 |
---|---|---|---|
cpu | since Linux 2.6.24 | CONFIG_CGROUP_SCHED | 用来限制cgroup的CPU使用率。 |
cpuacct | since Linux 2.6.24 | CONFIG_CGROUP_CPUACCT | 统计cgroup的CPU的使用率。 |
cpuset | since Linux 2.6.24 | CONFIG_CPUSETS | 绑定cgroup到指定CPUs和NUMA节点。 |
memory | since Linux 2.6.25 | CONFIG_MEMCG | 统计和限制cgroup的内存的使用率,包括process memory, kernel memory, 和swap。 |
devices | since Linux 2.6.26 | CONFIG_CGROUP_DEVICE | 限制cgroup创建(mknod)和访问设备的权限。 |
freezer | since Linux 2.6.28 | CONFIG_CGROUP_FREEZER | suspend和restore一个cgroup中的所有进程。 |
net_cls | since Linux 2.6.29 | CONFIG_CGROUP_NET_CLASSID | 将一个cgroup中进程创建的所有网络包加上一个classid标记,用于tc和iptables。 只对发出去的网络包生效,对收到的网络包不起作用。 |
blkio | since Linux 2.6.33 | CONFIG_BLK_CGROUP | 限制cgroup访问块设备的IO速度。 |
perf_event | since Linux 2.6.39 | CONFIG_CGROUP_PERF | 对cgroup进行性能监控。 |
net_prio | since Linux 3.3 | CONFIG_CGROUP_NET_PRIO | 针对每个网络接口设置cgroup的访问优先级。 |
hugetlb | since Linux 3.5 | CONFIG_CGROUP_HUGETLB | 限制cgroup的huge pages的使用量。 |
pids | since Linux 4.3 | CONFIG_CGROUP_PIDS | 限制一个cgroup及其子孙cgroup中的总进程数。 |
rdma | since Linux 4.11 | CONFIG_CGROUP_RDMA | 限制进程对RDMA/IB资源的使用。 |
cgroups文件系统接口
cgroup相关的所有操作都是基于内核中的cgroup virtual filesystem,如果使用cgroup直接挂载这个文件系统就可以了。一般情况下都是挂载到/sys/fs/cgroup目录下,也可以挂载到其它任何目录。特别注意的是在使用 systemd 系统的操作系统中,/sys/fs/cgroup 目录都是由 systemd 在系统启动的过程中挂载的,并且挂载为只读的类型。
[root@VM-8-17-centos ~]# mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
tmpfs on /sys/fs/cgroup type tmpfs
说明 /sys/fs/cgroup 目录下的文件都是存在于内存中的临时文件。
cgroup on /sys/fs/cgroup/systemd type cgroup
用于 systemd 系统对 cgroups 的支持。
其余的挂载点则是系统帮我们创建好的cgroup层级结构,一共创建了12棵cgroup树(hierarchy),并且分别已经绑定好了对应的子系统。例如:cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
挂载的设备为cgroup,挂载点为/sys/fs/cgroup/memory,文件系统类型为cgroup,括号中的信息是执行mount时候的选项,rw表示以可读写的方式挂载文件系统,noexec 表示不能在该文件系统上直接运行程序,memory表示绑定的subsystem为memory。
mount输出中的每行代表挂载的一个文件系统,其格式为:
fs_spec on fs_file type fs_vfstype (fs_mntopts)
- fs_spec:挂载的块设备或远程文件系统
- fs_file:文件系统的挂载点
- fs_vfstype:文件系统的类型
- fs_mntopts:与文件系统相关的更多选项,不同的文件系统其选项不一样。
/sys/fs/cgroup目录
系统已经将cgroup文件系统挂载到了/sys/fs/cgroup/……下的挂载点,并且将subsystem进行了指定。接下来我们来看一下/sys/fs/cgroup目录结构和内容。
[root@VM-8-17-centos cgroup]# cd /sys/fs/cgroup
[root@VM-8-17-centos 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
进入到/sys/fs/cgroup目录下我们看到了所有cgroup树的根目录,我们进入到memory目录。
[root@VM-8-17-centos systemd]# cd memory/
[root@VM-8-17-centos memory]# ls
cgroup.clone_children memory.kmem.tcp.max_usage_in_bytes memory.soft_limit_in_bytes
cgroup.event_control memory.kmem.tcp.usage_in_bytes memory.stat
cgroup.procs memory.kmem.usage_in_bytes memory.swappiness
cgroup.sane_behavior memory.limit_in_bytes memory.usage_in_bytes
init.scope memory.max_usage_in_bytes memory.use_hierarchy
memory.failcnt memory.memsw.failcnt mem_test
memory.force_empty memory.memsw.limit_in_bytes notify_on_release
memory.kmem.failcnt memory.memsw.max_usage_in_bytes release_agent
memory.kmem.limit_in_bytes memory.memsw.usage_in_bytes system.slice
memory.kmem.max_usage_in_bytes memory.move_charge_at_immigrate tasks
memory.kmem.slabinfo memory.numa_stat user.slice
memory.kmem.tcp.failcnt memory.oom_control YunJing
memory.kmem.tcp.limit_in_bytes memory.pressure_level
进入到memory目录后我们看到了很多文件,这些文件就是 cgroups 的 memory 子系统中的根级设置。比如 memory.limit_in_bytes 中的数字用来限制进程的最大可用内存,memory.swappiness 中保存着使用 swap 的权重等等。tasks文件内可以设置我们要进行资源限制的pid。
Cgroups是以这些配置文件进行API暴露,也就是说当我们对这些文件的参数进行设置后,绑定到cgroup的subsystem就会按照参数设定限制tasks内设定进程组的资源。
查看进程所属的cgroup
每个进程在/proc/[pid]目录下有一个cgroup文件,这个文件内保存了一个进程和cgroup的对应关系。
[root@VM-8-17-centos ~]# echo $$
1190917
[root@VM-8-17-centos ~]# cat /proc/1190917cgroup
12:blkio:/user.slice
11:rdma:/
10:freezer:/
9:hugetlb:/
8:perf_event:/
7:pids:/user.slice/user-0.slice/session-3066.scope
6:devices:/user.slice
5:cpu,cpuacct:/user.slice
4:cpuset:/
3:net_cls,net_prio:/
2:memory:/user.slice/user-0.slice/session-3066.scope
1:name=systemd:/user.slice/user-0.slice/session-3066.scope
从左到右,字段的含义分别是:
- cgroup树的ID, 和/proc/cgroups文件中的ID一一对应。
- 和cgroup树绑定的所有subsystem,多个subsystem之间用逗号隔开。这里name=systemd表示没有和任何subsystem绑定,只是给他起了个名字叫systemd。
- 进程在cgroup树中的路径,即进程所属的cgroup,这个路径是相对于挂载点的相对路径。
第三列是相对路径,补全就是/sys/fs/cgroup/memory/user.slice/user-0.slice/session-3066.scope
,我们切换到对应的路径并查看tasks内容,确认PID:1190917是否在tasks内。
[root@VM-8-17-centos session-3066.scope]# cat tasks
1190904
1190916
1190917
1195768
我们在/sys/fs/cgroup/memory下查通过tree命令看user.slice文件夹的完整路径。
[root@VM-8-17-centos memory]# tree -L 2 -P -a u*
user.slice
└── user-0.slice
├── session-3066.scope
└── user@0.service
进程1190917在Hierarchy 2的session-3066.scope cgroup节点下。
Subsystems, Hierarchies, CGroups and Tasks之间关系
下面了解一下子系统(subsystems)、cgroups的层级(hierarchies)和任务(tasks)之间的一些简单的规则。
规则1
单个层级结构可以附加一个或多个子系统。我们演示的系统支持13个子系统,理论上我们可以将13个子系统关联到一个cgroup层级结构上得到一棵树,我们也可以每个子系统分别关联一个cgroup层级结构,这样就有13个cgroup层级结构。
规则2
挂载一颗cgroups的层级结构时,可以指定多个subsystem与之关联,但一个subsystem只能关联到一颗cgroup树,一旦关联并在这颗树上创建了子cgroup,subsystems和这棵cgroup树就成了一个整体,不能再重新组合。
规则3
task可以加入到任意多个不同hierarchy的任意cgroup中,但在一个hierarchy中只能加入到一个cgroup。也就是说task和cgroup是1对多的关系。
创建了 cgroups 层级结构中的节点(cgroup 结构体)之后,可以把进程加入到某一个节点的控制任务列表中,一个节点的控制列表中的所有进程都会受到当前节点的资源限制。同时某一个进程也可以被加入到不同的 cgroups 层级结构的节点中,因为不同的 cgroups 层级结构可以负责不同的系统资源。所以说进程和 cgroup 结构体是一个多对多的关系。
上面这个图从整体结构上描述了进程与 cgroups 之间的关系。最下面的task代表一个进程。每一个进程的描述符中有一个指针指向了一个辅助数据结构css_set(cgroups subsystem set)。指向某一个css_set的进程会被加入到当前css_set的进程链表中。一个进程只能隶属于一个css_set,一个css_set可以包含多个进程,隶属于同一css_set的进程受到同一个css_set所关联的资源限制。
上图中的”M×N Linkage”说明的是css_set通过辅助数据结构可以与 cgroups 节点进行多对多的关联。但是 cgroups 的实现不允许css_set同时关联同一个cgroups层级结构下多个节点。这是因为 cgroups 对同一种资源不允许有多个限制配置。
一个css_set关联多个 cgroups 层级结构的节点时,表明需要对当前css_set下的进程进行多种资源的控制。而一个 cgroups 节点关联多个css_set时,表明多个css_set下的进程列表受到同一份资源的相同限制。
规则4
任意一个task(process)通过调用fork方法获得一个子进程,子进程自动继承父进程的cgroup关系。子进程也可以根据实际使用需要移动到其他cgroup中。
演示
我们在cpu hierarchy下创建一个cgroup,名称为cpu_test。
[root@VM-8-17-centos ~]# cd /sys/fs/cgroup/cpu
[root@VM-8-17-centos cpu]# mkdir cpu_test
[root@VM-8-17-centos cpu]# ls
cgroup.clone_children cpuacct.stat cpuacct.usage_percpu cpuacct.usage_sys cpu.cfs_quota_us cpu.shares init.scope system.slice YunJing
cgroup.procs cpuacct.usage cpuacct.usage_percpu_sys cpuacct.usage_user cpu.rt_period_us cpu.stat notify_on_release tasks
cgroup.sane_behavior cpuacct.usage_all cpuacct.usage_percpu_user cpu.cfs_period_us cpu.rt_runtime_us cpu_test release_agent user.slice
创建cgroup cpu_test成功后系统自动为我们生成了资源限制文件。此时我们执行一个死循环并查看CPU占用情况。
[root@VM-8-17-centos cpu]# while : ; do : ; done &
[1] 1244120
[root@VM-8-17-centos cpu]# top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1244120 root 20 0 27720 2664 576 R 99.7 0.0 1:13.43 bash
我们使用top看到1244120进程几乎将cpu占满,接下来我们新打开一个shell2对其进行限制。
[root@VM-8-17-centos cpu_test]# cd /sys/fs/cgroup/cpu/cpu_test/
[root@VM-8-17-centos cpu_test]# echo 1244120 > tasks
[root@VM-8-17-centos cpu_test]# echo 50000 > cpu.cfs_quota_us
限制后task 1244120的cpu使用率控制到50%。
如果我们再将一个死循环的进程加入到cpu_test cgroup下会怎样呢?我们在shell2中再执行一个死循环并且重复上述步骤将他加入到cpu_test中。
[root@VM-8-17-centos cpu_test]# while : ; do : ; done &
[1] 1245807
从上图看出,在shell2中执行死循环1245807 cpu再次被占满。
[root@VM-8-17-centos cpu_test]# echo 1245807 > tasks
当我们将1245807加入到tasks之后我们发现,1244120和1245807的cpu占用分别为25%。这是为什么呢?因为cgroup cpu_test限定了总的cpu使用是50%,加入到这个cgroup的task会共同使用50%的cpu资源,所以出现了上述现象。
总结
本章我们结合操作、图文对cgroups进行了进一步的探索和理解,并且最后在cpu hierarchy 的cpu_test cgroup内演示了对cpu使用率的控制。docker在启动容器的时候可以设置不同的启动参数,实现对资源的限制(resource_constraints),所依赖的底层技术就是cgroups。
到目前为止我们理解了namespace和cgroup,接下来我们将继续了解docker所依赖的底层技术联合文件系统(Union file systems )。
评论区