1、cgroups简介

cgroups(全称:control groups)是 Linux 内核的一个功能,它可以实现限制进程或者进程组的资源(如 CPU、内存、磁盘 IO 等)。

在 2006 年,Google 的工程师( Rohit Seth 和 Paul Menage 为主要发起人) 发起了这个项目,起初项目名称并不是cgroups,而被称为进程容器(process containers)。在 2007 年cgroups代码计划合入Linux 内核,但是当时在 Linux 内核中,容器(container)这个词被广泛使用,并且拥有不同的含义。为了避免命名混乱和歧义,进程容器被重名为cgroups,并在 2008 年成功合入 Linux 2.6.24 版本中。cgroups目前已经成为 systemd、Docker、Linux Containers(LXC) 等技术的基础。

cgroups 功能及核心概念

cgroups 主要提供了如下功能。

  • 资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。
  • 优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。
  • 审计:计算控制组的资源使用情况。
  • 控制:控制进程的挂起或恢复。

cgroups功能的实现依赖于三个核心概念:子系统、控制组、层级树。

  • 子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
  • 控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
  • 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。

实战核心概念概念解析表

核心概念 在实例中的表现 关键验证命令 结果分析
子系统 (资源控制器) cpumemory 子系统生效 cat cpu.max cat memory.max 输出: 100000 100000 (1核) 629145600 (600MB)
控制组 (策略集合) Docker 创建名为 docker-d3adb33fd3a.scope 的控制组 cat cgroup.procs 显示容器进程PID: 8123 8150
层级树 (继承结构) 继承 system.slice 父级策略 cat ../system.slice/cpu.max 父级通常为: max 100000 (不限制) → 子组覆盖了父级策略

总结关键结论:

  1. 子系统是引擎
    CPU/memory 等子系统通过 /sys/fs/cgroup/ 中的文件提供资源控制能力
  2. 控制组是载体
    Docker 自动为每个容器创建专属控制组目录承载策略
  3. 层级树是骨架
    控制组按 system.slice → docker-xxx.scope 形成继承链
  4. 策略继承规则

2、Cgroups 核心概念与实战对照表

概念类型 Cgroups术语 Docker参数映射 Linux内核文件示例 功能解释
资源限制 CPU限额 --cpus=1.5 /sys/fs/cgroup/cpu.max 限制容器使用1.5个CPU核心
内存硬限制 -m 500m /sys/fs/cgroup/memory.max 内存使用上限500MB(触发OOM)
内存软限制 --memory-reservation=300m /sys/fs/cgroup/memory.high 内存压力预警线300MB(优先回收)
PID数量限制 --pids-limit=100 /sys/fs/cgroup/pids.max 容器内进程数≤100
优先级策略 CPU权重 --cpu-shares=1024 /sys/fs/cgroup/cpu.weight 默认1024,值越高分配CPU越多
IO权重 --blkio-weight=500 /sys/fs/cgroup/io.bfq.weight 相对权重控制磁盘带宽
资源审计 内存使用统计 docker stats /sys/fs/cgroup/memory.stat 包含cache/RSS/swap等详细数据
CPU时间统计 docker stats /sys/fs/cgroup/cpu.stat 显示总CPU时间及限流次数
进程控制 容器暂停 docker pause /sys/fs/cgroup/cgroup.freeze 冻结容器内所有进程
OOM事件监控 docker events /sys/fs/cgroup/memory.events 记录OOM发生次数及时间

3、cgroups 实战操作步骤表

以CPU子系统为例:

