Linux/K8S 生产排查台账:CPU 闲置但接口卡死 (CFS 调度器限流排查指南)
01 前置理论与核心机制
1. 为什么 CPU 只有 30% 接口却出现卡顿?
- 监控工具的采样周期限制:常规使用的
top、docker stats或 Prometheus 监控面板,数据采样精度通常为秒级(1s - 15s)。监控展示的 CPU 30%,是 1 秒内的平均利用率。但在毫秒级的时间窗口内,容器线程可能经历了频繁的“运行-被强制挂起-运行”状态切换。 - 排查二分法:当 CPU 未打满且排除了代码层面的死锁与锁竞争时,接口耗时增加通常只有两种底层原因:要么线程在等待 I/O(如磁盘、网络),要么线程被内核剥夺了 CPU 执行权(强制挂起)。
2. Linux CFS 调度器与微观限流机制 (Quota Throttling)
- 时间切片分配:K8s 的
limits.cpu(或 Docker 的--cpus)底层依赖 Linux cgroups 的 CFS(完全公平调度器)。CFS 并非按“整秒”分配 CPU,而是默认以 100 毫秒 (100,000 微秒) 为一个调度周期(cfs_period_us)。 - 多核并发耗尽配额:假设配置了
--cpus=1(配额cfs_quota_us=100000),代表每 100 毫秒周期内,容器最多使用 100 毫秒的 CPU 时间。若宿主机有 8 个物理核,且容器内有 8 个并发线程同时处理请求,它们仅需运行 12.5 毫秒(12.5ms * 8 = 100ms),就会将该周期的 100 毫秒配额全部耗尽。 - 强制挂起:配额耗尽后,内核会在剩余的 87.5 毫秒 内将容器内的所有线程强制挂起(Throttled)。业务处理随之停滞,直到下一个 100 毫秒周期开始才会被唤醒。这是导致“CPU 平均使用率低,但接口延迟高”的核心原因。
02 故障排查与恢复闭环 SOP
遇到监控面板上 CPU 利用率不高,但业务接口出现几百毫秒至秒级的偶发延迟毛刺时,按以下步骤排查:
步骤一:锁定容器 PID 与排除 I/O 阻塞
优先确认是否存在磁盘 I/O 阻塞:
# 获取目标容器在宿主机上的真实 PID
docker inspect -f '{{.State.Pid}}' <container_name>
# 使用 pidstat 观测该进程的详细 I/O 与 CPU 状态 (每秒1次,输出3次)
pidstat -u -d -p <PID> 1 3
- 观测点:重点查看
iodelay列以及kB_rd/s、kB_wr/s。如果iodelay为 0 或处于极低水平,且用户态 (%usr) 存在一定消耗,说明线程并未阻塞在磁盘 I/O 上。排除 I/O 瓶颈后,转入内核调度排查。
步骤二:检查内核 Cgroup 调度统计数据
读取 Cgroup 底层文件,确认容器线程是否被 CFS 调度器限流:
# 查看容器的 CPU 调度统计信息 (路径根据 cgroup v1/v2 或 K8s 路径略有不同)
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.stat
# K8s 环境下通常在: /sys/fs/cgroup/cpu/kubepods/pod<uid>/<container_id>/cpu.stat
- 核心观测点:
nr_periods:经过的调度周期总数。nr_throttled:容器触发限流的周期总数。throttled_time(或throttled_usec):容器被强制挂起等待的累计时间(纳秒/微秒)。
- 定性判断:在压测或业务高峰期,连续多次执行
cat cpu.stat。若观察到nr_throttled和throttled_usec数值持续快速增长,即可确认容器正在触发严重的 CPU 调度限流。