cgroups 是在 linux kernel 中运行的一种机制,它提供了一种将系统上的 task 进行分组管理的方法。 cgroups 的英文全称为 Control Groups,即控制组。控制组支持层级结构,用目录树的方式描述,每个目录对应一个分组。
面向系统用户,cgroups 将控制组以虚拟文件系统的方式呈现,展示出一个树形的文件目录,通过目录中文件接口来调整控制组以及涉及的 subsystem。 面向内核开发,cgroups 定义了一套内核 API,支持各种 subsystem 的接入。
subsystem 从 cgroups 中获悉每个控制组中的 task,进而对 task 进行管控。 一个 subsystem 通常负责管控某一类资源的使用配额,比如 CPU、内存、带宽、进程号等,也被称为 resource controller。
cgroups 有 v1 和 v2 两个版本,两个版本都有实际应用,这里先学习 cgroups v1。
cgroups 是在 kernel 中运行的机制,需要先用 mount 命令挂载它的虚拟文件系统,把它的文件接口暴露出来。
cgroups v1 和 cgroup v2 的挂载方式略有不同,这里以 cgroups v1 为例:
# -t 指定 vfstype 为 cgroup
# xxx 是设备名,会在 /proc/mounts 出现,cgroups 自身不会用到设备名,可以根据自身的管理需要来定义设备名
$ mount -t cgroup xxx /sys/fs/cgroup
挂载操作只是将运行在内核中的 cgroups 的状态暴露出来,可以多次挂载到不同的目录中(每个挂载目录中的内容都相同)。linux 发行版通常已经默认挂载到 /sys/fs/cgroup 。
CentOS 是先挂载一个 tmpfs 类型的文件系统,然后作为 cgroup 的挂载点:
$ cat /proc/mounts |grep cgroup
# /sys/fs/cgroup 是一个 tmpfs
tmpfs /sys/fs/cgroup tmpfs ro,seclabel,nosuid,nodev,noexec,mode=755 0 0
# /sys/fs/cgroup/* cgroup v1 每类资源的挂载点
cgroup /sys/fs/cgroup/systemd cgroup rw,seclabel,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
cgroup /sys/fs/cgroup/hugetlb cgroup rw,seclabel,nosuid,nodev,noexec,relatime,hugetlb 0 0
cgroup /sys/fs/cgroup/freezer cgroup rw,seclabel,nosuid,nodev,noexec,relatime,freezer 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,seclabel,nosuid,nodev,noexec,relatime,cpuacct,cpu 0 0
cgroup /sys/fs/cgroup/memory cgroup rw,seclabel,nosuid,nodev,noexec,relatime,memory 0 0
cgroup /sys/fs/cgroup/pids cgroup rw,seclabel,nosuid,nodev,noexec,relatime,pids 0 0
cgroup /sys/fs/cgroup/perf_event cgroup rw,seclabel,nosuid,nodev,noexec,relatime,perf_event 0 0
cgroup /sys/fs/cgroup/cpuset cgroup rw,seclabel,nosuid,nodev,noexec,relatime,cpuset 0 0
cgroup /sys/fs/cgroup/blkio cgroup rw,seclabel,nosuid,nodev,noexec,relatime,blkio 0 0
cgroup /sys/fs/cgroup/devices cgroup rw,seclabel,nosuid,nodev,noexec,relatime,devices 0 0
cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,seclabel,nosuid,nodev,noexec,relatime,net_prio,net_cls 0 0
创建一个新目录,在这个目录来演示后续的 cgroups v1 操作:
$ mkdir -p /demo/cgroups/v1
$ mount -t tmpfs tmpfs_for_cgroups_v1 /demo/cgroups/v1
cgroups v1 的资源管理理念:每类资源或资源组合单独一个目录,在其中管理所有 task 对该类资源的使用。
挂载 cgroups v1 的虚拟文件系统时,要用 -o 指定目标目录要关联的 subsystem(即 resource controller),多个 subsystem 之间用“,”间隔。
# 在目标目录中挂载一个 subsystem: memory
$ mkdir -p /demo/cgroups/v1/memory
$ mount -t cgroup -o memory none /demo/cgroups/v1/memory
# 在目标目录中挂载两个 subsystem: cpu 和 cpuacct
$ mkdir -p /demo/cgroups/v1/cpu,cpuacct
$ mount -t cgroup -o cpu,cpuacct none /demo/cgroups/v1/cpu,cpuacct
cgroups v1 会为每个 subsystem 组合创建一个 active hierarchy
。挂载时先检查 -o 指定的 subsystem 组合是否已经有对应的 active hierarchy,如果已有,就直接复用。
从用户的角度看,就是一个 subsystem 组合可以多次挂载到不同的目录中,但是这些目录中的内容是完全相同的,因为关联的是同一个 active hierarchy。
# 将 memory subsystem 再次挂载到一个新目录
$ mkdir -p /demo/cgroups/v1/memory2
$ mount -t cgroup -o memory none /demo/cgroups/v1/memory2
# 在 /demo/cgroups/v1/memory 创建子目录 example-mem
$ mkdir /demo/cgroups/v1/memory/example-mem
# 在 /demo/cgroups/v1/memory2 中同时出现 example-mem
$ ls /demo/cgroups/v1/memory2/ |grep example-mem
example-mem
一个 subsystem 只能在所有的 active hierarchy 中出现一次,比如 cpu 和 cpuacct 已经出现在 cpu,cpuacct 组合对应的 active hierarchy,不能再出现在别的组合中:
# cpu 不能出现的新的 subsystem 组合中,无论是单独成组还是和其它 subsystem 组合
# cpu 单独成组
$ mkdir -p /demo/cgroups/v1/cpu2
$ mount -t cgroup -o cpu none /demo/cgroups/v1/cpu2
mount: none already mounted or /demo/cgroups/v1/cpu2 busy
# cpu 和 memory 组合成组
$ mkdir -p /demo/cgroups/v1/cpu,memory
$ mount -t cgroup -o cpu,memory none /demo/cgroups/v1/cpu,memory
mount: none already mounted or /demo/cgroups/v1/cpu,memory busy
可以在 -o 中用 “name=xxx” 为 subsystem 组合命名,命名后可以直接通过名称重复挂载:
# 指定 active hierarchy 名称 devicesA
$ mount -t cgroup -o devices,name=devicesA none /demo/cgroups/v1/devicesA/
# 直接用 name=devicesA 重复挂载 devices subsystem
$ mount -t cgroup -o name=devicesA none /demo/cgroups/v1/devicesA-1
# devicesA 和 devicesA-1 内容相同
$ ls /demo/cgroups/v1/devicesA
cgroup.clone_children cgroup.event_control cgroup.procs cgroup.sane_behavior devices.allow devices.deny devices.list notify_on_release release_agent tasks
$ ls /demo/cgroups/v1/devicesA-1
cgroup.clone_children cgroup.event_control cgroup.procs cgroup.sane_behavior devices.allow devices.deny devices.list notify_on_release release_agent tasks
如果没有为 subsystem 组合命名,重复挂载时只能指定具体组合,排列顺序可以不同。
# cpuacct,cpu 和 cpu,capuacct 是同一个 active hierarchy
$ mkdir -p /demo/cgroups/v1/cpuacct,cpu/
$ mount -t cgroup -o cpuacct,cpu none /demo/cgroups/v1/cpuacct,cpu/
有命名的 subsystem 组合,重复挂载时依然可以指定具体组合。但是如果带有命名,name 必须正确。
# 已经有 name 的组合,不指定 name 挂载
$ mkdir -p /demo/cgroups/v1/devicesA-2/
$ mount -t cgroup -o devices none /demo/cgroups/v1/devicesA-2/
# 已经有 name 的组合,指定 name 挂载,但是 name 不正确
$ mkdir -p /demo/cgroups/v1/devicesA-3
$ mount -t cgroup -o devices,name=devicesB none /demo/cgroups/v1/devicesA-3/
mount: none already mounted or /demo/cgroups/v1/devicesA-3/ busy
cgroups v1 的虚拟文件系统中默认包含控制组的文件接口,如果绑定了 subsystem,还会包含对应 subsystem 的文件接口。
挂载的时候用 -o none
表明不关联任何 subsystem,目录中将只存在控制组的文件接口。
$ mkdir -p /demo/cgroups/v1/pure-cgroups
# 不关联 subsystem 时,必须用 name 命名
$ mount -t cgroup -o none,name=pure-cgroups pure-cgroups /demo/cgroups/v1/pure-cgroups/
不关联 subsystem 时,必须用 name 命名,否则会出现下面的错误:
$ mount -t cgroup -o none pure-cgroups /demo/cgroups/v1/pure-cgroups/
mount: wrong fs type, bad option, bad superblock on none,
missing codepage or helper program, or other error
(for several filesystems (e.g. nfs, cifs) you might
need a /sbin/mount.<type> helper program)
In some cases useful info is found in syslog - try
dmesg | tail or so
cgroups v1 控制组的文件接口如下:
$ ls -1 /demo/cgroups/v1/pure-cgroups/
cgroup.clone_children # flag 0/1,子目录是否继承父目录的 cpuset 配置
cgroup.event_control # 未找到用法说明 2023-01-15 12:41:35
cgroup.procs # 可编辑文件,当前分组包含的 thread group IDs,thread group 包含的所有 thread 被一同纳入
cgroup.sane_behavior # cgroups v2 开发过程中引入,当前保留是为了历史兼容,value 一直为0,详情见 man 7 cgroups
notify_on_release # flag 0/1,当前分组包含的 task 变为空时,是否调用 release_agent
release_agent # release_agent 所在的路径,只存在于 cgroup 的顶层目录
tasks # 可编辑文件,当前分组包含的 PID
新控制组的创建方法非常简单,在 cgroups v1 的虚拟文件系统中直接用 mkdir 创建子目录即可。
$ mkdir /demo/cgroups/v1/pure-cgroups/group1
$ ls /demo/cgroups/v1/pure-cgroups/group1
cgroup.clone_children cgroup.event_control cgroup.procs notify_on_release tasks
子目录中会自动出现相应的文件接口。
cgroup.procs 和 tasks 用来管理控制组包含的 task,cgroup.procs 管理的是 thread group ID,tasks 管理的是 pid。通过编辑文件内容来进行 task 的增删。
notify_on_release 设置为 1 时,所在分组中的 tasks 变为空时,会调用 release_agent 中指定的命令,入参为对应的 cgroups 目录路径。
准备文件 /root/agent.sh ,内容如下:
#!/bin/bash
echo $* >>/tmp/cgroup_release_note.log
给予执行权限:
chmod +x /root/agent.sh
为 /demo/cgroups/v1/pure-cgroups/ 配置 release_agent(release_agent 只在顶层目录中存在):
$ echo "/root/agent.sh" > /demo/cgroups/v1/pure-cgroups/release_agent
触发 release_agent 执行:
# 创建新的分组 group2
$ mkdir -p /demo/cgroups/v1/pure-cgroups/group2
# 启动 group2 的 notify_on_release
$ echo 1 >/demo/cgroups/v1/pure-cgroups/group2/notify_on_release
# 向 group2 添加 task:将当前 sh 加入 group2
echo $$ >/demo/cgroups/v1/pure-cgroups/group2/tasks
# 从 group2 移除 task:将当前 sh 移动到 group2 的父目录,移动后 group2 中的 task 为空
echo $$ >/demo/cgroups/v1/pure-cgroups/tasks
/root/agent.sh 被执行,结果如下:
$ cat /tmp/cgroup_release_note.log
/group2
挂载时用 -o 指定的 subsystem 的文件接口会出现在虚拟文件系统中,subsystem 的文件接口各不相同。
memory subsystem 的文件接口:
$ ls /demo/cgroups/v1/memory/memory.*
/demo/cgroups/v1/memory/memory.failcnt /demo/cgroups/v1/memory/memory.limit_in_bytes
/demo/cgroups/v1/memory/memory.force_empty /demo/cgroups/v1/memory/memory.max_usage_in_bytes
/demo/cgroups/v1/memory/memory.kmem.failcnt /demo/cgroups/v1/memory/memory.move_charge_at_immigrate
/demo/cgroups/v1/memory/memory.kmem.limit_in_bytes /demo/cgroups/v1/memory/memory.numa_stat
/demo/cgroups/v1/memory/memory.kmem.max_usage_in_bytes /demo/cgroups/v1/memory/memory.oom_control
/demo/cgroups/v1/memory/memory.kmem.slabinfo /demo/cgroups/v1/memory/memory.pressure_level
/demo/cgroups/v1/memory/memory.kmem.tcp.failcnt /demo/cgroups/v1/memory/memory.soft_limit_in_bytes
/demo/cgroups/v1/memory/memory.kmem.tcp.limit_in_bytes /demo/cgroups/v1/memory/memory.stat
/demo/cgroups/v1/memory/memory.kmem.tcp.max_usage_in_bytes /demo/cgroups/v1/memory/memory.swappiness
/demo/cgroups/v1/memory/memory.kmem.tcp.usage_in_bytes /demo/cgroups/v1/memory/memory.usage_in_bytes
/demo/cgroups/v1/memory/memory.kmem.usage_in_bytes /demo/cgroups/v1/memory/memory.use_hierarchy
subsystem 的文件接口和管理的资源类型相关,需要花较多时间逐个学习。
cgroup-v1 给出了部分 subsystem 的介绍:
net_cls 用于给 group 内的网络报文打上特定的 tag,使报文可以通过 iptalbes 的 -m cgroup
识别,以及用于 tc: