3. Cgroup v2 文件系统:/sys/fs/cgroup 结构、核心控制器

Cgroup v2 在 Linux 内核中通过一个统一的层级文件系统暴露给用户空间,挂载点固定为 /sys/fs/cgroup。与 v1 的每个控制器独立挂载不同,v2 将所有控制器整合到单一树形结构中,消除了多层级带来的资源竞争与状态不一致问题。本章深入剖析该文件系统的目录布局、核心控制器的接口文件及其运维语义。

3.1 统一层级结构

Cgroup v2 的挂载点下,每个目录代表一个 cgroup,根 cgroup 对应 /sys/fs/cgroup 本身。子目录通过 mkdir 创建,删除通过 rmdir(需先清空子 cgroup)。关键特征如下:

  • 单根树:所有控制器挂载在同一棵树上,不再有 v1 的 /sys/fs/cgroup/cpu、/sys/fs/cgroup/memory 等独立挂载点。
  • 控制器启用:通过 cgroup.controllers 和 cgroup.subtree_control 文件控制。根 cgroup 默认启用所有可用控制器,子 cgroup 需父节点授权。
  • 线程模式:通过 cgroup.type 文件支持线程级资源隔离(threaded 模式),但默认仍以进程为粒度。
运维要点:在 v2 中,/sys/fs/cgroup 下不再有 release_agent 文件;通知机制改为 cgroup.events 文件中的 populated 字段,通过 inotify 监听。

3.2 核心控制器接口文件

每个 cgroup 目录下,除了通用接口文件(如 cgroup.procs、cgroup.controllers),还包含已启用控制器的专用文件。以下为三个最关键的控制器:

3.2.1 CPU 控制器

CPU 控制器在 v2 中统一了 v1 的 cpu、cpuacct 功能,并提供权重与带宽两种模型。

接口文件 类型 说明 典型值/范围
cpu.weight RW 相对权重,控制 CPU 时间分配比例 [1, 10000],默认 100
cpu.weight.nice RW 将权重映射为类似 nice 值的语义 [-20, 19]
cpu.max RW 带宽上限:$MAX $PERIOD,格式如 50000 100000 默认 max 100000
cpu.pressure RO PSI 压力指标,反映 CPU 资源竞争程度 如 some avg10=0.00 avg60=0.00 avg300=0.00 total=0
cpu.stat RO 统计信息:usage_usec、user_usec、system_usec、nr_periods、nr_throttled、throttled_usec —
容量规划提示:cpu.stat 中的 nr_throttled 和 throttled_usec 是判断容器是否被 CPU 限流的关键指标。若 throttled_usec / (nr_periods * period) 超过 5%,说明 CPU 上限设置过紧。

3.2.2 Memory 控制器

Memory 控制器在 v2 中合并了 v1 的 memory 和 memsw,并引入更精细的回收与压力管理。

接口文件 类型 说明 典型值/范围
memory.current RO 当前内存使用量(字节),包含 page cache —
memory.min RW 硬性保护,保证该 cgroup 至少可用的内存量 默认 0
memory.low RW 软性保护,尽力保留的内存,竞争时可被回收 默认 0
memory.high RW 内存软上限,超过后触发异步回收,不直接 OOM 默认 max
memory.max RW 内存硬上限,超过后触发 OOM Killer 默认 max
memory.oom.group RW OOM 时是否杀死整个 cgroup(1)还是单个进程(0) 0 或 1
memory.pressure RO PSI 内存压力指标 格式同 cpu.pressure
memory.stat RO 详细统计:anon、file、kernel_stack、slab、pgfault、pgmajfault 等 —
memory.swap.max RW Swap 使用硬上限 默认 max
memory.zswap.max RW Zswap 压缩池上限(内核 6.1+) 默认 max
避坑指南:memory.high 设置过小会导致频繁异步回收,引发 CPU 开销飙升和延迟抖动。建议 memory.high 至少为 memory.max 的 80%,避免回收风暴。

3.2.3 I/O 控制器

I/O 控制器在 v2 中统一了 blkio,并引入写回(writeback)与 IO 优先级模型。

接口文件 类型 说明 典型值/范围
io.weight RW IO 权重,控制带宽分配比例 [1, 10000],默认 100
io.max RW IO 带宽上限:$MAJ:$MIN rbps=$VALUE wbps=$VALUE riops=$VALUE wiops=$VALUE 默认无限制
io.stat RO IO 统计:rbytes、wbytes、rios、wios、dbytes、dios —
io.pressure RO PSI IO 压力指标 格式同 cpu.pressure
协同运行时场景:在 RunC 容器中,io.max 常用于限制日志写入或数据卷的 IOPS,防止一个容器的磁盘 I/O 影响同主机的其他容器。注意 io.weight 仅在 CFQ 或 BFQ 调度器下生效。

3.2.4 PIDs 控制器

PIDs 控制器用于限制 cgroup 内可创建的进程/线程总数,防止 fork 炸弹。

