Linux系统中cgroups v2的深入解析与应用
1. cgroup控制器概述
cgroup控制器是内核的底层组件,负责在cgroup层次结构(一个cgroup及其后代)内分配特定资源,如CPU周期、内存和I/O带宽等。可以将其视为特定cgroup层次结构的“资源限制器”。
以下是现代Linux系统中常见的cgroups v2控制器及其功能和可用的内核版本:
| Cgroups v2控制器名称 | 启用时控制(或约束、调节)的内容 | 自内核版本 |
| — | — | — |
| cpu | CPU带宽(周期) | 4.15 |
| cpuset | CPU亲和性和内存节点放置(对大型NUMA系统特别有用) | 5.0 |
| memory | 内存(RAM)使用 | 4.5 |
| io | I/O资源的分配 | 4.5 |
| pids | cgroup中进程数量的硬限制 | 4.5 |
| devices | 设备文件的创建和访问(仅通过cgroup BPF程序) | 4.15 |
| rdma | 远程直接内存访问(RDMA)资源的分配和核算 | 4.11 |
| hugetlb | 每个cgroup的HugeTLB(大页面)使用限制 | 5.6 |
| misc | 各种;详见https://www.kernel.org/doc/html/v6.1/admin-guide/cgroup-v2.html#misc | 5.13 |
例如,PIDS控制器可通过限制从cgroup或其后代派生的进程数量来防止fork炸弹攻击。
2. 内核cgroups与用户空间的交互
内核cgroups通过专门构建的合成或伪文件系统向用户空间暴露,即cgroup文件系统,通常挂载在
/sys/fs/cgroup
。在cgroups v2中,文件系统类型为
cgroup2
,可以使用
mount | grep cgroup
命令查看。
要查看系统中启用的控制器,可以使用以下命令:
cat /sys/fs/cgroup/cgroup.controllers
输出示例:
cpuset cpu io memory hugetlb pids rdma misc
需要注意的是,
/proc/cgroups
仅与cgroups v1兼容,不适用于cgroups v2。
在cgroups v2中,所有控制器都挂载在单个层次结构中,而cgroups v1允许多个控制器挂载在多个层次结构或组下。现代的init框架
systemd
同时使用v1和v2 cgroups,并在启动时自动挂载cgroups v2文件系统。
3. 探索cgroups v2层次结构
3.1 确认挂载位置
使用以下命令确认cgroups v2层次结构的挂载位置:
mount | grep cgroup2
输出示例:
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
可以看到,它通常挂载在
/sys/fs/cgroup
。
3.2 旧发行版的处理
如果使用的是较旧的发行版(如Ubuntu 18.04),可能会存在混合的cgroups v1和v2,导致cgroup2中没有控制器。要仅使用v2版本并使所有配置的控制器可见,需要在启动时传递内核命令行参数
cgroup_no_v1=all
,然后重启系统并重新检查。而较新的发行版(如Ubuntu 22.04或Fedora 38)则无需此操作。
3.3 根cgroup下的内容
在根cgroup位置
/sys/fs/cgroup
下,可以看到许多文件和文件夹,这些都是通过sysfs挂载在RAM中的易失性伪文件对象:
-
常规文件
:如
cgroup.controllers
、
cpu.pressure
等,是cgroup2接口文件,可进一步分为核心接口和控制器接口。所有以
cgroup.*
开头的文件是核心接口文件,
cpu.*
是CPU控制器的接口文件,
memory.*
是内存控制器的接口文件等。
-
文件夹
:代表控制组或cgroups,但并非所有cgroups都受到约束。默认情况下,这些cgroups是由
systemd
在启动时设置的。
4. 启用或禁用控制器
4.1 cgroup.controllers文件
cgroup.controllers
文件列出了cgroup可用的控制器。对于根cgroup,它显示的是内核配置的控制器。默认情况下,现代发行版通常包含
cpuset cpu io memory hugetlb pids rdma misc
。
需要注意的是,控制器出现在此列表中并不意味着它在cgroup层次结构中已启用,默认情况下所有控制器都是禁用的。
4.2 启用和禁用控制器的方法
要启用控制器,将字符串
+<controller-name>
写入
cgroup.subtree_control
伪文件;要禁用控制器,则写入
-<controller-name>
。例如,要启用CPU和I/O控制器,但禁用内存控制器(对当前cgroup及其后代生效),可以使用以下命令(需以root权限执行):
echo "+cpu +io -memory" > cgroup.subtree_control
cgroup.subtree_control
文件列出了从该cgroup到其子代的资源分配中启用的控制器。
4.3 其他核心cgroup接口文件
- cgroup.events :只读文件,有两个可能的值:
-
populated:0或1。如果为1,表示cgroup或其后代包含活动进程;否则为0,表示为空或未填充的cgroup。 -
frozen:0或1。如果为1,表示cgroup被冻结;否则为0,表示正常。
例如,查看init.scopecgroup的cgroup.events文件:
cat /sys/fs/cgroup/init.scope/cgroup.events
输出示例:
populated 1
frozen 0
由于
populated
为1,说明该cgroup值得进一步研究。
-
cgroup.kill
:只写文件,写入1会导致cgroup树及其所有后代死亡,目标cgroup树中的进程将收到
SIGKILL信号。
5. cgroups层次结构中的cgroups
5.1 init.scope cgroup
以
init.scope
cgroup为例,查看其内部的文件和接口:
cd /sys/fs/cgroup/
ls init.scope/
部分输出示例:
cgroup.controllers cgroup.threads io.latency memory.high memory.swap.current
cgroup.events cgroup.type io.max memory.low memory.swap.events
cgroup.freeze cpu.idle io.pressure memory.max memory.swap.high
cgroup.kill cpu.max io.prio.class memory.min memory.swap.max
查看
init.scope
cgroup中的
cgroup.procs
文件,它显示了属于该cgroup的进程的PID列表:
cat init.scope/cgroup.procs
输出示例:
1
这表明
init.scope
cgroup只包含PID为1的init进程(即
systemd
本身)。
要将进程迁移到给定的cgroup,可以将其PID写入目标cgroup的
cgroup.procs
文件。写入者需要具有相应的权限,root用户可以执行此操作,非root用户在权限匹配的情况下也可以。
5.2 查看CPU控制器接口
使用以下命令查看
init.scope
cgroup中CPU控制器的接口和值:
grep . init.scope/cpu.*
可以方便地查看所有CPU控制器接口及其当前值。
5.3 嵌套cgroups
cgroups可以嵌套,一个cgroup可以包含其他cgroups。例如,查看
system.slice
cgroup下的子文件夹(即子cgroups):
find /sys/fs/cgroup/system.slice/ -maxdepth 1 -type d
每个cgroup下都有类似的伪文件,代表核心和控制器接口,用于限制系统资源,并且可能包含更多的cgroups。
5.4 使用systemd-cgls查看cgroup层次结构
使用
systemd-cgls
工具可以查看cgroup层次结构。例如,查看
init.scope
cgroup下的进程:
systemd-cgls /sys/fs/cgroup/init.scope
输出示例:
Directory /sys/fs/cgroup/init.scope:
└─1 /usr/lib/systemd/systemd --switched-root --system –deserialize=32
查看
system.slice
cgroup下的进程:
systemd-cgls /sys/fs/cgroup/system.slice
输出示例:
Directory /sys/fs/cgroup/system.slice:
├─abrt-journal-core.service
│ └─1386 /usr/bin/abrt-dump-journal-core -D -T -f -e
├─bolt.service
│ └─1425 /usr/libexec/boltd
├─low-memory-monitor.service
│ └─1296 /usr/libexec/low-memory-monitor
├─systemd-udevd.service …
│ └─udev
│ ├─ 688 /usr/lib/systemd/systemd-udevd
│ ├─610731 (udev-worker)
│ ├─610732 (udev-worker)
需要注意的是,只有当cgroup或其父cgroup中的
cgroup.subtree_control
文件启用了相关控制器时,cgroup的资源限制功能才会生效。
6. systemd与cgroups
6.1 systemd的作用
手动管理cgroups可能是一项艰巨的任务,而强大的
systemd
init框架可以在启动时创建和管理cgroups,自动利用其功能为用户和应用程序带来好处。
6.2 可视化工具
有多种工具可以帮助可视化系统中定义的cgroups(以及切片和作用域),包括
ps
、
systemd-cgls
、
systemctl
和
systemd-cgtop
。
6.3 切片和作用域
systemd
通过定义和使用切片(slice)和作用域(scope)来逻辑地组织进程。切片用于表示属于特定用户的所有进程,或一组通过该单元管理资源的应用程序。作用域是对切片的进一步逻辑划分。
例如,所有在终端窗口中运行的进程通常会被
systemd
分组到一个
session-<number>.scope
cgroup中。每个登录系统的用户会在
user.slice
节点下显示为一个“切片”,他们运行的应用程序将显示在该cgroup下。
6.4 更改默认设置的方法
如果想更改
systemd
设置cgroups的默认方式,有三种主要方法:
1. 手动编辑服务单元文件。
2. 使用
systemctl set-property
子命令进行编辑。
3. 在
systemd
目录结构中使用所谓的“drop-in”文件。
例如,查看
/usr/lib/systemd/system/user@.service
文件:
cat /usr/lib/systemd/system/user@.service
部分内容示例:
[Unit]
Description=User Manager for UID %i
Documentation=man:user@.service(5)
After=user-runtime-dir@%i.service dbus.service systemd-oomd.service
Requires=user-runtime-dir@%i.service
IgnoreOnIsolate=yes
[Service]
User=%i
PAMName=systemd-user
Type=notify-reload
ExecStart=/usr/lib/systemd/systemd --user
Slice=user-%i.slice
KillMode=mixed
Delegate=pids memory cpu
TasksMax=infinity
7. 使用systemctl和systemd-cgtop可视化cgroups
7.1 使用systemctl
systemd
定义了多种单元类型,其中
slice
和
scope
与cgroups相关。可以使用
systemctl
命令查看它们:
systemctl list-units --type=slice,scope
7.2 使用systemd-cgtop
systemd-cgtop
工具可以可视化cgroups层次结构,并在运行时观察哪些cgroups(以及其中的切片/服务)占用了大部分资源。默认情况下,输出按CPU负载排序。
以下是
systemd-cgtop
的一些常用选项:
| 选项 | 描述 |
| — | — |
|
-h --help
| 显示帮助信息 |
|
--version
| 显示软件包版本 |
|
-p --order=path
| 按路径排序 |
|
-t --order=tasks
| 按任务/进程数量排序 |
|
-c --order=cpu
| 按CPU负载排序(默认) |
|
-m --order=memory
| 按内存负载排序 |
|
-i --order=io
| 按I/O负载排序 |
|
-r --raw
| 提供原始(非人类可读)数字 |
|
--cpu=percentage
| 以百分比显示CPU使用率(默认) |
|
--cpu=time
| 以时间显示CPU使用率 |
|
-P
| 计算用户空间进程而不是任务(不包括内核) |
|
-k
| 计算所有进程而不是任务(包括内核) |
|
--recursive=BOOL
| 递归计算进程数量 |
|
-d --delay=DELAY
| 更新之间的延迟 |
|
-n --iterations=N
| 运行N次迭代后退出 |
|
-1
|
--iterations=1
的快捷方式 |
|
-b --batch
| 以批处理模式运行,不接受输入 |
|
--depth=DEPTH
| 最大遍历深度(默认:3) |
|
-M --machine=
| 显示容器 |
需要注意的是,仅运行
systemd-cgtop
工具是不够的,如果cgroup上未启用资源核算(即
CPUAccounting=1
、
MemoryAccounting=1
和
BlockIOAccounting=1
未启用),它将无法提供有关资源使用情况的信息。
8. 查看进程所属的cgroup
使用
ps
命令并添加
-o cgroup
选项可以查看进程所属的cgroup:
ps fw -eo pid,user,cgroup,args
输出示例:
PID USER CGROUP COMMAND
2 root - [kthreadd]
3 root - \_ [rcu_gp]
4 root - \_ [rcu_par_gp]
5 root - \_ [slub_flushwq]
1 root 0::/init.scope /usr/lib/systemd/systemd --switched-root --system [...]
1096 root 0::/system.slice/systemd-jo /usr/lib/systemd/systemd-journald
1109 root 0::/system.slice/systemd-ud /usr/lib/systemd/systemd-udevd
1228 systemd+ 0::/system.slice/systemd-oo /usr/lib/systemd/systemd-oomd
1229 systemd+ 0::/system.slice/systemd-re /usr/lib/systemd/systemd-resolved
可以看到,内核线程不属于任何cgroup,而其他进程则被分配到相应的cgroup中。
当用户启动新进程时,
systemd
会将其放置在适当的切片和作用域中,从而纳入相应的cgroup进行管理。例如,运行
vim
编辑文件后,使用
ps
命令可以看到
vim
进程已成为用户切片的一部分:
ps fw -eo pid,user,cgroup,args | grep "[v]im"
输出示例:
1378003 kaiwan 0::/user.slice/user-1000.sl \_ vim cgroupsv2_explore
如果系统未运行
systemd
,则需要手动管理cgroups。除了
systemd
,还有其他工具可用于cgroup管理,如
cg*
工具套件(通过
cgroup-tools/libcgroup
软件包安装,包含
cgcreate
、
cgexec
和
cgclassify
等工具)。
9. 内核命名空间简介
容器技术主要基于Linux内核中的两个关键技术:cgroups和命名空间。可以将容器视为轻量级的虚拟机。内核命名空间是实现容器概念的重要结构,通过它,内核可以对资源进行分区,使不同命名空间中的进程看到不同的值。
常见的内核命名空间及其提供的功能如下:
| 命名空间 | 提供的功能 |
| — | — |
| Mount | 每个挂载命名空间都有自己的文件系统布局(因此
/proc
在不同挂载命名空间中的内容不同) |
| PID | 进程隔离(每个命名空间可以有自己的PID 1进程) |
| Network | 网络隔离 |
| UTS | 域名和主机名隔离 |
| IPC | IPC资源(共享内存、消息队列和信号量)可以隔离 |
| User | 用户ID隔离(允许不同命名空间中的进程具有相同的UID/GID) |
在Linux中,
clone()
系统调用的
CLONE_NEW*
标志可用于在内核中创建新命名空间的进程。其他与命名空间相关的系统调用包括
setns()
、
unshare()
和
ioctl_ns()
。
10. 总结与操作流程回顾
10.1 关键操作总结
为了更好地理解和运用上述知识,我们将关键操作总结如下:
| 操作目的 | 操作命令及说明 |
| — | — |
| 查看系统启用的控制器 |
cat /sys/fs/cgroup/cgroup.controllers
,输出为以空格分隔的可用控制器列表 |
| 确认cgroups v2挂载位置 |
mount | grep cgroup2
,通常挂载在
/sys/fs/cgroup
|
| 启用或禁用控制器 | 以root权限执行
echo "+<controller-name> +<controller-name> -<controller-name>" > cgroup.subtree_control
,如
echo "+cpu +io -memory" > cgroup.subtree_control
|
| 查看cgroup内进程PID列表 |
cat <cgroup-name>/cgroup.procs
,如
cat init.scope/cgroup.procs
|
| 迁移进程到指定cgroup | 将进程PID写入目标cgroup的
cgroup.procs
文件,需相应权限 |
| 查看CPU控制器接口值 |
grep . <cgroup-name>/cpu.*
,如
grep . init.scope/cpu.*
|
| 查看cgroup层次结构 |
systemd-cgls <cgroup-path>
,如
systemd-cgls /sys/fs/cgroup/init.scope
|
| 查看进程所属cgroup |
ps fw -eo pid,user,cgroup,args
|
10.2 操作流程示例
下面是一个简单的操作流程示例,展示如何查看和管理cgroups:
graph LR
A[开始] --> B[查看系统启用的控制器]
B --> C[确认cgroups v2挂载位置]
C --> D{是否为旧发行版}
D -- 是 --> E[传递内核参数并重启]
D -- 否 --> F[查看根cgroup下内容]
E --> F
F --> G[启用或禁用控制器]
G --> H[查看cgroup内进程PID列表]
H --> I[迁移进程到指定cgroup]
I --> J[查看CPU控制器接口值]
J --> K[查看cgroup层次结构]
K --> L[查看进程所属cgroup]
L --> M[结束]
11. 实际应用场景分析
11.1 资源限制场景
在多用户或多应用的服务器环境中,为了避免某个用户或应用占用过多资源影响其他服务的正常运行,可以使用cgroups进行资源限制。例如,通过启用
cpu
和
memory
控制器,对特定用户或应用所在的cgroup进行CPU和内存使用限制。
# 启用CPU和内存控制器
echo "+cpu +memory" > /sys/fs/cgroup/myapp/cgroup.subtree_control
# 设置CPU最大带宽
echo "100000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 设置内存最大使用量
echo "512M" > /sys/fs/cgroup/myapp/memory.max
11.2 容器化场景
在容器化应用部署中,cgroups和命名空间是实现容器隔离和资源管理的关键技术。通过
systemd
或其他容器管理工具,可以为每个容器创建独立的cgroup,对其CPU、内存、I/O等资源进行精确控制。例如,使用
docker
创建容器时,会自动为容器分配一个cgroup,并根据配置对资源进行限制。
# 创建一个限制CPU和内存使用的容器
docker run -it --cpus=0.5 --memory=256M ubuntu:latest /bin/bash
11.3 性能监控场景
使用
systemd-cgtop
工具可以实时监控cgroups的资源使用情况,帮助管理员及时发现资源使用异常的cgroup和进程。例如,通过按CPU负载排序查看哪些cgroup占用了大量CPU资源。
systemd-cgtop -c
12. 注意事项与技巧
12.1 权限问题
在进行cgroups操作时,很多操作需要root权限。例如,写入
cgroup.subtree_control
文件、迁移进程到指定cgroup等操作,普通用户需要相应权限才能执行。可以使用
sudo
命令以root权限执行操作。
12.2 正则表达式的使用
在使用
grep
命令过滤进程时,使用正则表达式可以避免显示
grep
命令本身。例如,使用
grep "[v]im"
而不是
grep "vim"
来查找
vim
进程。
12.3 资源核算问题
使用
systemd-cgtop
工具时,需要确保cgroup上启用了资源核算(即
CPUAccounting=1
、
MemoryAccounting=1
和
BlockIOAccounting=1
),否则无法获取准确的资源使用信息。可以在
systemd
服务单元文件中添加相应配置来启用资源核算。
[Service]
CPUAccounting=yes
MemoryAccounting=yes
BlockIOAccounting=yes
13. 未来发展趋势
随着云计算、容器化和微服务等技术的不断发展,cgroups和命名空间在系统资源管理和应用隔离方面的作用将越来越重要。未来,可能会出现更强大的cgroup管理工具和更精细的资源分配策略,以满足不断增长的复杂应用场景需求。同时,内核命名空间的功能也可能会进一步扩展,提供更完善的隔离和安全机制。
14. 总结
本文深入介绍了Linux系统中cgroups v2的相关知识,包括cgroup控制器、层次结构、启用和禁用控制器的方法、与
systemd
的结合使用、内核命名空间以及可视化工具等内容。通过实际操作命令和示例,展示了如何查看、管理和监控cgroups。同时,分析了cgroups在不同场景下的应用和注意事项。希望读者通过本文的学习,能够更好地理解和运用cgroups技术,提高系统资源管理的效率和安全性。
超级会员免费看
1304

被折叠的 条评论
为什么被折叠?



