bigywater.tech
Kubernetes · 38

CPU limit 与节流:requests 和 limits 在 cgroup 里的映射

CPU limit 到底是怎么限制容器的?requests 又在什么时候起作用?这篇直接读 cgroup 的 cpu.max、cpu.weight 和 cpu.stat,用死循环做对照,把限流的计数、没有 limit 的容器能用多少核、竞争时按什么比例分都量出来。

2.06 秒 / 10.3 秒
limit 200m 的死循环在窗口内用掉的 CPU 时间,以及窗口长度
1 : 3.08
requests 100m 与 400m 的两个容器在同一节点吃满核时的 CPU 用量比(权重比 2.88)
4.3 → 54.8 秒
同样的计算在没有 limit 与 limit 100m 下的墙钟时间

limit 是每个周期的配额

实测 · 本机执行输出
  cpu.max=20000 100000(每 100000 微秒的周期里最多 20000 微秒);cpu.weight=17
  10.3 秒的窗口里:用掉 CPU 2.06 秒(= 20% 个核);nr_periods=102 nr_throttled=79(77% 的周期被限流);throttled_usec=4.64 秒

limit 200m 在 cgroup 里变成 cpu.max=20000 100000:每 100 毫秒周期里最多跑 20 毫秒。一个死循环想用满整个核,10.3 秒的窗口里只用掉 2.06 秒 CPU(20%);102 个周期里有 79 个用完了配额被停住(nr_throttled),throttled_usec 累计 4.64 秒就是被迫等待的时间。requests 变成 cpu.weight=17,和限流无关。

没有 limit 就是自由的

实测 · 本机执行输出
  free4(4 个死循环,没有 limit):cpu.max=max 100000;10.3 秒用掉 CPU 41.3 秒(平均 3.99 个核);被限流的周期占 0%
  lim1(4 个死循环,limit 1 核):cpu.max=100000 100000;10.5 秒用掉 CPU 10.5 秒(平均 1.00 个核);被限流的周期占 100%

同样 4 个死循环:没有 limit 的容器平均用了 3.99 个核,被限流周期 0%;limit 1 核的只有 1.00 个核,被限流周期 100%。没有 limit 的容器能用到节点上所有空闲的核,所以“requests 设得很小、不设 limit”并不会限制它用多少 CPU。

requests 只在竞争时起作用

实测 · 本机执行输出
  small(requests 100m,12 个死循环)cpu.weight=17;big(requests 400m,12 个死循环)cpu.weight=49
  同一次读取里取两个容器的 cpu.stat,间隔 12 秒:small 用了 28.9 秒 CPU,big 用了 89.2 秒 CPU,比例 big/small = 3.08(两个 cpu.weight 的比例是 2.88);合计 118 秒 ≈ 9.8 个核,节点共有 10 个核,整台节点的忙碌时间是 107 秒)

在同一个节点上各跑 12 个死循环,把 10 个核吃满:requests 100m 的容器 cpu.weight=17,400m 的 cpu.weight=49;同一次读取里量 12 秒,用量分别是 28.9 秒和 89.2 秒,比例 3.08,接近权重比 2.88,合计约 9.8 个核。没有竞争时 requests 不限制任何东西,有竞争时按它的比例分。

限流就是变慢

实测 · 本机执行输出
  w-free(没有 limit):awk 做 2000 万次加法的墙钟时间 4.3 秒
  w-500(limit 500m):awk 做 2000 万次加法的墙钟时间 8.4 秒
  w-100(limit 100m):awk 做 2000 万次加法的墙钟时间 54.8 秒

awk 做 2000 万次加法:没有 limit 4.3 秒,limit 500m 8.4 秒,limit 100m 54.8 秒。CPU 密集的程序在 limit 下的墙钟时间远大于它真正占用 CPU 的时间。排查延迟毛刺时要看 nr_throttled 与 throttled_usec,只看平均 CPU 使用率会误以为还有余量。

分步演示

CPU requests 与 limits
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0,每个节点 10 个核,cgroup v2);死循环是 busybox sh 的 (while :; do :; done) &;cpu.max、cpu.weight、cpu.stat 直接用 docker exec 从节点的 /sys/fs/cgroup/kubelet.slice/… 读出,窗口前后各读一次(第 3 节在同一次 docker exec 里同时读两个容器,避免读取先后造成的误差——最初分别读取得到了超过 10 个核的假结果);第 4 节的计算是 awk BEGIN{for(i=0;i<20000000;i++)x+=i};每种情形各一次
    • 没有验证:cpu.max.burst(突发配额);CPU Manager static 策略下 Guaranteed Pod 的独占核不受限流;cpuset 与 limit 的关系;内核 CFS 带宽控制的已知问题(多线程应用的限流放大);cpu limit 在不同内核版本上的行为差异;kubectl top 的数字(本集群没有 metrics-server)
    京ICP备2026067502号-1京公网安备11010502063487号