namespace 让进程”看到啥”,cgroup 让进程”用多少”。Linux 4.5 引入的 cgroup v2(现在各大发行版已是默认)把 v1 混乱的多 hierarchy 统一成一棵树,把资源控制器(controller)统一到一套语义。本文讲 v2 的核心概念、关键 knob、PSI 压力指标、和 systemd 的整合。
一、总览
flowchart TB
R[/sys/fs/cgroup]-->SLICE[system.slice]
R-->USLICE[user.slice]
R-->INIT[init.scope]
SLICE-->A[nginx.service]
SLICE-->B[postgresql.service]
SLICE-->C[docker-XXX.scope]
USLICE-->U1000[user-1000.slice]
U1000-->S1[session-1.scope]
A-. cpu.weight=100 .->R
A-. memory.max=2G .->R
B-. io.weight=200 .->R
classDef sys fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef u fill:#f0883e22,stroke:#f0883e,color:#adbac7;
classDef svc fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class R,SLICE,INIT sys
class USLICE,U1000 u
class A,B,C,S1 svc
关键设计:
- 每个进程只属于一个 cgroup(不像 v1 可跨多 hierarchy)
- 树结构即挂载:
/sys/fs/cgroup/xxx/yyy/... - 子 cgroup 要用哪个 controller,父 cgroup 要
cgroup.subtree_control += +cpu +memory +io - 叶子节点才能有 task(内部节点只做分组;“no internal process” 约束)
二、controller 清单(v2)
- cpu:cpu.weight(1-10000,默认 100,CFS/EEVDF 权重)、cpu.max(绝对配额 quota/period)、cpu.pressure(PSI)
- memory:memory.min(保护下限)、memory.low(软保护)、memory.high(触发回收压力的软上限)、memory.max(硬上限,超了 OOM)、memory.swap.max、memory.events、memory.stat、memory.pressure
- io:io.weight(权重)、io.max(IOPS/bytes 绝对上限,BFQ/block 层)、io.latency(目标延迟)、io.pressure
- pids:pids.max(限制可 fork 数量,防 fork bomb)
- cpuset:cpuset.cpus、cpuset.mems(NUMA 绑定)
- hugetlb:huge page 配额
- rdma、misc:边缘场景
三、key knobs 深入
3.1 cpu.weight vs cpu.max
- weight:比例公平——负载高时按比例分;没负载时可抢
- max:绝对上限——永远不超,即使 CPU 空闲
生产建议:优先 weight;需要严控(SLA / billing)用 max。
3.2 memory.max vs memory.high
- max:硬墙,触发 cgroup 级 OOM
- high:软墙——到达后内核对该 cgroup 施加回收压力;进程不会被杀,但被”拖慢”
Kubernetes 的 “memory.high” 用法:limits 对齐 memory.max,保 hard-bound;requests 对齐 memory.min,保最低。
3.3 io.weight 的历史包袱
v1 的 blkio 子系统只在 CFQ 下有效;移到 v2 后 io.weight
依赖 BFQ 或 mq-deadline(有限支持)。云厂商常用 NVMe + none
scheduler,weight 就失效——容器 I/O
隔离经常是”看起来设了其实没效”。真限流用
io.max 绝对 IOPS/MBps。
四、PSI:Pressure Stall Information
v2 独有,4.20+。提供 “some” / “full” 两种指标:
/sys/fs/cgroup/foo/cpu.pressure
some avg10=0.12 avg60=0.05 avg300=0.01 total=12345678
full avg10=0.00 ...
- some:至少一个 task 在等该资源的时间比例
- full:所有 runnable task 都在等的比例
含义:some=30% 表示这个 cgroup 里过去 10
秒有 30% 时间”有人在等 CPU”。比 load average 准确得多——load
混合了 R+D 且是全局。
应用:
- Facebook oomd / systemd-oomd 监控 PSI,达到阈值就主动杀;避免 kernel OOM 大延迟
- Kubernetes node-pressure eviction
- PID 调度器可查 PSI 决定 throttle
五、systemd 与 cgroup 的协作
systemd 是 cgroup v2 管理器的事实标准。它把系统组织成三类单元:
- .slice:分组容器(system.slice、user.slice、user-1000.slice)
- .service:持久服务
- .scope:程序外部创建的进程组(比如 login session、docker 容器)
systemctl set-property nginx.service CPUQuota=200%
会写到 system.slice/nginx.service/cpu.max。
systemd-cgls
看树;systemd-cgtop 看实时。
六、delegation:把子树交给 rootless
user ns + cgroup delegation 让非特权用户管理自己的 cgroup:
systemctl --user status
# 在 user-1000.slice 下创建、调整自己的 cgroup
systemd.unit(5) 的 Delegate=yes
开启。rootless podman、flatpak 都用这个。
七、cgroup v2 的常见使用姿势
7.1 让服务受 OOM 保护
[Service]
MemoryMin=256M # 保护下限
MemoryHigh=1G # 软上限
MemoryMax=1500M # 硬上限
7.2 IO 绝对限流
[Service]
IOReadBandwidthMax=/dev/nvme0n1 100M
IOWriteBandwidthMax=/dev/nvme0n1 100M
7.3 CPU 亲和与配额
[Service]
CPUAffinity=4-7
CPUQuota=200% # 最多用 2 个核的时间
CPUWeight=1000
7.4 限制 fork
[Service]
TasksMax=512
八、诊断
cat /sys/fs/cgroup/foo/cpu.stat
# usage_usec, user_usec, system_usec, nr_periods, nr_throttled, throttled_usec
cat /sys/fs/cgroup/foo/memory.events
# low high max oom oom_kill
cat /sys/fs/cgroup/foo/io.stat
# rbytes wbytes rios wios ...
观察 throttled 比例判断是否打到 quota;memory.events 里 oom_kill 递增说明被杀;io.stat 结合 PSI 看磁盘瓶颈。
bpftrace 有 tracepoint:cgroup:cgroup_rmdir
等。
九、v1 → v2 迁移的坑
- 所有 controller 必须同树:v1 里 cpu 和 memory 可挂在不同 mount point;v2 不行
- 部分 knob 语义变了:比如 v1 的
memory.limit_in_bytes等于 v2 的 memory.max;但 swap 计算方式不同 - pod 里 NVIDIA 驱动等工具链曾不兼容 v2,现已大部分支持
- Kubernetes 1.25+ 默认 v2;docker 20.10+ 支持
容器运行时会自动按发行版选 v1/v2;应用一般无感,但监控系统(cAdvisor、node_exporter)要分别处理路径。
十、常见 bug
bug A:CPU quota 设了但延迟还是抖 原因:100ms period 默认;短 quota 频繁被 throttle 解决:增大 period 或放宽 quota;或 pinning + weight
bug B:memory.high 压力大但不 OOM 现象:进程卡顿、page fault 暴涨 原因:内核持续 reclaim;交换开启时尤甚 解决:调整 memory.max、memory.swap.max、或引入 PSI 监控
bug C:io.max 限流不生效
原因:调度器是 none,v2
限流依赖 BFQ 或特定路径
解决:cat /sys/block/nvme0n1/queue/scheduler;换调度器;或
NVMe 原生 I/O priority
bug D:容器里 nproc 和 cgroup TasksMax 冲突 原因:RLIMIT_NPROC 是 per-uid;TasksMax 是 per-cgroup 解决:同时设置合理值
十一、小结
- v2 统一 hierarchy、统一语义
- cpu.weight/io.weight/memory.max 是三条主 knob
- PSI 是资源压力的权威指标,取代 load/usage 率做决策
- systemd slice/scope/service 是日常接口,直接写 cgroupfs 是运行时/诊断场景
- delegation + user ns 是 rootless 基石
至此子系列 B 完结。下一篇将进入子系列 C:调度器深度(C-19 起)——从调度理论到 CFS/EEVDF 内部、实时与 deadline、多核负载均衡、big.LITTLE、调度延迟诊断。
参考文献
- Linux kernel:
Documentation/admin-guide/cgroup-v2.rst - Rosen, R. Linux Kernel Networking; Bovet & Cesati Understanding the Linux Kernel ch. 14 (v1)
- Heo, T. “cgroup v2: unified hierarchy.” LWN.net 系列
- Weiner, J. “PSI — Pressure Stall Information.” kernel commit / LWN 2018
- systemd.resource-control(5)
- “The current state of kernel page-error handling” LWN (memory.high 行为)
工具
systemd-cgls/systemd-cgtop—— 实时cat /sys/fs/cgroup/.../cpu.stat memory.events io.stat—— 原始psi-monitor(示例脚本)—— 读 /proc/pressurebpftrace -e 'tracepoint:cgroup:*'cgroupv2tree(oomd 配套)
上一篇:namespace:容器的内核根基 下一篇:调度理论:从 FCFS 到 MLFQ 到 CBS
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】调度延迟分析:是不是调度器的锅?
用户抱怨「慢」时第一问题:是 CPU 本身跑得慢,还是调度器让你等?本文讲 runq latency、wakeup latency、block time 三线拆分;perf sched、bpftrace runqlat、schedviz 工具链;生产案例:CFS quota throttle、NUMA 迁移、PSI 告警。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。