1. cgroup是什么?

控制族群(control group),简称 cgroup或cgroups。cgroup可为系统中所运行任务(进程)的用户定义组群分配资源,比如 CPU 时间、系统内存、网络和IO带宽或者这些资源的组合。

By using cgroups, system administrators gain fine-grained control over allocating, prioritizing, denying, managing, and monitoring system resources. Hardware resources can be appropriately divided up among tasks and users, increasing overall efficiency.(使用 cgroup,系统管理员可更具体地控制对系统资源的分配、优先顺序、拒绝、管理和监控。可更好地根据任务和用户分配硬件资源,提高总体效率。)

使用 cgroup,系统管理员可更具体地控制对系统资源的分配、优先顺序、拒绝、管理和监控。可更好地根据任务和用户分配硬件资源,提高总体效率。

2. cgroup v1示例

看个实例:

snippet.bash
# 找到cgroup的挂载点
[root@localhost ~]# mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
 
# 查看虚拟文件系统下的文件
[root@localhost ~]# cd /sys/fs/cgroup/
[root@localhost cgroup]# ls -l
drwxr-xr-x.  2 root root  40 1215 02:45 blkio
lrwxrwxrwx.  1 root root  11 1215 02:45 cpu -> cpu,cpuacct
lrwxrwxrwx.  1 root root  11 1215 02:45 cpuacct -> cpu,cpuacct
drwxr-xr-x.  2 root root  40 1215 02:45 cpu,cpuacct
dr-xr-xr-x.  2 root root   0 1215 02:45 cpuset
dr-xr-xr-x.  3 root root   0 1215 02:45 devices
dr-xr-xr-x.  2 root root   0 1215 02:45 freezer
dr-xr-xr-x.  2 root root   0 1215 02:45 hugetlb
dr-xr-xr-x.  6 root root   0 1215 02:45 memory
lrwxrwxrwx.  1 root root  16 1215 02:45 net_cls -> net_cls,net_prio
dr-xr-xr-x.  2 root root   0 1215 02:45 net_cls,net_prio
lrwxrwxrwx.  1 root root  16 1215 02:45 net_prio -> net_cls,net_prio
dr-xr-xr-x.  2 root root   0 1215 02:45 perf_event
dr-xr-xr-x.  6 root root   0 1215 02:45 pids
dr-xr-xr-x.  2 root root   0 1215 02:45 rdma
dr-xr-xr-x.  6 root root   0 1215 02:45 systemd
 
# 查看cpu子系统,注意顶层下有个“test”目录,这是我创建的子层。
[root@localhost cpu]# ls -l
-rw-r--r--.  1 root root   0 1215 06:07 cgroup.clone_children
-rw-r--r--.  1 root root   0 1215 06:03 cgroup.procs
-r--r--r--.  1 root root   0 1215 06:07 cgroup.sane_behavior
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.stat
-rw-r--r--.  1 root root   0 1215 06:07 cpuacct.usage
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.usage_all
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.usage_percpu
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.usage_percpu_sys
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.usage_percpu_user
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.usage_sys
-r--r--r--.  1 root root   0 1215 06:07 cpuacct.usage_user
-rw-r--r--.  1 root root   0 1215 06:07 cpu.cfs_period_us
-rw-r--r--.  1 root root   0 1215 06:07 cpu.cfs_quota_us
-rw-r--r--.  1 root root   0 1215 06:07 cpu.rt_period_us
-rw-r--r--.  1 root root   0 1215 06:07 cpu.rt_runtime_us
-rw-r--r--.  1 root root   0 1215 06:07 cpu.shares
-r--r--r--.  1 root root   0 1215 06:07 cpu.stat
-rw-r--r--.  1 root root   0 1215 06:07 notify_on_release
-rw-r--r--.  1 root root   0 1215 06:07 release_agent
-rw-r--r--.  1 root root   0 1215 06:07 tasks
drwxr-xr-x. 2 yz   yz   0 1215 06:10 test
 
# 切换到test组
[root@localhost cpu]# cd test/
 
# 查看CPU使用率限制:
[root@localhost test]# cat cpu.cfs_period_us
100000
[root@localhost test]# cat cpu.cfs_quota_us
-1
 
# 假设这个系统上只有一个CPU核。修改CPU使用率限制为50%。
[root@localhost test]# echo 50000 > cpu.cfs_quota_us
[root@localhost test]# cat cpu.cfs_quota_us
50000
 
# 将被限制进程的PID加入cgroup.procs,这样进程45992的CPU使用率将被限制为不超过50%。
[root@localhost test]# echo 45992 > cgroup.procs
[root@localhost test]# cat cgroup.procs
45992

要使用cgroup,Linux内核版本需为2.5.32及更高版本。

3. cgroup v1的问题

cgroup v2的重要特性

4. CentOS 8启用cgroup v2

建议内核版本4.15或更高。

相对来说,CentOS 8切换到cgroup v2很容易。CentOS 7虽然也可以切换到cgroup v2,但是比较麻烦,需要更换内核、更换systemd,而systemd相关组件也需要更换,且有不稳定的风险,不建议生产环境上使用。

以下仅给出CentOS 8的切换方法。

snippet.bash
[root@bogon ~]# vim /etc/default/grub
GRUB_CMDLINE_LINUX添加"cgroup_no_v1=all systemd.unified_cgroup_hierarchy=1"
[root@bogon ~]# reboot

所以,以上配置后,CentOS 8将完全使用cgroup v2资源管理功能。

三种模式的设置方法:

unified模式:

GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1 systemd.legacy_systemd_cgroup_controller=0 cgroup_no_v1=all"

legacy模式:

GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller=1"

hybrid模式:

GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1 systemd.legacy_systemd_cgroup_controller=1"

