侧边栏壁纸
  • 累计撰写 32 篇文章
  • 累计创建 55 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Linux cgroups:深入理解cgroups v1版本

Testerfans
2022-07-08 / 0 评论 / 29 点赞 / 2,201 阅读 / 3,879 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-07-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

本文演示 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 

image-1657615759096
限制后task 1244120的cpu使用率控制到50%。

如果我们再将一个死循环的进程加入到cpu_test cgroup下会怎样呢?我们在shell2中再执行一个死循环并且重复上述步骤将他加入到cpu_test中。

[root@VM-8-17-centos cpu_test]# while : ; do : ; done &
[1] 1245807

image-1657615962513

从上图看出,在shell2中执行死循环1245807 cpu再次被占满。

[root@VM-8-17-centos cpu_test]# echo 1245807 > tasks

image-1657616029714
当我们将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 )。


本文参考:
Linux Cgroup系列(01):Cgroup概述
linux cgroups 简介
man

29

评论区