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 中的启用关系。
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 图展示多控制器协同下的资源流与竞争关系:
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 文件,实现容量预警。