5. cgroup v2层级概览

先看看cgroup v2层级结构:

snippet.bash
# 找到cgroup v2的挂载点
[root@bogon ~]# mount -l | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate)
 
# 查看虚拟文件系统下的文件,目录结构与v1完全不同。test是我创建的子级。
[root@localhost ~]# cd /sys/fs/cgroup
[root@localhost cgroup]# ls -l
-r--r--r--. 1 root root 0 1215 07:38 cgroup.controllers
-rw-r--r--. 1 root root 0 1215 07:23 cgroup.max.depth
-rw-r--r--. 1 root root 0 1215 07:23 cgroup.max.descendants
-rw-r--r--. 1 root root 0 1215 07:23 cgroup.procs
-r--r--r--. 1 root root 0 1215 07:23 cgroup.stat
-rw-r--r--. 1 root root 0 1215 07:23 cgroup.subtree_control
-rw-r--r--. 1 root root 0 1215 07:23 cgroup.threads
-rw-r--r--. 1 root root 0 1215 07:23 cpu.pressure
-r--r--r--. 1 root root 0 1215 07:23 cpu.stat
-rw-r--r--. 1 root root 0 1215 07:23 io.pressure
-rw-r--r--. 1 root root 0 1215 07:23 memory.pressure
drwxr-xr-x. 2 yz   yz   0 1215 07:39 test

查看当前层级的全部可用子控制器(cgroup.controllers):

snippet.bash
[root@localhost cgroup]# cat cgroup.controllers
cpuset cpu io memory hugetlb pids rdma

看个例子:

snippet.bash
[root@localhost cgroup]# pwd
/sys/fs/cgroup
 
# 父级的可用控制器:
[root@localhost cgroup]# cat cgroup.controllers
cpuset cpu io memory hugetlb pids rdma
 
# 当前还没有为子层配置控制器,所以子层test现在没有任何可用控制器:
[root@localhost cgroup]# cat cgroup.subtree_control
(输出为空)
[root@localhost cgroup]# cat test/cgroup.controllers
(输出为空)
 
# 我们为子层配置CPU控制器,这样子层就有了CPU控制器:
[root@localhost cgroup]# echo "+cpu" > cgroup.subtree_control
[root@localhost cgroup]# cat cgroup.subtree_control
cpu
# 子层test现在可以配置CPU相关的限制了。
[root@localhost cgroup]# cat test/cgroup.controllers
cpu
 
# 子层不会自动继承父层的可用控制器。
[root@localhost cgroup]# cat test/cgroup.subtree_control
(输出为空)

6. cgroup v2使用注意事项

6.1 接口特性

控制器的添加与删除,以最后一次的操作为准:

snippet.bash
[root@localhost cgroup2]# echo "+cpu -cpu +memory +cpu -cpu -cpu -cpu" > cgroup.subtree_control
[root@localhost cgroup2]# cat cgroup.subtree_control
(输出为空)

一次操作是一次“事务”,如果操作错误,将回滚本次操作:

snippet.bash
[root@localhost cgroup2]# echo "+cpu +mm" > cgroup.subtree_control
-bash: echo: write error: Invalid argument
[root@localhost cgroup2]# cat cgroup.subtree_control
(输出为空)

6.2 theaded模式

v1版本中,将进程PID加入cgroup.procs从而控制进程的资源使用,而将线程的LWPID(Light Weight Process ID)加入tasks从而控制线程的资源使用。进程和线程可以同时控制。

到v2版本,就没有那么方便了,在任意非顶层的层级下都有一个cgroup.type,用于选择是进程还是线程模式,两者不能同时使用,其中,

更麻烦的是,模式选择改成theaded模式是单向不可逆的,也就是说,一个资源组一旦修改为theaded模式就无法再改为进程模式了。对应用层来说十分不友好。

6.3 无法使用CPU控制器

在CentOS 8中,当使用cgroup v1和v2混合模式时,我们发现CPU控制器无法被使用:

snippet.bash
[root@localhost ~]# mkdir /mnt/cgroup2
[root@localhost ~]# mount -t cgroup2 cgroup2 /mnt/cgroup2/
 
[root@localhost ~]# umount /sys/fs/cgroup/cpu
[root@localhost ~]# cat /mnt/cgroup2/cgroup.controllers
cpu
[root@localhost ~]# cat /mnt/cgroup2/cgroup.subtree_control
(输出为空)
[root@localhost ~]# echo "+cpu" > /mnt/cgroup2/cgroup.subtree_control
-bash: echo: write error: Invalid argument

原因是,当前系统中有实时进程存在,无法将cpu控制器从cgroup v1切换到v2,需要先停止实时进程,详见参考文献[6, 7]。

6.4 避免频繁在 cgroup 之间迁移进程

在 cgroup 之间迁移进程是一个开销相对较高的操作,而且 有状态资源(例如 memory)是不会随着进程一起迁移的。因此,不建议为了达到某种资源限制目的而频繁地在 cgroup 之间迁移进程。

7. cgroup v2的坑

7.1 cgroup.procs权限

非root用户想实际使用cgroup v2,首先面临的问题就是cgroup.procs权限问题,即使用户有权限的层级,也没有写cgroup.procs的权限,需要将根层级(root)的cgroup.procs设置为所有用户写权限。

7.2 没有与v1对应的net_cls和net_prio

cgroup v2没有与v1版的net_clsnet_prio相对应的控制器。

Instead, support was added in iptables to allow BPF filters that hook on cgroup v2 pathnames to allow control of NW traffic on a per-cgroup basis (Since Linux 4.5(?))

请参阅文献[8]。

7.3 cgroup v2 iptables的bug

内核的bug,iptables使用cgroup --path参数时,路径识别错误。

请参阅文献[9]。

参考