一、cgroups v2 架构总览
Control Groups(控制组)是 Linux 内核提供的一种机制,用于限制、记录和隔离进程组所使用的物理资源(CPU、内存、IO、网络等)。cgroups v2 是对 v1 的重大重构,解决了 v1 中层次结构混乱、控制器不一致等问题。
核心设计理念变化:
- v1 中每个控制器可以有自己的层次结构,v2 统一为单一层次树
- v2 采用"单一写入者"原则,防止子进程脱离父进程控制
- v2 区分"资源分配"与"资源限制"语义更加清晰
- 进程只能位于叶子节点,非叶子节点仅用于分组管理
二、cgroups v2 启用与基础操作
检查并启用 cgroups v2:
# 检查当前 cgroup 版本
stat -fc %T /sys/fs/cgroup
# 输出 cgroup2fs 表示 v2,tmpfs 表示 v1
# GRUB 配置启用 v2(/etc/default/grub)
GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"
# 验证 v2 已启用
mount | grep cgroup2
# tmpfs on /sys/fs/cgroup type tmpfs ...
基础 cgroup 操作:
# 创建子 cgroup
mkdir /sys/fs/cgroup/myapp
# 将进程加入 cgroup
echo $$ > /sys/fs/cgroup/myapp/cgroup.procs
# 查看 cgroup 内进程
cat /sys/fs/cgroup/myapp/cgroup.procs
# 删除空 cgroup
rmdir /sys/fs/cgroup/myapp
三、PID 控制器 — 进程数量限制
PID 控制器限制一个 cgroup 内可以创建的进程/线程总数。在容器场景中,这是防止 fork 炸弹攻击的关键防护。
# 启用 PID 控制器(父 cgroup)
echo "+pids" > /sys/fs/cgroup/cgroup.subtree_control
# 创建子 cgroup
mkdir /sys/fs/cgroup/myapp
echo "+pids" > /sys/fs/cgroup/myapp/cgroup.subtree_control
# 设置最大进程数
echo 100 > /sys/fs/cgroup/myapp/pids.max
# 查看当前进程数
cat /sys/fs/cgroup/myapp/pids.current
# 查看历史峰值
cat /sys/fs/cgroup/myapp/pids.peak
systemd 集成方式(service unit):
[Service]
TasksMax=100
# 或使用更精细的 cgroup 控制
IODeviceWeight=/dev/sda 50
CPUWeight=100
Docker/Kubernetes 中的应用:
# Docker 限制容器进程数
docker run --pids-limit=200 mycontainer
# Kubernetes pod 级别限制 (feature gate: SupportPodPidsLimit)
# spec:
# containers:
# - name: app
# resources:
# limits:
# cpu: "1"
# memory: "512Mi"
PID 耗尽攻击防护原理:
当 cgroup 内进程数达到 pids.max 时,任何 fork() 或 clone() 调用都会返回 EAGAIN 错误。这意味着攻击者无法通过 fork 炸弹耗尽系统全局 PID 池,实现了有效的故障隔离。
四、内存控制器 — 精细化的内存管理
cgroups v2 的内存控制器使用三个关键参数:memory.min、memory.low、memory.high 和 memory.max,形成四层保护阶梯。
四层内存保护模型:
- memory.min — 硬保护保证,内核绝不回收该 cgroup 内存至 min 以下
- memory.low — 尽力而为保护,内核尽量避免回收 low 以下内存
- memory.high — 节流阈值,超过时开始施加回收压力但不阻塞分配
- memory.max — 硬上限,超过触发 OOM killer(cgroup 级别)
# 启用内存控制器
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/database
echo "+memory" > /sys/fs/cgroup/database/cgroup.subtree_control
# 设置内存层次保护
echo 1G > /sys/fs/cgroup/database/memory.min # 硬保证 1GB
echo 2G > /sys/fs/cgroup/database/memory.low # 尽量保护 2GB
echo 3G > /sys/fs/cgroup/database/memory.high # 超过3GB开始节流
echo 4G > /sys/fs/cgroup/database/memory.max # 4GB 绝对上限
# 查看内存使用情况
cat /sys/fs/cgroup/database/memory.current
cat /sys/fs/cgroup/database/memory.stat
cat /sys/fs/cgroup/database/memory.pressure # PSI 压力信息
memory.stat 详解:
# 各字段含义
anon - 匿名映射(堆/栈)内存
file - 文件缓存页
kernel_stack - 内核栈内存
pagetables - 页表占用
sock - socket 缓冲区
shmem - 共享内存
file_mapped - 文件映射页数
inactive_anon / active_anon - LRU 链表分布
pgfault / pgmajfault - 缺页/主缺页次数
五、IO 控制器 — 磁盘带宽精细化分配
cgroups v2 的 IO 控制器支持两种模式:权重分配(io.weight)和设备级带宽限制(io.max)。
io.weight — 权重分配:
# 启用 IO 控制器
echo "+io" > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/mysql
echo "+io" > /sys/fs/cgroup/mysql/cgroup.subtree_control
# 设置相对权重(10-10000,默认 100)
echo 500 > /sys/fs/cgroup/mysql/io.weight # MySQL 权重 50
echo 100 > /sys/fs/cgroup/backup/io.weight # 备份任务权重 10
io.max — 设备级硬限速:
# 限制 /dev/sda 读写带宽
# 格式:设备号 rbps wbps riops wiops
echo "8:0 rbps=104857600 wbps=52428800 riops=1000 wiops=500" \
> /sys/fs/cgroup/myapp/io.max
# 查看 IO 统计
cat /sys/fs/cgroup/myapp/io.stat
io.bfq.weight — BFQ 调度器权重:
当使用 BFQ IO 调度器时,io.bfq.weight 提供更细粒度的带宽分配控制。与 cfq 的 slice 概念不同,BFQ 采用budget 机制,更公平地分配磁盘时间片。
六、CPU 控制器 — 三层 CPU 调度
cgroups v2 的 CPU 控制器同样采用三层模型:cpu.weight、cpu.max、cpu.max.burst。
# 启用 CPU 控制器
echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/webserver
echo "+cpu" > /sys/fs/cgroup/webserver/cgroup.subtree_control
# 方法一:权重分配(相对比例)
echo 200 > /sys/fs/cgroup/webserver/cpu.weight # Web服务权重 200
echo 100 > /sys/fs/cgroup/background/cpu.weight # 后台任务权重 100
# 方法二:硬限速(类似 CFS bandwidth control)
# 格式:$MAX $PERIOD(微秒)
echo "100000 100000" > /sys/fs/cgroup/webserver/cpu.max
# 表示每 100ms 周期内最多使用 100ms CPU 时间(即 1 个核)
echo "50000 100000" > /sys/fs/cgroup/webserver/cpu.max
# 每 100ms 周期最多使用 50ms(相当于 0.5 核)
# CPU Burst — 允许短期超额使用
echo "50000 100000" > /sys/fs/cgroup/webserver/cpu.max
echo 150000 > /sys/fs/cgroup/webserver/cpu.max.burst
# 允许累积未用配额后在下次突发额外使用 150ms
NUMA 感知 CPU 分配:
在多 NUMA 节点系统中,结合 cpuset 控制器可以实现 CPU 核心绑定 + 内存节点亲和性,避免跨 NUMA 内存访问带来的性能损失。这在 DPDK、高频交易等场景尤为重要。
七、PSI — 资源压力停滞信息
Pressure Stall Information(PSI)是 Linux 4.20+ 引入的功能,提供精确的资源压力度量,比传统负载指标更灵敏。
# 查看当前内存压力
cat /sys/fs/cgroup/myapp/memory.pressure
# 输出格式:avg10=0.00 avg60=0.00 avg300=0.00 total=123456789
# avg10/avg60/avg300 - 过去10秒/60秒/300秒平均阻塞百分比
# total - 自创建以来累计阻塞时间(微秒)
# 设置压力阈值通知(通过 epoll)
# 当 10 秒内 CPU 压力超过 50% 时触发通知
echo "some 50000 100000" > /sys/fs/cgroup/myapp/cpu.pressure
# PSI 分类
# some - 至少有一个任务被阻塞
# full - 所有可运行任务同时被阻塞(最严重)
PSI 在容器编排中的价值:
传统的 CPU 平均负载无法反映瞬时争用,PSI 直接测量任务等待资源的时间占比。Kubernetes 已引入 PSI 指标用于更精确的 OOM 预判和自动扩缩容决策。
八、systemd 与 cgroups v2 深度集成
systemd 是 cgroups v2 最深度的一体化使用者,理解 systemd 的 cgroup 集成对运维至关重要。
# 查看进程所属 cgroup
systemctl status nginx.service
# 或直接查看
cat /proc/self/cgroup
# 最实用的 systemd-cgtop(类似 top,按 cgroup 分组)
systemd-cgtop
# 设置服务资源限制
systemctl set-property nginx.service MemoryMax=1G CPUQuota=50%
# 临时修改(不持久化)
systemctl set-property --runtime nginx.service IOWeight=50
# 持久化到 unit 文件
systemctl edit nginx.service
# 添加:
[Service]
MemoryMax=1G
CPUQuota=50%
IODeviceWeight=/dev/sda 50
systemd 自动 cgroup 层次结构:
每个 systemd service 自动创建对应 cgroup,scope unit 用于管理非 systemd 启动的进程组,slice unit 实现层级资源配额。这种设计使得整个系统的资源分配策略可以统一管理。
九、实战案例:多租户容器平台资源隔离方案
架构设计:
- 使用 Kubernetes 1.26+ 开启 cgroups v2 支持
- Pod 级别通过 RuntimeClass 选择 cgroup 驱动
- 节点级使用 systemd cgroup driver
- 结合 PriorityClass + cgroup 权重实现 QoS 分级
# Pod 资源定义示例
apiVersion: v1
kind: Pod
metadata:
name: high-priority-app
spec:
priorityClassName: production
containers:
- name: app
image: myapp:latest
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
关键优化参数(kubelet):
# kubelet 配置
cgroupDriver: systemd
cgroupsPerQOS: true
systemCgroups: /system.slice
kubeletCgroups: /kubelet.slice
# Linux kernel >= 5.8 推荐开启 MemoryQoS
featureGates:
MemoryQoS: true
十、总结与展望
cgroups v2 相比 v1 是质的飞跃,统一的层次模型、精细的四层资源控制、PSI 压力监控,使其成为现代容器基础设施的基石。掌握 PID 控制器防止进程爆炸、内存四层阶梯精确控制 IO/CPU 调度,是构建高可靠性容器平台的核心技能。
Linux 内核社区一直在持续改进 cgroups 的可观测性和性能:PSI 内置化、cgroup.eBPF 程序支持、跨 cgroup 资源借贷机制等,预示着资源管理将进入更智能的新阶段。对于运维工程师和平台开发者来说,深入理解 cgroups v2 不仅是容器技术的必修课,更是构建大规模、高可用系统的不二法门。

发表评论 取消回复