Linux cgroup v2 进程资源控制实战:CPU、内存、IO 限速
深入讲解 Linux cgroup v2(统一层级)的资源控制机制,包括 CPU 配额与权重、内存限制与 OOM 策略、IO 限速,以及如何用 systemd 单元文件和 cgroupfs 接口在生产环境中实现进程级资源隔离。
容器运行时(Docker、containerd)的资源限制底层都是 cgroup。理解 cgroup v2 不只是为了“知道原理”——在排查容器 OOM、CPU 限流、IO 抢占问题时,直接读 cgroup 文件是最快的诊断手段。本文讲解 cgroup v2 的实战配置,覆盖不用容器、直接管理进程资源的场景。
cgroup v1 vs v2
cgroup v1(旧,Linux 3.x 时代):
每种资源独立层级树
/sys/fs/cgroup/cpu/myapp/ ← CPU 控制器
/sys/fs/cgroup/memory/myapp/ ← 内存控制器
/sys/fs/cgroup/blkio/myapp/ ← IO 控制器
问题:跨控制器协调困难,线程/进程归属混乱
cgroup v2(新,Linux 4.5+,推荐):
统一单一层级树
/sys/fs/cgroup/myapp/
├── cgroup.controllers ← 可用控制器
├── cpu.max ← CPU 配额
├── memory.max ← 内存上限
└── io.max ← IO 限速
# 检查系统使用 v1 还是 v2
mount | grep cgroup
# cgroup2 on /sys/fs/cgroup type cgroup2 ← 纯 v2
# cgroup on /sys/fs/cgroup/cpu type cgroup ← v1
# Ubuntu 22.04+ 默认纯 v2
# CentOS 7 默认 v1,CentOS 8+ 支持 v2
cat /proc/filesystems | grep cgroup
# 强制启用纯 cgroup v2(修改 GRUB)
# /etc/default/grub 中 GRUB_CMDLINE_LINUX 加:
# systemd.unified_cgroup_hierarchy=1
通过 systemd 控制资源(推荐方式)
systemd 是管理 cgroup 的最佳入口,无需直接操作 /sys/fs/cgroup。
限制服务资源
# 方法1:systemctl set-property(立即生效,持久化)
sudo systemctl set-property nginx.service CPUQuota=50%
sudo systemctl set-property nginx.service MemoryMax=512M
sudo systemctl set-property nginx.service IOReadBandwidthMax="/dev/sda 50M"
# 配置保存在 /etc/systemd/system.control/nginx.service.d/
cat /etc/systemd/system.control/nginx.service.d/50-CPUQuota.conf
# [Service]
# CPUQuota=50%
# 方法2:直接编辑 unit drop-in 文件
mkdir -p /etc/systemd/system/nginx.service.d/
cat > /etc/systemd/system/nginx.service.d/resource-limits.conf << 'EOF'
[Service]
# CPU:最多使用 1 个核心的 50%(不限制时 CPUQuota 为空)
CPUQuota=50%
# CPU 权重(相对调度优先级,默认100,范围1-10000)
CPUWeight=200
# 内存
MemoryMax=1G # 超过则 OOM Kill
MemoryHigh=800M # 超过则内存回收压力增加(软限制)
MemorySwapMax=0 # 禁止使用 swap
# IO 限速(针对特定设备)
IOReadBandwidthMax=/dev/sda 100M
IOWriteBandwidthMax=/dev/sda 50M
IOWeight=100 # IO 调度权重
# 任务数(防 fork 炸弹)
TasksMax=1024
EOF
systemctl daemon-reload
systemctl restart nginx
验证资源限制生效
# 查看服务的 cgroup 状态
systemctl status nginx
# CGroup: /system.slice/nginx.service
# ├─12345 nginx: master process
# └─12346 nginx: worker process
# 查看 cgroup 路径
cat /proc/$(pidof nginx | awk '{print $1}')/cgroup
# 0::/system.slice/nginx.service
# 直接读 cgroup 文件
CGROUP_PATH="/sys/fs/cgroup/system.slice/nginx.service"
cat $CGROUP_PATH/cpu.max
# 50000 100000 ← 含义:每 100ms 周期内最多用 50ms(= 50%)
cat $CGROUP_PATH/memory.max
# 1073741824 ← 1GB(字节)
# 实时查看 CPU/内存使用(比 top 更精确)
systemd-cgtop
手动创建 cgroup(不用 systemd)
# 创建自定义 cgroup
mkdir /sys/fs/cgroup/myapp
# 启用控制器(子组继承父组的控制器)
# 先看根组支持哪些控制器
cat /sys/fs/cgroup/cgroup.controllers
# cpuset cpu io memory hugetlb pids rdma
# 把控制器委托给子组
echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control
# 创建子 cgroup
mkdir /sys/fs/cgroup/myapp
# 设置 CPU 限制
echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 格式:quota_us period_us
# 50000/100000 = 50% CPU
# 设置内存限制
echo $((512 * 1024 * 1024)) > /sys/fs/cgroup/myapp/memory.max # 512MB
# 把进程加入 cgroup
echo $$ > /sys/fs/cgroup/myapp/cgroup.procs # 当前 shell
# 或
echo 12345 > /sys/fs/cgroup/myapp/cgroup.procs # 指定 PID
# 查看 cgroup 中的进程
cat /sys/fs/cgroup/myapp/cgroup.procs
CPU 控制详解
CPU 配额(硬限制)
# cpu.max 格式:max period(单位微秒)
# 限制 50%(单核)
echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 限制 200%(2个核心的全部)
echo "200000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 不限制
echo "max 100000" > /sys/fs/cgroup/myapp/cpu.max
# 通过 systemd(等效)
CPUQuota=50% # 50% 单核
CPUQuota=200% # 2 核满载
CPU 权重(软限制,竞争时生效)
# cpu.weight 范围:1-10000,默认 100
# 只在 CPU 资源紧张时有效,空闲时不限制
# 高优先级应用(4倍于默认)
echo 400 > /sys/fs/cgroup/myapp_high/cpu.weight
# 低优先级批处理(1/4)
echo 25 > /sys/fs/cgroup/batch/cpu.weight
# systemd 等效
CPUWeight=400
# 监控 CPU 限流(throttle)情况
cat /sys/fs/cgroup/myapp/cpu.stat
# usage_usec 1234567 ← 总 CPU 使用时间
# user_usec 987654 ← 用户态
# system_usec 246913 ← 内核态
# nr_periods 1000 ← 总调度周期数
# nr_throttled 150 ← 被限流的周期数(这个高说明 CPUQuota 太低)
# throttled_usec 75000 ← 被限流的总时间
内存控制详解
# memory.max(硬限制):超过则 OOM Kill
echo $((1 * 1024**3)) > /sys/fs/cgroup/myapp/memory.max # 1GB
# memory.high(软限制):超过则触发内存回收,降速但不 Kill
echo $((800 * 1024**2)) > /sys/fs/cgroup/myapp/memory.high # 800MB
# memory.min(保证):保证至少有这么多内存不被回收
echo $((256 * 1024**2)) > /sys/fs/cgroup/myapp/memory.min # 256MB
# 禁止 swap(重要!避免内存不足时换出到磁盘导致性能下降)
echo 0 > /sys/fs/cgroup/myapp/memory.swap.max
# 查看内存使用情况
cat /sys/fs/cgroup/myapp/memory.current
# 536870912 ← 当前使用 512MB
cat /sys/fs/cgroup/myapp/memory.stat
# anon 104857600 ← 匿名内存(堆、栈)
# file 432013312 ← 文件页缓存
# shmem 0 ← 共享内存
# kernel_stack 131072 ← 内核栈
# oom_kill 0 ← 被 OOM Kill 的次数(非0就要告警)
# OOM 事件通知(通过 eventfd 机制,生产中用来触发告警)
# 简单方式:直接监控 oom_kill
watch -n 5 "cat /sys/fs/cgroup/myapp/memory.stat | grep oom_kill"
OOM Kill 策略
# memory.oom.group
# 默认:0 - 只 kill 超限的进程
# 设为 1 - OOM 时 kill 整个 cgroup(适合多进程应用,kill 一个不如全清干净)
echo 1 > /sys/fs/cgroup/myapp/memory.oom.group
# OOM Killer 打分(-1000 到 1000,越高越先被 kill)
# 通过 /proc/PID/oom_score_adj 调整
echo -500 > /proc/12345/oom_score_adj # 降低被 kill 的优先级(重要进程)
echo 500 > /proc/12346/oom_score_adj # 提高被 kill 的优先级(可牺牲进程)
IO 控制详解
# 先找到磁盘的设备号
ls -la /dev/sda
# brw-rw---- 1 root disk 8, 0 ... ← 8:0 是设备号(major:minor)
# 或者
cat /sys/block/sda/dev
# 8:0
# io.max 格式:major:minor rbps=N wbps=N riops=N wiops=N
# 限制 /dev/sda 读 100MB/s,写 50MB/s
echo "8:0 rbps=104857600 wbps=52428800" > /sys/fs/cgroup/myapp/io.max
# 只限制 IOPS
echo "8:0 riops=1000 wiops=500" > /sys/fs/cgroup/myapp/io.max
# 同时限制带宽和 IOPS
echo "8:0 rbps=104857600 wbps=52428800 riops=1000 wiops=500" > /sys/fs/cgroup/myapp/io.max
# systemd 等效
IOReadBandwidthMax=/dev/sda 100M
IOWriteBandwidthMax=/dev/sda 50M
# 查看 IO 使用情况
cat /sys/fs/cgroup/myapp/io.stat
# 8:0 rbytes=1073741824 wbytes=536870912 rios=1024 wios=512 ...
# IO 权重(相对优先级)
echo "8:0 100" > /sys/fs/cgroup/myapp/io.weight # 默认 100
echo "8:0 500" > /sys/fs/cgroup/batch/io.weight # 批处理让高优先级优先
实战:多应用资源隔离
# 场景:同一台服务器运行 Web(高优先)和批处理任务(低优先)
# Web 应用 systemd 配置
cat > /etc/systemd/system/webapp.service.d/cgroup.conf << 'EOF'
[Service]
CPUQuota=300% # 最多 3 核
CPUWeight=500 # 高调度权重
MemoryMax=4G
MemoryHigh=3G
MemorySwapMax=0
IOWeight=500
TasksMax=2048
EOF
# 批处理任务 systemd 配置
cat > /etc/systemd/system/batch-job.service.d/cgroup.conf << 'EOF'
[Service]
CPUQuota=200% # 最多 2 核
CPUWeight=50 # 低调度权重(CPU 竞争时让路)
MemoryMax=2G
MemorySwapMax=512M # 允许少量 swap
IOWeight=50
IOReadBandwidthMax=/dev/sda 20M
IOWriteBandwidthMax=/dev/sda 10M
TasksMax=512
EOF
systemctl daemon-reload
systemctl restart webapp batch-job
# 用 systemd-cgtop 监控
systemd-cgtop --depth=3 --delay=2
诊断容器资源问题
# 找到容器的 cgroup 路径
docker inspect mycontainer | grep -i cgroup
# "CgroupParent": ""
# cgroup 路径:/sys/fs/cgroup/system.slice/docker-<ID>.scope
CONTAINER_ID=$(docker inspect mycontainer --format '{{.Id}}')
CGROUP="/sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope"
# 查看容器是否被 CPU 限流
cat $CGROUP/cpu.stat | grep throttled
# nr_throttled 2500 ← 大量限流,说明 CPUQuota 不够
# 查看容器 OOM 次数
cat $CGROUP/memory.stat | grep oom_kill
# oom_kill 3 ← 被 OOM 杀了 3 次
# 查看容器当前内存使用
cat $CGROUP/memory.current
小结
cgroup v2 是容器、systemd、运维资源管控的底层基石。CPU cpu.max 和 cpu.weight 分别控制硬上限和竞争优先级;内存 memory.max 是硬限制,memory.high 是软限制做预警;IO io.max 精确到设备级别的带宽和 IOPS。生产中最有价值的操作是:通过 systemd unit 配置资源限制(而不是手动写 cgroup 文件),通过 cpu.stat 的 nr_throttled 判断 CPU 是否成为瓶颈,通过 memory.stat 的 oom_kill 发现隐性 OOM 问题。
