帮你快速理解、总结文档立即下载

cgroup 资源限制

最近更新时间:2026-08-24 18:27:30
我的收藏
通过 cgroup 对 Linux 进程的 CPU、内存等核心资源进行精细化配额管控,从根源避免业务进程资源抢占导致的系统雪崩。

cgroup 概述

cgroup(Control Group)是 Linux 内核提供的一种资源管理机制,它将进程组织成层次结构,并对每个层次结构中的进程分配资源限制。通过 cgroup,可以实现资源限制、调整资源访问优先级、监控和报告资源使用情况、批量控制所有进程的状态等功能。
cgroup 通过层级结构组织在一起,然后对进程进行资源控制,形成多对多的关系,如下图所示。

cgroup 子系统(最上层):针对每种可以控制的资源,如 cpu、memory、devices 子系统等。每个 cgroup 子系统只能 attach 到一个层级结构:cgroup 子系统针对特定资源进行控制和监视,如果被 attach 到多个层级结构,会导致资源分配和限制的冲突,从而影响系统的稳定性和可靠性。
cgroup 层级结构(中间层):可以 attach 一个或者几个 cgroup 子系统,并对其 attach 的 cgroup 子系统进行资源的限制。
进程与 css_set(最底层):每一个进程指向一个 css_set(cgroups subsystem set),且只能隶属于一个 css_set;一个 css_set 可以包含多个进程,隶属于同一 css_set 的进程受到该 css_set 所关联的资源限制。

环境默认设置

cgroup 版本

默认提供 cgroup 能力。您可以通过如下命令查询是否支持:
检查内核是否启用 ‌cgroup 基础框架‌
grep CONFIG_CGROUPS=y /boot/config-$(uname -r)
返回结果如下:
CONFIG_CGROUPS=y
检查内核是否启用 ‌cgroup 调度器支持‌。
grep CONFIG_CGROUP_SCHED=y /boot/config-$(uname -r)
返回结果如下:
CONFIG_CGROUP_SCHED=y

TencentOS Server 4 默认使用 cgroup v2,TencentOS Server 3 默认使用 cgroup v1。
您可通过以下命令查询目前 cgroup 版本:
stat -fc %T /sys/fs/cgroup/
不同 cgroup 版本的返回结果如下:
cgroup2fs # 当前使用 cgroup v2
tmpfs # 当前使用 cgroup v1
cgroup v2 的目录结构如下:
ls /sys/fs/cgroup/
返回结果如下:
cgroup.controllers cgroup.threads init.scope memory.numa_stat sys-kernel-debug.mount
cgroup.max.depth cpu.pressure io.cost.model memory.pressure sys-kernel-tracing.mount
cgroup.max.descendants cpuset.cpus.effective io.cost.qos memory.reclaim system.slice
cgroup.pressure cpuset.mems.effective io.pressure memory.stat user.slice
cgroup.procs cpu.stat io.prio.class misc.capacity
cgroup.stat dev-hugepages.mount io.stat sys-fs-fuse-connections.mount
cgroup.subtree_control dev-mqueue.mount irq.pressure sys-kernel-config.mount
cgroup v1 的目录结构如下:
ls /sys/fs/cgroup/
返回结果如下:
blkio cpuacct cpuset freezer memory net_cls net_prio pids systemd
cpu cpu,cpuacct devices hugetlb misc net_cls,net_prio perf_event rdma unified
警告:切换 cgroup 版本需要修改内核启动参数并重启系统,属于高风险操作。切换前请确认业务对 cgroup 版本的兼容性,并做好回退预案。

cgroup 子系统

查询 /proc/cgroups 确认当前 cgroup 支持的子系统:
cat /proc/cgroups
返回结果如下,目前默认支持的子系统如下所示:
#subsys_name hierarchy num_cgroups enabled
cpuset 0 132 1
cpu 0 132 1
cpuacct 0 132 1
blkio 0 132 1
memory 0 132 1
devices 0 132 1
freezer 0 132 1
net_cls 0 132 1
perf_event 0 132 1
net_prio 0 132 1
hugetlb 0 132 1
pids 0 132 1
rdma 0 132 1
misc 0 132 1
查询 /sys/fs/cgroup/cgroup.controllers 确认当前 cgroup 层次结构中已启用的子系统:
cat /sys/fs/cgroup/cgroup.controllers
返回结果如下:
cpuset cpu io memory hugetlb pids rdma misc
通过 mount 命令向指定挂载点添加系统支持且未启用的子系统:
mkdir -p /mnt/cgroups/freezer
mount -t cgroup -o freezer freezer /mnt/cgroups/freezer
ls /mnt/cgroups/freezer/
返回结果如下:
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
通过 lscgroup 确认子系统已添加:
lscgroup | grep freezer
返回结果如下:
freezer:/
如果不再需要可以通过 umount 命令卸载:
umount /mnt/cgroups/freezer

