Load Average:加了 CPU Cgroup 限制,为什么容器还是很慢?

发布时间:2026/7/24 16:36:22
Load Average:加了 CPU Cgroup 限制,为什么容器还是很慢? Load Average加了 CPU Cgroup 限制为什么容器还是很慢实验环境Ubuntu 24.04.4 LTS / 内核 6.8.0-106-generic / Cgroup v2unified hierarchy/ 华为云 FlexusX 实例 8 vCPU 16GB / Docker 29.1.3引子CPU 明明只用了 20%为什么接口还是 5 秒才返回你给容器设了--cpus2监控上docker stats显示 CPU 才用到 20%可业务方说 P99 延迟飙到了 5 秒。top一看load average: 18.32——负载高得离谱但 CPU 明明很闲。这俩现象放一起很多人第一反应是CPU 不够加核心。于是--cpus调到 4结果延迟一点没降load average 还是 18。问题出在Load Average 和 CPU 使用率根本不是一回事。而且 CPU Cgroup 能限制的是用了多少 CPU 时间却限制不了因为等 I/O 而卡在 D 状态的进程对 load average 的贡献。本文用一个 8 核实验机把 R 状态、D 状态、CFS 限流throttle三种慢逐一复现、逐一拆开。一、先讲清楚Load Average 到底是什么Linux 的 Load Average/proc/loadavg 那三个数1/5/15 分钟指数衰减平均统计的是当前处于 R 状态TASK_RUNNING在运行或在运行队列等待的进程数 处于 D 状态TASK_UNINTERRUPTIBLE不可中断睡眠的进程数内核里由calc_load()kernel/sched/loadavg.c每 5 秒采样一次做指数平滑。关键结论R 状态进程 正在用 CPU 或排队等 CPU。它们既抬高 load average也消耗 CPU。D 状态进程 卡在内核的不可中断睡眠里通常在等 I/O磁盘、网络文件系统、某些内核锁、vfork 父进程等。它们抬高 load average但不消耗任何 CPU——因为根本没在跑。S 状态TASK_INTERRUPTIBLE可中断睡眠进程比如sleep、read等待可读不计入load average能被信号唤醒。一个极易踩的坑/proc/loadavg里的running/total字段如17/321只统计R 状态nr_running并不含 D 状态。而前面那三个 load 平均数是含 D 的。所以你会看到running 只有 17load average 却有 12.97——差值就是 D 状态进程。这也是为什么load 高但 CPU 闲是自洽的D 状态进程让 load 爆表但它们一个 CPU 周期都没用。二、实验 1R 状态进程 → load 升高且 CPU 100%起一个不限 CPU 的容器里面stress-ng --cpu 88 个 CPU 压测进程正好压满 8 核dockerrun-d--namecpu-exp ubuntu:22.04...dockerexec-dcpu-exp stress-ng--cpu8--timeout180宿主机上启动前 vs 跑了约 65 秒后基线 load average: 1.74 2.36 1.24 1/267 运行65s后 load: 12.97 5.97 2.64 17/3211 分钟负载从1.74 涨到 12.97running/total 从1/267变成17/32117 个 R 状态任务在跑/排队。同一时刻宿主机真实 CPU 占用# mpstat 1 3 Average: all 99.00 0.00 1.00 0.00 ... 0.00 (CPU 几乎 100% busy)容器内ps也确认了 8 个stress-ng都处于R336 R stress-ng 337 R stress-ng 338 R stress-ng ... (共 8 个 R)结论R 状态进程 load 升高 CPU 跑满。这是真·CPU 瓶颈加--cpus或优化算法才有效。三、实验 2D 状态进程 → load 升高但 CPU 完全空闲用上一篇文章里那个vfork程序父进程 vfork 子进程后子进程sleep不退出父进程被 vfork 语义卡在D 状态既不消耗 CPU 也不响应信号。起容器dockerrun-d--named-exp-v/root/d_sleep:/d alpine /d4dockerexecd-expps-eopid,ppid,stat,comm容器内确认 1 号进程陷入 DPID PPID STAT COMMAND 1 0 D d -- 父进程卡在 TASK_UNINTERRUPTIBLE 7 1 S d -- 子进程在可中断睡眠 8 0 R ps宿主机上对比 load 与真实 CPU基线 load average: 1.77 1.76 0.82 9/336 运行后 load: 2.89 2.00 0.90 9/329 -- load 涨了约 1正好一个 D 进程 宿主机 mpstat: Average: all 0.00 ... 99.94 idle -- CPU 几乎 100% 空闲!这就是核心反例仅 1 个 D 状态进程就让 load average 抬了约 1而宿主机 CPU 99.94% 空闲。D 状态计入了 load average但没占哪怕一个 CPU 周期。顺手看一眼这个几乎空闲的容器的 cgroup 压力指标cpu.pressurePSI详见第六节全是 0 压力some avg100.00 avg600.00 avg3000.00 total19514 full avg100.00 avg600.00 avg3000.00 total14486更夸张的 D 状态现场如果一次性制造 8 个 vfork 父进程每个都卡在 D宿主机 load average 直接冲到load average: 17.68 8.65 3.80 16/348 宿主机 D 状态进程数: 8 35509 D stress-ng 35512 D stress-ng 35513 D stress-ng 35517 D stress-ng ...⚠️ 注意一个诚实的坑上面这个 17.68 是用stress-ng --vfork 8造出来的那一刻宿主机mpstat显示 CPU79% busy——但忙的不是 D 状态本身而是stress-ng在疯狂vfork的循环开销fork/exec 本身吃 CPU。D 状态进程自己依然 0 CPU。别把现场有很多 D 状态进程和CPU 是被 D 状态吃掉的混为一谈——前者真后者假。判断标准很简单看mpstat的%usr/%sys和 CPU 是否真的在跑。四、实验 3CFS 带宽限流throttle→ 另一种慢前面两节都是没加 CPU 限制的情况。现在加限制容器--cpus11 核里面却跑 4 个 CPU 压测进程必然超额被限流。dockerrun-d--cpus1--namethrottle-exp ubuntu:22.04...dockerexec-dthrottle-exp stress-ng--cpu4--timeout300读它的 cgroup v2cpu.stat在启动约 3 秒和约 23 秒各采一次cpu.max 100000 100000 -- 限额 1 核 --- t0 (约3s) --- usage_usec 8456737 nr_throttled 30 throttled_usec 7078724 -- 才3秒就被限流了 7.0M 微秒 --- t20 (再20s后) --- usage_usec 28456742 nr_throttled 230 -- 限流次数 30 - 230 throttled_usec 48877190 -- 累计被限流 48.9M 微秒 ≈ 48.9 秒的CPU拿不到docker stats同时把它卡在 1 核NAME CPU % MEM USAGE / LIMIT throttle-exp 100.31% 15.49MiB / 14.78GiB含义拆解这个容器想要 4 核但cpu.max只允许 1 核/每周期。CFS 带宽控制CFS Bandwidth Control在每个 period 把超额的部分整个 cgroup 挂起throttle直到下个 period 刷新额度。20 秒里它累计被限流48.9M µs ≈ 48.9 秒的 CPU 时间想要却拿不到——这就是为什么它慢而且这种慢和 D 状态毫无关系被限流的任务是 R 状态只是被内核暂时摘出运行队列它们不一定抬高 load averagethrottled 的任务不在 run queue 上nr_running不计数它们消耗 CPU 的能力被压制所以docker stats顶在 100%1 核但业务就是拿不到更多算力。五、CPU Cgroup 限制不了 D 状态对 load 的贡献把三节串起来回答标题那个问题为什么加了 CPU Cgroup 限制容器还是很慢因为 CPU CgroupCFS 带宽控制只管这个 cgroup 能用多少 CPU 时间它能限制 R 状态进程的 CPU 用量→ throttle见实验 3完全管不到 D 状态进程对 load average 的贡献——D 状态是进程卡在内核 I/O 等待里根本没在消耗 CPU 时间CFS 配额无从谈起也管不到等 I/O这件事本身——磁盘慢、NFS 卡死、内核锁竞争这些都不是 CPU 资源Cgroup 限不了。所以你给容器加了--cpus2如果它慢是因为大量 D 状态iowait / 存储 / 网络文件系统卡顿load average 照样高、top照样显示 CPU 很闲加 CPU 配额毫无用处。这正是加了限制还是很慢的典型场景。六、怎么正确排查容器的慢用 cgroup 指标别用容器内 top既然/proc/stat、/proc/loadavg在容器里是宿主机的全局值见本系列第二篇排查容器自身问题时必须用 cgroup 暴露的隔离指标。Cgroup v2 给了两套利器1.cpu.stat的nr_throttled/throttled_usec是否被打限流nr_throttled 230 throttled_usec 48877190持续上涨 容器 CPU 配额不够被 CFS 限流拖慢。这是加 --cpus 就能好的信号。2.cpu.pressurePSIPressure Stall Information—— 每个容器自己的负载压力Cgroup v2 的cpu.pressure给出本 cgroup 内任务因等 CPU 而停滞的时间比例是真正的容器级 load替代指标全局 load average 是混在一起、分不清是谁的# 一个几乎空闲的容器只有 1 个 D 进程不耗 CPU some avg100.00 avg600.00 avg3000.00 total19514 full avg100.00 avg600.00 avg3000.00 total14486而同一个实验机上一个被--cpus1限制、却跑 4 个 CPU 压测进程的容器同样的cpu.pressure长这样# 被 CFS 限流的容器--cpus1, stress-ng --cpu 4 some avg1055.18 avg6016.42 avg3003.76 total12260980 full avg1055.18 avg6016.42 avg3003.76 total12260965 nr_throttled 183 throttled_usec 53697940some avg1055.18表示最近 10 秒里有 55% 的时间至少有一个任务在等 CPU 而停滞——这就是 PSI 给出的这个容器自己的真实压力和全局 load average 那种混在一起、分不清是谁的数字完全不同。它和nr_throttled183 次互相印证容器确实被 CFS 带宽控制限流了。字段含义some 至少有一个任务在停滞full 所有非空闲任务都在停滞。avg10/60/300是 10 秒/1 分钟/5 分钟平均total是累计微秒。一个被限流的容器some/full的 avg 会明显大于 0配合上面nr_throttled一起看互相印证。对比全局 load averagePSI 是按 cgroup 隔离的能精确到哪个容器在等资源而全局 load average 把宿主机上所有容器的 R/D 混在一起没法归因。诊断决策树现象根因对不对 CPU Cgroup 管用第一反应docker statsCPU 顶满配额 nr_throttled涨CFS 限流throttle✅ 加--cpus立竿见影调大--cpuscpu.pressuresome/full 高 nr_throttled涨同上限流✅调大--cpus全局 load average 高但宿主机mpstat显示 CPU 很闲D 状态 / iowaitI/O 卡❌ Cgroup 限不了查磁盘/NFS/内核锁load 高且CPU 也忙真·CPU 瓶颈R 状态✅但不是加了限制才慢是本来就不够加 CPU 或优化算法容器内top说 CPU 才 10%误读看的是宿主机/proc/stat—以docker stats/cpu.stat为准一句话容器变慢先分两类——是被 CFS 限流throttle“还是卡在 D 状态iowait”。前者看nr_throttled/cpu.pressure加--cpus后者 CPU Cgroup 无能为力得去查 I/O 路径。七、小结与思考题Load Average R 状态 D 状态进程数R 既抬 load 又耗 CPUD 只抬 load 不耗 CPU。实测R 状态压测让 8 核机 load 从 1.74→12.97 且 CPU 99% 忙单个 D 状态进程让 load 1 而 CPU 99.94% 空闲8 个 D 进程把 load 顶到 17.68。CPU CgroupCFS 带宽控制只能限 R 状态的 CPU 用量D 状态iowait / 存储 / NFS / 内核锁对 load 的贡献它限制不了所以加了 --cpus 还是很慢往往是 D 状态在作怪。throttle 是另一种慢nr_throttled/throttled_usec持续上涨 配额不够与 D 状态无关加 --cpus 有效。排查用隔离指标cpu.stat的nr_throttled/throttled_usec判断是否被限流cpu.pressurePSI看每容器真实压力而不是容器里top/全局load average。思考题一个容器的进程大量卡在 D 状态比如 NFS 无响应宿主机的全局 load average 会被拉高但这个容器自己的cpu.pressure的some/full会高吗为什么nr_throttled在涨但容器的全局 load average 没怎么动——这矛盾吗怎么解释如果把一个卡在 D 状态的进程从容器里kill -9能杀掉吗结合本系列第一篇的SIGNAL_UNKILLABLE想想为什么提示D 状态连 SIGKILL 都不响应但原因和 init 不同。Cgroup v1 没有cpu.pressurePSI 是 v2 才有的在 v1 系统上你还能用什么指标近似判断某容器是否在等资源