操作类型 手动命令 实际用途 Docker等价命令
1. 创建cgroup sudo mkdir /sys/fs/cgroup/cpu/demo CPU限制组创建 docker run -it --rm --cgroup-parent=/demo/ alpine
2. 设置限制 echo 100000 > /sys/fs/cgroup/cpu/demo/cpu.max 限制1核CPU docker run --cpus=1.0 ...
3. 加入进程 echo $PID > /sys/fs/cgroup/cpu/demo/cgroup.procs 控制特定进程 自动处理
4. 动态修改 echo 50000 > /sys/fs/cgroup/cpu/demo/cpu.max CPU减半 docker update --cpus=0.5 <container>
5. 监控资源 cat /sys/fs/cgroup/memory/demo/memory.stat 查看内存统计 docker stats --no-stream
6. 清理cgroup sudo rmdir /sys/fs/cgroup/cpu/demo/ 删除控制组 容器停止时自动清理

4、深度解析 Docker 的动作序列(以 CPU+内存子系统为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
flowchart TB
step1[1. 挂载子系统<br>(Docker daemon启动时)]
step2[2. 创建容器控制组]
step3[3. 写入资源限制]
step4[4. 加入容器进程]

step1 -->|执行| mount1[“mount -t cgroup -o cpu,cpuacct none /sys/fs/cgroup/cpu/docker”]
step1 -->|执行| mount2[“mount -t cgroup -o memory none /sys/fs/cgroup/memory/docker”]

step2 -->|在CPU子系统| cpumkdir[“mkdir /sys/fs/cgroup/cpu/docker/<容器ID>”]
step2 -->|在内存子系统| memmkdir[“mkdir /sys/fs/cgroup/memory/docker/<容器ID>”]

step3 -->|CPU限制| cpulimit[“echo '50000 100000' > /sys/fs/cgroup/cpu/.../cpu.max”]
step3 -->|内存限制| memlimit[“echo '100000000' > /sys/fs/cgroup/memory/.../memory.max”]

step4 -->|绑定到CPU控制组| cpuattach[“echo &lt;PID&gt; > /sys/fs/cgroup/cpu/.../cgroup.procs”]
step4 -->|绑定到内存控制组| memattach[“echo &lt;PID&gt; > /sys/fs/cgroup/memory/.../cgroup.procs”]

classDef box fill:#f9f,stroke:#c0c;
class mount1,mount2,cpumkdir,memmkdir,cpulimit,memlimit,cpuattach,memattach box;

可以用城市水电系统深化类比:控制组相当于每户的电表箱(集中展示电流/电压监控接口),供电局(内核)通过线路(挂载点)将服务接入,住户(容器进程)用电行为直接触发电表(子系统控制器)计数。

验证:手工在宿主机模拟容器限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 创建控制组目录(模拟容器)
sudo mkdir /sys/fs/cgroup/cpu/demo_container
sudo mkdir /sys/fs/cgroup/memory/demo_container

# 2. 设置CPU限额(单核50%)
echo "50000 100000" | sudo tee /sys/fs/cgroup/cpu/demo_container/cpu.max

# 3. 设置内存限额(10MB)
echo "10000000" | sudo tee /sys/fs/cgroup/memory/demo_container/memory.max

# 4. 创建牺牲进程(模拟容器进程)
(while true; do :; done) & # 后台运行空循环

# 5. 加入控制组
echo $! | sudo tee /sys/fs/cgroup/cpu/demo_container/cgroup.procs \
/sys/fs/cgroup/memory/demo_container/cgroup.procs

# 6. 观察限制生效
top -p $!

进程的CPU使用率会被压制在50%,内存超过10MB会被OOM killer终止

❗ 关键规则

  1. 必然创建
    只要设置了资源限制(哪怕不显式设置,Docker 默认也会给限制),必然在子系统目录下创建控制组

  2. 创建位置
    已挂载的子系统目录下 创建:

    1
    /sys/fs/cgroup/子系统名称/docker/容器ID
  3. 一对一绑定
    每个容器对应 多个控制组目录(每个子系统一个):

    1
    2
    # 查看容器在各类子系统中的控制组
    ls /sys/fs/cgroup/*/docker/$(docker inspect -f '{{.Id}}' demo)

*本质:*控制组是子系统能力的客户端接口*,而非服务端实例*。就像插头(控制组)插入电源(子系统)才能生效,但电源始终存在于电网(内核)中!