结论先行:硬实时线程不该去"哄"CFS 带宽节流,方向错了。 你在 cgroup v2 上却用了 v1 的文件名(cpu.cfs_quota_us / cpu.cfs_period_us / cpu.burst),v2 根本不认这些名字——你"调了"的很多参数其实没生效。真正的两条正路是:(A) 把 RT 线程从 CFS 里摘出来,用绑核(cpuset) + SCHED_FIFO;(B) 纯 cgroup 路线则必须改用 v2 的 cpu.max。而 cpu.burst 对周期性 RT 是救不了的,下面说清为什么。
一、先纠一个隐藏坑:v1 / v2 文件名混用
cgroup v2 里没有 cpu.cfs_quota_us,对应关系是:
| v1(你写的) | v2(实际生效的) |
|---|---|
| cpu.cfs_quota_us + cpu.cfs_period_us | cpu.max(格式 "$QUOTA $PERIOD") |
| cpu.cfs_burst_us | cpu.max.burst |
| cpu.stat | cpu.stat(字段名一致) |
所以你要这样写才真正生效(在容器 cgroup 目录下):
CG=$(cat /proc/self/cgroup | head -1 | cut -d: -f3)
D=/sys/fs/cgroup$CG
echo "200000 100000" > $D/cpu.max # 200ms/100ms = 200%
echo 5000000 > $D/cpu.max.burst
cat $D/cpu.stat # 看 nr_throttled / throttled_usec
二、为什么 cpu.burst 救不了你的 10ms 周期任务
cpu.max.burst 的语义是跨周期累积:把"本周期没用完的配额"存进银行(上限约 5 个周期 ≈ 500ms),供后续周期"透支"。它解决的是短时突发峰值,不是周期性硬实时。
你的采集线程是每 10ms 一次的规律尖峰。低负载时单次采集耗时短,一个 100ms 周期内的总耗时没撞配额,所以 p99 只有 0.3ms;高负载时(别的线程抢占、cache miss 变长)单次采集耗时被拉长,几次叠加就超过 50ms 配额 → throttle。burst 存的是"富余",而你恰恰没有富余,方向反了。这也是为什么"5 倍 burst"抖动改善不明显。
三、不独占节点的可行方案(按推荐度)
方案 A(治本,推荐):绑核 + SCHED_FIFO,把 RT 线程移出 CFS 带宽管控。
- 节点开 Static CPU Manager:kubelet
--cpu-manager-policy=static(或featureGates: CPUManager)。 - Pod 设
requests.cpu == limits.cpu且为整数核(如都填2),Static 策略会把整核通过 cpuset 划给它,独占核上不再受 CFS 带宽限制:resources: requests: { cpu: "2", memory: "1Gi" } limits: { cpu: "2", memory: "1Gi" } - 采集线程设 SCHED_FIFO,需
CAP_SYS_NICE:
启动时套一层(比全局 capabilities 更可控):securityContext: capabilities: add: ["SYS_NICE"]
坑:SYS_NICE 给到整个容器偏宽,建议只对这一个 RT 容器加;重启/重建后绑核由 K8s 重新分配,别手工写 cgroup 文件(会被 runtime 覆盖)。chrt --fifo 80 ./inference-server # 或进程内:sched_setscheduler(0, SCHED_FIFO, {sched_priority=80})
方案 B(次优,纯 cgroup 不碰调度策略):
用 v2 文件名给足配额 + 大 period:echo "500000 100000" > cpu.max(500%,共享节点上会挤压别人,需权衡),配合 cpu.max.burst。能缓解但仍可能被别的核抢占影响延迟,不如 A 稳。
方案 C(最稳,要动节点):
在节点上给该容器预配一个 cpuset 独占核的 cgroup:echo 0-0 > cpuset.cpus、echo "max 100000" > cpu.max(无限额),再叠 SCHED_FIFO。适合有专属边缘节点的场景。
四、容器内可复现的验收方法
CG=$(cat /proc/self/cgroup | head -1 | cut -d: -f3); D=/sys/fs/cgroup$CG
# 1) 是否还在被节流:连看两次,throttled_usec 增量应为 0
cat $D/cpu.stat
# 2) 是否已脱离带宽限制(绑核方案下应看到 max)
cat $D/cpu.max # 期望 "max 100000"
cat $D/cpuset.cpus # 期望独占核,如 "0"
# 3) 确认调度策略
chrt -p <采集线程tid> # 期望显示 SCHED_FIFO
# 4) 直接测抖动:容器内跑 10ms 打点脚本,统计 p99 是否 < 1ms
一句话总结:先确认你在 v2 上用的是 cpu.max 而不是 v1 文件名;再用"绑核 + SCHED_FIFO"把 RT 线程移出 CFS 带宽管控,而不是堆 burst。 你已验证"挪到独占节点抖动消失",方案 A 本质上就是在共享节点上把"独占核"精确还给那一个线程。
谢谢采纳!这个问题排查过程确实很典型——cgroup v1/v2 文件名混用是很多人(包括我)都踩过的坑。如果后续在绑核 + SCHED_FIFO 方案落地时遇到「独占核被其他 Pod 挤占」的问题(static 策略下偶尔会出现),可以再开帖讨论,我在 cgroup cpuset 隔离那块还有些实操数据可以分享。