2.CGroup
概述
参考:
Control Groups(控制组,简称 CGroups) 是一个 Linux 内核特性,用于限制、隔离一组进程集合的资源使用,资源包括 CPU、内存、磁盘 IO、网络 等。CGroups 由 Google 的两位工程师开发,自 2008 年 1 月发布的 Linux 2.6.24 版本的内核中提供此能力。到目前为止,CGroups 分 v1 和 v2 两个版本,v1 实现较早,功能比较多,但是由于它里面的功能都是零零散散的实现的,所以规划的不是很好,导致了一些使用和维护上的不便,v2 的出现就是为了解决 v1 中这方面的问题,在最新的 4.5 内核中,cgroup v2 声称已经可以用于生产环境了,但它所支持的功能还很有限,随着 v2 一起引入内核的还有 cgroup namespace。v1 和 v2 可以混合使用,但是这样会更复杂,所以一般没人会这样用。
在 Linux 里,一直以来就有对进程进行分组的概念和需求,比如 session group, progress group 等,后来随着人们对这方面的需求越来越多,比如需要追踪一组进程的内存和 IO 使用情况等,于是出现了 cgroup,用来统一将进程进行分组,并在分组的基础上对进程进行监控和资源控制管理等。
Cgroup 是 Linux kernel 的一项功能:它是在一个系统中运行的层级制进程组,你可对其进行资源分配(如 CPU 时间、系统内存、网络带宽或者这些资源的组合)。通过使用 cgroup,系统管理员在分配、排序、拒绝、管理和监控系统资源等方面,可以进行精细化控制。硬件资源可以在应用程序和用户间智能分配,从而增加整体效率。
cgroup 和 namespace 类似,也是将进程进行分组,但它的目的和 namespace 不一样,namespace 是为了隔离进程组之间的资源,而 cgroup 是为了对一组进程进行统一的资源监控和限制。CGroup 还能对进程进行优先级设置、审计、以及将进程挂起和恢复等操作
术语
cgroup 在不同的上下文中代表不同的意思,可以指整个 Linux 的 cgroup 技术,也可以指一个具体进程组。
cgroup 是 Linux 下的一种将进程按组进行管理的机制,在用户层看来,cgroup 技术就是把系统中的所有进程组织成一颗一颗独立的树,每棵树都包含系统的所有进程,树的每个节点是一个进程组,而每颗树又和一个或者多个 subsystem 关联,树的作用是将进程分组,而 subsystem 的作用就是对这些组进行操作。
cgroup 主要包括下面两部分:
- subsystem(子系统) # 一个 subsystem 就是一个内核模块,他被关联到一颗 cgroup 树之后,就会 在树的每个节点(进程组)上做具体的操作。subsystem 经常被称作 resource controller,因为它主要被用来调度或者限制每个进程组的资源,但是这个说法不完全准确,因为有时我们将进程分组只是为了做一些监控,观察一下他们的状态,比如 perf_event subsystem。到目前为止,Linux 支持 12 种 subsystem,比如限制 CPU 的使用时间,限制使用的内存,统计 CPU 的使用情况,冻结和恢复一组进程等,后续会对它们一一进行介绍。
- hierarchy(层次结构) # 一个 hierarchy 可以理解为一棵 cgroup 树,树的每个节点就是一个进程组,每棵树都会与零到多个 subsystem 关联。在一颗树里面,会包含 Linux 系统中的所有进程,但每个进程只能属于一个节点(进程组)。系统中可以有很多颗 cgroup 树,每棵树都和不同的 subsystem 关联,一个进程可以属于多颗树,即一个进程可以属于多个进程组,只是这些进程组和不同的 subsystem 关联。目前 Linux 支持 12 种 subsystem,如果不考虑不与任何 subsystem 关联的情况(systemd 就属于这种情况),Linux 里面最多可以建 12 颗 cgroup 树,每棵树关联一个 subsystem,当然也可以只建一棵树,然后让这棵树关联所有的 subsystem。当一颗 cgroup 树不和任何 subsystem 关联的时候,意味着这棵树只是将进程进行分组,至于要在分组的基础上做些什么,将由应用程序自己决定,systemd 就是一个这样的例子。
CGroup 子系统类型
可以通过 /proc/cgroups 文件查看当前系统支持哪些 subsystem:
~]# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 6 5 1
cpu 8 95 1
cpuacct 8 95 1
blkio 4 95 1
memory 12 236 1
devices 11 95 1
freezer 9 5 1
net_cls 10 5 1
perf_event 5 5 1
net_prio 10 5 1
hugetlb 2 5 1
pids 3 103 1
rdma 7 1 1
整理一下:
| subsys_name | hierarchy | num_cgroups | enabled |
|---|---|---|---|
| cpuset | 6 | 5 | 1 |
| cpu | 8 | 95 | 1 |
| cpuacct | 8 | 95 | 1 |
| blkio | 4 | 95 | 1 |
| memory | 12 | 236 | 1 |
| devices | 11 | 95 | 1 |
| freezer | 9 | 5 | 1 |
| net_cls | 10 | 5 | 1 |
| perf_event | 5 | 5 | 1 |
| net_prio | 10 | 5 | 1 |
| hugetlb | 2 | 5 | 1 |
| pids | 3 | 103 | 1 |
| rdma | 7 | 1 | 1 |
从左到右,字段的含义分别是:
- subsys_name # subsystem 的名字
- blkio # 块设备 IO
- cpu # 基于 CFS 对 CPU 时间配额进行限制的子系统,CFS 概念详见:CPU 管理 章节中的 CFS 调度器。该子系统是 cgroup 对进程使用 CPU 资源进行限制的主要手段
- cpuacct # CPU 资源使用报告
- cpuset # 多处理器平台上的 CPU 集合
- devices # 设备访问
- freezer # 挂载器或恢复任务
- hungetlb #
- memory # 内存用量及报告
- net_cls # cgroup 中的任务创建的数据包的类别标识符
- net_prio #
- perf_event # 对 cgroup 中的任务进行统一性能测试
- pids #
- rdma #
- hierarchy # subsystem 所关联到的 cgroup 树的 ID,如果多个 subsystem 关联到同一颗 cgroup 树,那么他们的这个字段将一样,比如这里的 cpu 和 cpuacct 就一样,表示他们绑定到了同一颗树。如果出现下面的情况,这个字段将为 0:
- 当前 subsystem 没有和任何 cgroup 树绑定
- 当前 subsystem 已经和 cgroup v2 的树绑定
- 当前 subsystem 没有被内核开启
- num_cgroups # subsystem 所关联的 cgroup 树中进程组的个数,也即树上节点的个数
- enabled # 1 表示开启,0 表示没有被开启(可以通过设置内核的启动参数“cgroup_disable”来控制 subsystem 的开启).
CGroup 关联文件
/sys/fs/cgroup/ - CGroup 根目录
CGroup 的相关操作都是基于内核中的 CGroup Virtual Filesystem(控制组虚拟文件系统)。所以,使用 CGroup 首先需要挂载这个文件系统,通常,现代系统在启动时,都默认会挂载相关的 CGroup 文件系统:
- CGroupV1 该目录下的每个目录都是 CGroup 子系统的名称。其中包含该子系统中所关联的进程的资源控制信息。
~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
...... 略
- CGroupV2,则只会有一个 cgroup2 on /sys/fs/cgroup type cgroup2 (……) 的挂载项
~]# mount -t cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
这里面的 /sys/fs/cgroup/ 目录,就称为 CGroup 的根目录。CGroup 文件系统的 V1 与 V2 的根目录下的内容,各不相同,详见 CGroup FS 章节
/proc/PID/cgroup - 进程号为 PID 的进程所属的 cgroup 信息
在 /proc/PID/cgroup 文件中会指定进程所使用的 CGropu 的相对路径。文件中每行都是进程所属的 CGroup 子系统,每行子系统信息由以 : 分割的三个字段组成
- hierarchy-ID # Hierarchy 唯一标识符。与 /proc/cgroups 文件中的 Hierarchy ID 相同。
- CGroup v2 版本该字段始终为 0
- controller-list # 绑定到 Hierarchy ID 的控制器列表。也就是 CGroup 的子系统。
- CGroup v2 版本该字段为空
- cgroup-path # 进程所属 CGroup 子系统的信息的路径。这是一个相对路径。
- 这里面的
/就是指 CGroup 的根节点中对应子系统的目录- 对于 CGroupV1 来说通常是 /sys/fs/cgroup/SUBSYSTEM。所以,一个完整的 cgroup-path 应该是
/sys/fs/cgroup/SUBSYSTEM/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope/*
- 对于 CGroupV1 来说通常是 /sys/fs/cgroup/SUBSYSTEM。所以,一个完整的 cgroup-path 应该是
- 这里面的
cgroup 的 v1 和 v2 版本显示的信息不同
CGroupV1
~]# cat /proc/1185/cgroup
12:memory:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
11:devices:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
10:net_cls,net_prio:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
9:freezer:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
8:cpu,cpuacct:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
7:rdma:/
6:cpuset:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
5:perf_event:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
4:blkio:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
3:pids:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
2:hugetlb:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
1:name=systemd:/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
0::/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
比如,1185 进程的 cpu 子系统的 CGroup 信息,就在 /sys/fs/cgroup/cpu/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope/ 目录中:
~]# ls /sys/fs/cgroup/cpu/system.slice/docker-b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460.scope
cgroup.clone_children cpuacct.usage cpuacct.usage_percpu_sys cpuacct.usage_user cpu.shares cpu.uclamp.min
cgroup.procs cpuacct.usage_all cpuacct.usage_percpu_user cpu.cfs_period_us cpu.stat notify_on_release
cpuacct.stat cpuacct.usage_percpu cpuacct.usage_sys cpu.cfs_quota_us cpu.uclamp.max tasks
CGropuV2
~]# cat /proc/1277/cgroup
0::/system.slice/docker-020cfdfbd4cd43981570f4fa7def9a2b600025b2e60e3150e742a5049562f30f.scope
比如,1277 进程的 CGroup 信息,就在 /sys/fs/cgroup/system.slice/docker-020cfdfbd4cd43981570f4fa7def9a2b600025b2e60e3150e742a5049562f30f.scope/ 目录中:
~]# ls /sys/fs/cgroup/system.slice/docker-020cfdfbd4cd43981570f4fa7def9a2b600025b2e60e3150e742a5049562f30f.scope/
cgroup.controllers cgroup.procs cpu.max cpuset.mems cpu.weight io.weight memory.low memory.stat rdma.max
cgroup.events cgroup.stat cpu.pressure cpuset.mems.effective cpu.weight.nice memory.current memory.max pids.current
cgroup.freeze cgroup.subtree_control cpuset.cpus cpu.stat io.max memory.events memory.min pids.events
cgroup.max.depth cgroup.threads cpuset.cpus.effective cpu.uclamp.max io.pressure memory.events.local memory.oom.group pids.max
cgroup.max.descendants cgroup.type cpuset.cpus.partition cpu.uclamp.min io.stat memory.high memory.pressure rdma.current
其他文件
/proc/cgroups # 当前系统支持的所有 CGroup 子系统
systemd 的 slice 单元
在 Systemd 作为 1 号进程的系统中,进程的 CGroup 都可以配置为由 Systemd 管理,其中 Slice 类型的单元就是用来控制 CGroup 的。默认会创建 3 个顶级 Slice
- system.slice # 所有 Service Unit 的默认。
- user.lice # 所有用户进程的默认。
- machine.slice # 所有虚拟机和容器的默认。
systemd-cgls 命令可以查看 CGroup 的层次结构
~]# systemd-cgls
Control group /:
-.slice
├─931 bpfilter_umh
├─user.slice
│ └─user-1000.slice
│ ├─user@1000.service …
│ │ └─init.scope
│ │ ├─81271 /lib/systemd/systemd --user
│ │ └─81276 (sd-pam)
│ ├─session-431.scope
│ │ ├─81902 sshd: desistdaydream [priv]
│ │ ├─81998 sshd: desistdaydream@pts/1
│ │ ├─82001 -bash
│ │ ├─82100 su - root
│ │ ├─82101 -bash
│ │ ├─82697 systemd-cgls
│ │ └─82698 pager
│ └─session-432.scope
│ ├─82013 sshd: desistdaydream [priv]
│ ├─82097 sshd: desistdaydream@notty
│ └─82098 /usr/lib/openssh/sftp-server
├─init.scope
│ └─1 /sbin/init nospectre_v2 nopti noibrs noibpb
└─system.slice
├─irqbalance.service
│ └─524 /usr/sbin/irqbalance --foreground
├─uniagent.service
│ └─537 /usr/local/uniagent/bin/uniagent
├─containerd.service …
│ ├─ 714 /usr/bin/containerd
│ ├─ 1140 /usr/bin/containerd-shim-runc-v2 -namespace moby -id b8f92f970f0d17377e7ad4c9b75f8316cdb15a6dd7dd81466f415e6fcaed6460 -address /run/containerd/containerd.sock
│ ├─31778 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 4c5ec4bc9717bb9fd2a2ea7b507ac3c0e16da95fa87974152f0fe3b3a653cef9 -address /run/containerd/containerd.sock
......
systemd-cgtop 命令可以查看 CGroup 的动态信息。
Control Group Tasks %CPU Memory Input/s Output/s
/ 221 1.0 3.1G - -
user.slice 11 0.7 1.5G - -
system.slice 139 0.4 1.2G - -
system.slice/containerd.service 46 0.2 276.0M - -
system.slice/cloudResetPwdUpdateAgent.service 18 0.2 102.4M - -
system.slice/docker-4c5ec4…d2a2ea7b507ac3c0e16da95fa87974152f0fe3b3a653cef9.scope 1 0.1 1.3M - -
system.slice/multipathd.service 7 0.0 13.8M - -
init.scope 1 - 7.6M - -
system.slice/ModemManager.service 3 - 6.8M - -
system.slice/NetworkManager.service 3 - 13.6M - -
system.slice/accounts-daemon.service 3 - 6.5M - -
CGroupV2
检查 cgroup v2 是否已启用
如果 /sys/fs/cgroup/cgroup.controllers 存在于系统上,则使用的是 v2,否则使用的是 v1。
已知以下发行版默认使用 cgroup v2:
- Fedora(31 起)
- Arch Linux(自 2021 年 4 月起)
- openSUSE Tumbleweed(自 2021 年起)
- Debian GNU/Linux(从 11 开始)
- Ubuntu(自 21.10 起)
启用 cgroup v2
为容器启用 cgroup v2 需要内核 4.15 或更高版本。建议使用内核 5.2 或更高版本。
然而,将 cgroup v2 控制器委派给非 root 用户需要最新版本的 systemd。建议使用 systemd 244 或更高版本。
要使用 cgroup v2 引导主机,请将以下字符串添加到 GRUB_CMDLINE_LINUX 行/etc/default/grub,然后运行 sudo update-grub.
systemd.unified_cgroup_hierarchy=1
启用 CPU、CPUSET 和 I/O 委派
默认情况下,非 root 用户只能获取 memory 控制器和 pids 要委托的控制器。
$ cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers
memory pids
要允许委派其他控制器,例如 cpu、cpuset 和 io,请运行以下命令:
$ sudo mkdir -p /etc/systemd/system/user@.service.d
$ cat <<EOF | sudo tee /etc/systemd/system/user@.service.d/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids
EOF
$ sudo systemctl daemon-reload
建议使用委派 cpuset 以及 cpu. 委派 cpuset 需要 systemd 244 或更高版本。
更改 systemd 配置后,您需要重新登录或重新启动主机。建议重启主机。
反馈
此页是否对你有帮助?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.