cgroup 使用说明

使用 systemd 操作 cgroup

systemd 通过将 cgroup 层级结构与 systemd unit 树绑定,将资源管理简化到应用程序级别。用户可以直接通过 systemctl 指令或修改 systemd unit 的配置文件,来管理 unit 相关的资源。
在资源管控方面,systemd 提供了三种 unit 类型:
service:一个或一组进程,由 systemd 依据 unit 配置文件启动。service 对指定进程进行封装,使进程可以作为一个整体被启动或终止。
scope:一组外部创建的进程。这些进程通过 fork() 函数启动和终止,之后由 systemd 在运行时注册并封装。例如,用户会话、容器和虚拟机通常被视为 scope。
slice:一组按层级排列的 unit。slice 本身不包含进程,而是组建一个层级结构,将 scope 和 service 放置其中。真正的进程包含在 scope 或 service 中。在这一层级树中,每一个 slice 单元的名称对应通向层级中某个位置的路径。

查看 cgroup 的层级结构

通过 systemd-cgls 命令可以查看 cgroup 的层级结构:
systemd-cgls
返回结果如下:
Control group /:
-.slice
├─user.slice (#1203)
│ → user.invocation_id: f7d3942d34cd4635a9a1567f35cf247a
│ → trusted.invocation_id: f7d3942d34cd4635a9a1567f35cf247a
│ └─user-0.slice (#41553)
│ → user.invocation_id: d1ff03802e014648a616ac2bdafbb73a
...
├─init.scope (#25)
│ └─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
└─system.slice (#65)
├─rngd.service (#2701)
│ → user.invocation_id: eb2c44177aec4afc8095560dc6269402
│ → trusted.invocation_id: eb2c44177aec4afc8095560dc6269402
│ └─674 /usr/sbin/rngd -f -x pkcs11 -x nist
...

启动 service

通过 systemd-run 命令可以创建、启动临时 service 或 scope 单位,并在此单位中运行自定义指令,格式如下:
systemd-run --unit=name --scope --slice=<slice_name> command
如果未指定 slice 则默认属于 system.slice

设置 service 属性

服务启动后,就可以使用 systemctl set-property 来修改 cgroup:
systemctl set-property name parameter=value
与 cgroup v1 相比,v2 的 property 有许多调整,可通过如下命令来查询当前支持的 property 具体类型:
man 5 systemd.resource-control

libcgroup 工具

libcgroup 是当前版本管理 cgroup 的主要工具之一,您可以通过如下命令安装,tools 子包会把主包也依赖进来安装到环境上:
dnf install -y libcgroup-tools

显示 cgroup 层次结构

libcgroup 提供了 lscgroup 来显示 cgroup 层次结构,可以直接列出当前全量的已启用 cgroup 子系统信息:
lscgroup
返回结果如下:
cpuset,cpu,io,memory,hugetlb,pids,rdma,misc:/
cpuset,cpu,io,memory,hugetlb,pids,rdma,misc:/sys-fs-fuse-connections.mount
cpuset,cpu,io,memory,hugetlb,pids,rdma,misc:/sys-kernel-config.mount
cpuset,cpu,io,memory,hugetlb,pids,rdma,misc:/sys-kernel-debug.mount
cpuset,cpu,io,memory,hugetlb,pids,rdma,misc:/dev-mqueue.mount
...
也可以指定一个子系统以及其路径:
lscgroup -g cpuset:/sys-fs-fuse-connections.mount
返回结果如下:
cpuset,cpu,io,memory,hugetlb,pids,rdma,misc:/sys-fs-fuse-connections.mount/

创建/删除控制群组

libcgroup 提供了 cgcreate/cgdelete 这一对命令用来创建/删除控制群组。
使用前,需要先挂载子系统,如:
mkdir -p /mnt/cgroups/freezer
mount -t cgroup -o freezer freezer /mnt/cgroups/freezer
使用 cgcreate 创建控制群组:
cgcreate -g freezer:/cgcreate_child
完成后,可以通过 lscgroup 命令确认:
lscgroup | grep freezer
返回结果如下:
freezer:/
freezer:/freezer
freezer:/cgcreate_child
此时,/mnt/cgroups/freezer 目录下就多了 cgcreate_child 子目录,下面有 freezer 相关的文件:
ls /mnt/cgroups/freezer/cgcreate_child/
返回结果如下:
cgroup.clone_children freezer.parent_freezing freezer.state tasks
cgroup.procs freezer.self_freezing notify_on_release
另外,也可以直接通过 mkdir 的方式替代 cgcreate
mkdir /mnt/cgroups/freezer/childgroup
通过 lscgroup 确认创建结果:
lscgroup | grep freezer
返回结果如下:
freezer:/
freezer:/freezer
freezer:/childgroup
freezer:/cgcreate_child
查看新创建的子目录内容:
ls /mnt/cgroups/freezer/childgroup
返回结果如下:
cgroup.clone_children freezer.parent_freezing freezer.state tasks
cgroup.procs freezer.self_freezing notify_on_release
如果不再需要,可以通过 cgdelete 命令删除子群组:
cgdelete freezer:/cgcreate_child
cgdelete freezer:/childgroup
确认删除结果:
lscgroup | grep freezer
返回结果如下:
freezer:/
freezer:/freezer

添加进程到控制群组

可以通过运行 cgexec 指令在手动创建的 cgroup 中启动进程。cgexec 的语法为:
cgexec -g controllers:path_to_cgroup command arguments
如果进程已启动,可以运行 cgclassify 指令将进程移动到 cgroup 中:
cgclassify -g controllers:path_to_cgroup pidlist
以将进程 15729 移动到 childgroup 中示例:
cgclassify -g freezer:/childgroup 15729
查看 childgroup 中的进程列表:
cat /mnt/cgroups/freezer/childgroup/tasks
返回结果如下:
15729

参考示例

使用 systemd-run 将服务加入 cgroup

这里通过 cgroup 的 memory 系统来举例说明。
警告:以下示例会构造内存泄漏场景并触发 OOM,仅用于演示 cgroup 内存限制的效果,请勿在生产环境执行。
1. 
编辑 demo
如下,此处构造一个内存缓慢泄漏的场景,正常情况下,这个进程运行很久才会由于 OOM 被 kill 掉:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE 1024 * 10 // 10kB
int main() {
char *ptr;
while (1) {
ptr = (char *) malloc(SIZE);
memset(ptr, 0, SIZE);
sleep (1);
}
return 0;
}
2. 通过 systemd-run 命令拉起服务:
systemd-run --unit=my_malloc --slice=test ./my_malloc
服务拉起后可以通过 systemd-cgls 命令查到服务:
systemd-cgls
返回结果如下:
Control group /:
-.slice
...
├─test.slice (#5581)
│ → user.invocation_id: 41e6db3f95094ae99a7f4d7ce471d3a1
│ → trusted.invocation_id: 41e6db3f95094ae99a7f4d7ce471d3a1
│ └─my_malloc.service (#5637)
│ → user.invocation_id: 02d4d4b8d38b4101b5f6ef5af2addfb3
│ → trusted.invocation_id: 02d4d4b8d38b4101b5f6ef5af2addfb3
│ └─2124 /home/./my_malloc
如果未指定 slice 则默认属于 system.slice
systemd-run --unit=my_malloc ./my_malloc
查看 cgroup 层级结构:
systemd-cgls
返回结果如下:
Control group /:
-.slice
├─user.slice (#1123)
│ → user.invocation_id: 17274220636d448c8b14dca5d4ce1d45
│ → trusted.invocation_id: 17274220636d448c8b14dca5d4ce1d45
...
└─system.slice (#65)
...
├─my_malloc.service (#4501)
│ → user.invocation_id: 7d25225645844d228f300b1f6dc21905
│ → trusted.invocation_id: 7d25225645844d228f300b1f6dc21905
│ └─2146 /home/./my_malloc
3. 服务拉起后,cgroup 根目录下可以查到 my_malloc 这个服务的信息:
ls /sys/fs/cgroup/test.slice/my_malloc.service
返回结果如下:
cgroup.controllers cgroup.threads memory.low memory.swap.events
cgroup.events cgroup.type memory.max memory.swap.high
cgroup.freeze cpu.pressure memory.min memory.swap.max
cgroup.kill cpu.stat memory.numa_stat memory.zswap.current
cgroup.max.depth io.pressure memory.oom.group memory.zswap.max
cgroup.max.descendants irq.pressure memory.peak pids.current
cgroup.pressure memory.current memory.pressure pids.events
cgroup.procs memory.events memory.reclaim pids.max
cgroup.stat memory.events.local memory.stat pids.peak
cgroup.subtree_control memory.high memory.swap.current
4. memory.max 默认是 max,我们通过 systemctl 设置其为 10k。先查看当前值:
cat memory.max
返回结果如下:
max
设置内存限制为 10K:
systemctl set-property my_malloc.service MemoryMax=10K
5. 可以发现服务由于 oom 提前退出:
systemctl status my_malloc
返回结果如下:
× my_malloc.service - /home/./my_malloc
Loaded: loaded (/run/systemd/transient/my_malloc.service; transient)
Transient: yes
Drop-In: /run/systemd/transient/my_malloc.service.d
└─50-MemoryMax.conf
Active: failed (Result: oom-kill) since Tue 2023-05-23 17:20:46 CST; 12s ago
Duration: 6min 15.691s
Process: 2124 ExecStart=/home/./my_malloc (code=killed, signal=KILL)
Main PID: 2124 (code=killed, signal=KILL
CPU: 16ms
May 23 17:14:30 controller systemd[1]: Started my_malloc.service - /home/./my_malloc.
May 23 17:20:46 controller systemd[1]: my_malloc.service: A process of this unit has been killed by the OO>
May 23 17:20:46 controller systemd[1]: my_malloc.service: Main process exited, code=killed, status=9/KILL
May 23 17:20:46 controller systemd[1]: my_malloc.service: Failed with result 'oom-kill'.

使用 /sys/fs/cgroup 管理 task

除了使用 systemd 的命令,您也可以直接操作 /sys/fs/cgroup,本节仍使用上文的 demo 做说明。
1. 列出 controllers 支持的子系统:
cat /sys/fs/cgroup/cgroup.subtree_control
返回结果如下:
cpuset cpu io memory pids
2. 启用 memory 子系统:
echo "+memory" >> /sys/fs/cgroup/cgroup.subtree_control
您可以通过 man 命令来查询 cgroup.subtree_control 的具体使用方法:
man 7 cgroups
3. 创建 /sys/fs/cgroup/Example/ 目录:
mkdir /sys/fs/cgroup/Example/
此时,子系统文件也会在目录中自动创建:
ls /sys/fs/cgroup/Example/
返回结果如下:
cgroup.controllers cgroup.type cpuset.mems memory.events memory.stat
cgroup.events cpu.idle cpuset.mems.effective memory.events.local memory.swap.current
cgroup.freeze cpu.max io.bfq.weight memory.high memory.swap.events
cgroup.kill cpu.max.burst io.latency memory.low memory.swap.high
cgroup.max.depth cpu.pressure io.max memory.max memory.swap.max
cgroup.max.descendants cpu.stat io.pressure memory.min memory.zswap.current
cgroup.pressure cpu.weight io.prio.class memory.numa_stat memory.zswap.max
cgroup.procs cpu.weight.nice io.stat memory.oom.group pids.current
cgroup.stat cpuset.cpus io.weight memory.peak pids.events
cgroup.subtree_control cpuset.cpus.effective irq.pressure memory.pressure pids.max
cgroup.threads cpuset.cpus.partition memory.current memory.reclaim pids.peak
此时,Example/cgroup.subtree_control 需要使能,刚创建的时候为空:
cat /sys/fs/cgroup/Example/cgroup.subtree_control
4. 启用 Example 的 memory 子系统:
echo "+memory" >> /sys/fs/cgroup/Example/cgroup.subtree_control
确认已启用:
cat /sys/fs/cgroup/Example/cgroup.subtree_control
返回结果如下:
memory
5. 创建 /sys/fs/cgroup/Example/tasks/ 目录:
mkdir /sys/fs/cgroup/Example/tasks/
此时 tasks 目录下就只有 memory 子系统相关的文件:
ls /sys/fs/cgroup/Example/tasks/
返回结果如下:
cgroup.controllers cgroup.procs io.pressure memory.max memory.stat
cgroup.events cgroup.stat irq.pressure memory.min memory.swap.current
cgroup.freeze cgroup.subtree_control memory.current memory.numa_stat memory.swap.events
cgroup.kill cgroup.threads memory.events.local memory.peak memory.swap.high
cgroup.max.depth cgroup.type memory.high memory.pressure memory.swap.max
cgroup.max.descendants cpu.pressure memory.low memory.reclaim memory.zswap.current
6. 查看 memory 最大值,默认为 max:
cat /sys/fs/cgroup/Example/tasks/memory.max
返回结果如下:
max
设置 memory 最大值为 10K:
echo "10K" > /sys/fs/cgroup/Example/tasks/memory.max
确认设置结果:
cat /sys/fs/cgroup/Example/tasks/memory.max
返回结果如下:
8192
需要注意的是,echo 的是 10k,但是显示的是 8k,说明是按照 4k 对齐的。
7. 获取 demo 的 pid:
./my_malloc &
返回结果如下:
[1] 2475
将该进程加入 cgroup 中:
echo "2475" > /sys/fs/cgroup/Example/tasks/cgroup.procs
8. 执行后 demo 将被系统 kill
[1]+ Killed ./my_malloc

使用 cgconfig.conf 管理子系统

libcgroup-tools 提供了 cgconfig.service 这个服务帮助用户简便地添加和删除层级,该服务默认关闭,用户在编辑 /etc/cgconfig.conf 后使能服务即可完成对层级的操作,下面举例说明。
1. 显示当前支持的子系统:
cat /proc/cgroups
返回结果如下:
#subsys_name hierarchy num_cgroups enabled
cpuset 0 90 1
cpu 0 90 1
cpuacct 0 90 1
blkio 0 90 1
memory 0 90 1
devices 0 90 1
freezer 0 90 1
net_cls 0 90 1
perf_event 0 90 1
net_prio 0 90 1
hugetlb 0 90 1
pids 0 90 1
rdma 0 90 1
misc 0 90 1
2. 确定需要挂载的子系统没有被占用,以 freezer 为例,执行如下命令,确保子系统未被占用:
lscgroup | grep freezer
子系统被占用时 cgconfig 服务会起不来,已使用被占用的 memory 为例,报错如下:
systemctl status cgconfig
返回结果如下:
× cgconfig.service - Control Group configuration service
Loaded: loaded (/usr/lib/systemd/system/cgconfig.service; disabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Tue 2023-05-23 20:53:49 CST; 1min 10s ago
Process: 2420 ExecStart=/usr/sbin/cgconfigparser -l /etc/cgconfig.conf -s 1664 (code=exited, status=101)
Main PID: 2420 (code=exited, status=101)
CPU: 3ms
May 23 20:53:49 controller systemd[1]: Starting cgconfig.service - Control Group configuration service...
May 23 20:53:49 controller cgconfigparser[2420]: /usr/sbin/cgconfigparser; error loading /etc/cgconfig.conf: Cgroup mounting failed
May 23 20:53:49 controller cgconfigparser[2420]: Error: cannot mount memory to /mnt/cgroups/memory: Device or resource busy
May 23 20:53:49 controller systemd[1]: cgconfig.service: Main process exited, code=exited, status=101/n/a
May 23 20:53:49 controller systemd[1]: cgconfig.service: Failed with result 'exit-code'.
May 23 20:53:49 controller systemd[1]: Failed to start cgconfig.service - Control Group configuration service.
3. 创建挂载条目,编辑 /etc/cgconfig.conf 输入如下内容:
mount {
freezer = /mnt/cgroups/freezer;
}
重启 cgconfig 服务:
systemctl restart cgconfig
需要注意的是,重启会失效,如果需要持久化,可以通过 enable 服务实现:
systemctl enable cgconfig
此时指定的 /mnt/cgroups/freezer 目录下就会有子系统文件:
ls /mnt/cgroups/freezer/
返回结果如下:
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
上述步骤的配置等效于如下操作:
mkdir -p /mnt/cgroups/freezer
mount -t cgroup -o freezer freezer /mnt/cgroups/freezer
完成后您可以参考上文中 cgroup 的使用方法来控制进程的资源。