结论:不需要"拍 150% headroom",用一个三步公式直接算出 quota 和 burst 的精确值。核心原则:limit 要覆盖「稳态 + 突发」的叠加峰值,burst 要覆盖 deficit × 连续突发次数。
Step 1: 算 quota(limit)
CFS bandwidth controller 每 100ms 一个 period,cgroup 可用 limit × 100ms 的 CPU 时间。你的服务在突发窗口内的 CPU 需求是:
peak_cores = steady_cores + burst_cores = 0.6 + 3.8 = 4.4 cores
这已经超过了 limit=4——即使突发任务单独跑 3.8 < 4 看似够,但加上稳态的 0.6 就超了。这就是你 stress-ng 压测时 P99 从 95ms 飙到 210ms 的根因。
定量公式:
limit_min = (steady_cores + burst_cores) × safety_factor
- safety_factor:Java 服务建议 1.2~1.3(GC、JIT、线程调度开销)
- 你的场景:4.4 × 1.25 ≈ 5.5 → limit = 6
验证:
per_period_quota = 6 × 100ms = 600ms
burst_window_need = 4.4 × 1000ms = 4400ms(取 1s 突发窗口上限)
burst_window_quota = 6 × 1000ms = 6000ms
headroom = 1600ms / 6000ms ≈ 27%(够用)
Step 2: 算 burst(cpu.max.burst)
如果你坚持 limit=4(成本约束),必须靠 burst 补 deficit:
deficit_per_window = (steady + burst) × duration - limit × duration
= 4.4 × 1000 - 4 × 1000 = 400ms
你之前设 burst=200ms 不够,因为 deficit 是 400ms,200 只补了一半。
考虑连续突发(30s 一次,可能连续 2~3 次)+ 安全余量:
burst_total = deficit × consecutive_bursts × safety
= 400 × 3 × 1.5 = 1800ms
→ cpu.max.burst = 1800000 (µs)
⚠️ K8s 1.29 目前没有原生的 cpu.max.burst 字段(cpuCFSBurst feature gate 在 1.29 还是 alpha,1.30 才 GA)。生产环境两个选择:
- 用 init container / sidecar 直接写 cgroup:
echo 1800000 > /sys/fs/cgroup/<pod-cgroup>/cpu.max.burst - 等 K8s 1.30+ 用原生字段(
spec.containers[].resources.limits.cpuCFSBurst: 1800ms)
Step 3: 压测口径(复现"稳态 + 突发叠加")
stress-ng 只能验证纯 CPU 天花板,不能复现"稳态 0.6 核 + 突发 3.8 核"的叠加模式。推荐组合:
# 1. 稳态负载:6 个线程各跑 10% duty cycle 的 busy loop ≈ 0.6 核
stress-ng --cpu 6 --cpu-load 10 --timeout 600s &
# 2. 突发脉冲:每 30s 触发一次 3.8 核 × 1s
python3 -c "
import time, os, multiprocessing
def busy():
end = time.time() + 1.0
while time.time() < end: pass
for i in range(20):
ps = [multiprocessing.Process(target=busy) for _ in range(3)]
# 3 个 100% 进程 ≈ 3 核,再加 stress-ng 的 0.6 ≈ 3.6~3.8 核
for p in ps: p.start()
for p in ps: p.join()
print(f'burst #{i+1} done')
time.sleep(30)
" &
# 3. 监控(关键指标,每秒采样)
watch -n1 'grep -E "nr_throttled|nr_periods|throttled_time" /sys/fs/cgroup/<pod>/cpu.stat; cat /sys/fs/cgroup/<pod>/cpu.max'
判定标准(cpu.stat):
| 指标 | 安全 | 有隐患 | 必须调 |
|---|---|---|---|
| nr_throttled / nr_periods | < 1% | 1%~5% | > 5% |
| throttled_time 增量 | < 10ms/s | 10~50ms/s | > 50ms/s |
Step 4: 防回退(治理)
这是最容易被忽略的一环。推荐用 Kyverno(比 OPA Gatekeeper 轻量)写一条 policy:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: protect-cpu-burst-sensitive
spec:
rules:
- name: min-limit-for-burst-services
match:
any:
- resources:
kinds: [Pod]
annotations:
cpu-burst-sensitive: "true"
validate:
message: "cpu-burst-sensitive 服务的 CPU limit 不得低于 5000m"
rule:
any:
- resourceDescription:
fieldPath: spec.containers[*].resources.limits.cpu
operator: greaterThan
condition: "5000m"
配套流程:
- 所有「突发型」服务(批处理、报表、聚合)在 Deployment 上打
cpu-burst-sensitive: "true"注解 - 资源治理脚本批量调 limit 前,先
kubectl get pods -A --show-labels | grep cpu-burst-sensitive过滤出这批服务,单独走审批 - 每季度 review 一次 limit/burst 配置(业务负载会变,3.8 核的峰值可能变成 4.5 核)
一句话总结:limit = (稳态 + 突发) × 1.25,burst = deficit × 连续次数 × 1.5,压测用"稳态 busy loop + 定时脉冲"复现真实模式,治理用 Kyverno policy 锁住下限。你之前 burst=200ms 不够的根因就是没算上稳态的 0.6 核叠加。