接口文件 类型 说明 典型值/范围
pids.current RO 当前进程/线程数 —
pids.max RW 进程/线程数上限 默认 max,建议设置如 1024
pids.events RO 达到上限时的统计:max 字段记录触发次数 —
容量规划提示:对于 Java 或 Node.js 应用,每个线程对应一个 PID,pids.max 应至少为预期线程数的 2 倍,避免因线程池扩展导致容器被 OOM 或 kill。

3.3 控制器协同与层级关系

以下 SVG 图展示 Cgroup v2 文件系统的典型层级结构,以及各控制器在根 cgroup 和子 cgroup 中的启用关系。

Cgroup v2 文件系统层级与控制器启用关系 /sys/fs/cgroup (根) cgroup.controllers: cpu memory io pids 子 cgroup: /app-a 子 cgroup: /app-b cgroup.subtree_control cpu memory (仅启用 CPU 和 Memory) cgroup.subtree_control cpu io pids (启用 CPU、IO 和 PIDs) /app-a 接口文件 cpu.weight, cpu.max memory.current, memory.max memory.high, memory.stat cgroup.procs cgroup.events /app-b 接口文件 cpu.weight, cpu.max io.weight, io.max pids.current, pids.max cgroup.procs cgroup.events

3.4 实战:查看与配置 cgroup 接口

以下命令演示如何在 RunC 容器运行时环境中操作 cgroup v2 接口:

# 1. 查看根 cgroup 的可用控制器
cat /sys/fs/cgroup/cgroup.controllers
# 输出示例: cpu memory io pids

# 2. 为容器创建专用 cgroup
mkdir -p /sys/fs/cgroup/containers/my-container

# 3. 启用 CPU 和 Memory 控制器(需在父 cgroup 授权)
echo "+cpu +memory" > /sys/fs/cgroup/containers/cgroup.subtree_control

# 4. 设置 CPU 权重和上限
echo "100" > /sys/fs/cgroup/containers/my-container/cpu.weight
echo "50000 100000" > /sys/fs/cgroup/containers/my-container/cpu.max

# 5. 设置内存硬上限 512MB
echo "536870912" > /sys/fs/cgroup/containers/my-container/memory.max

# 6. 设置 PIDs 上限
echo "1024" > /sys/fs/cgroup/containers/my-container/pids.max

# 7. 将容器进程 PID 加入 cgroup
echo "12345" > /sys/fs/cgroup/containers/my-container/cgroup.procs

# 8. 验证配置
cat /sys/fs/cgroup/containers/my-container/cpu.max
cat /sys/fs/cgroup/containers/my-container/memory.current
cat /sys/fs/cgroup/containers/my-container/pids.current
常见错误:在 v2 中,cgroup.procs 写入 PID 时,该进程的所有线程自动加入同一 cgroup。若需线程级隔离,需先设置 cgroup.type 为 threaded,但大多数容器场景不推荐。

3.5 控制器协同与资源竞争

当多个控制器同时生效时,资源竞争遵循以下原则:

  • CPU vs Memory:内存回收(如 memory.high 触发)会消耗 CPU 周期,导致 cpu.pressure 升高。容量规划时需为内存回收预留 5-10% 的 CPU 余量。
  • IO vs CPU:同步 I/O 操作(如 fsync)会阻塞 CPU 调度,表现为 iowait 升高。IO 密集型容器应配合 io.weight 和 cpu.weight 联合调优。
  • PIDs vs Memory:每个线程占用内核栈(约 8KB),大量线程会消耗 kernel_stack 内存,反映在 memory.stat 中。设置 pids.max 可间接控制内核内存开销。

以下 SVG 图展示多控制器协同下的资源流与竞争关系:

Cgroup v2 多控制器协同资源流 容器进程 CPU 控制器 Memory 控制器 IO 控制器 资源竞争与反馈 CPU 回收 → 内存压力升高 → IO 等待增加 → PIDs 上限触发 关键运维指标 cpu.pressure · memory.pressure · io.pressure · pids.events

3.6 运维检查清单

日常巡检 cgroup v2 文件系统时,建议执行以下检查项:

检查项 命令/文件 正常阈值 异常处理
CPU 限流率 cpu.stat 的 nr_throttled / nr_periods < 5% 增大 cpu.max 或调整权重
内存回收压力 memory.pressure 的 some avg10 < 10 增大 memory.high 或 memory.max
IO 延迟 io.pressure 的 some avg10 < 5 调整 io.weight 或限制 IOPS
PID 使用率 pids.current / pids.max < 80% 增大 pids.max 或排查线程泄漏
OOM 事件 memory.events 的 oom 字段 0 分析内存使用,增大 memory.max
自动化脚本建议:将上述检查项封装为 Prometheus 指标采集器,通过 node_exporter 的 textfile collector 或自定义 exporter 定期读取 *.stat 和 *.pressure 文件,实现容量预警。