一、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 不仅是容器技术的必修课,更是构建大规模、高可用系统的不二法门。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部