在一个4核测试节点上,同一个.NET服务、同一个250m的CPU request、同一份负载,唯一的变量是要不要再挂一条500m的CPU limit——挂了之后,尾延迟慢了2.4倍。更扎眼的是,测试负载平均只需要320m,远没摸到500m的上限,但被限制的容器在负载高峰期仍有89%的100毫秒窗口被内核直接冻结,而监控面板上的CPU曲线全程健康,看不出一丝异常。
这组数据出自GitHub上一份分析报告《Kubernetes CPU limits make your apps slow and costly》。作者做的事很直接:把Kubernetes社区多年沿用的"CPU request+CPU limit都要设"的资源配置惯例,用同一套代码重新测了一遍,得出的结论是——limit该扔,request该留,memory limit不能动。这套说法要是站得住,等于是在质疑一条写进无数运维手册的默认动作。
100毫秒窗口里,配额是怎么被吃光的
Linux内核每100毫秒算一次账。500m的limit意味着这100毫秒里只能用50毫秒CPU时间,用完了整个容器(不管里面跑了多少线程)直接被冻结,等下一个窗口。一个典型的.NET服务同时跑着HTTP处理线程、后台消费者和GC,八个线程一起抢配额,50毫秒的预算大约12.5毫秒就能耗尽——冻结因此一天能密集发生到10次/秒。
这跟CPU request完全是另一回事。request对应cgroup的cpu.weight,决定节点繁忙时谁分多少;limit对应cpu.max,是内核按周期强制执行的硬顶。没有limit时,Linux自带的CFS调度器(Completely Fair Scheduler)已经按request权重公平分配CPU,某个pod闲下来,别的pod可以借用它的空闲份额,一旦它重新有活儿干,份额立刻还回去——这套机制不需要limit配合就能运转,limit唯一多做的事,是在配额用完时把应用整个冻住。
监控全绿,是因为没人盯这个指标
限流之所以危险,是因为它躲得过标准监控。1分钟平均CPU曲线永远看不出问题,因为限流是毫秒级的瞬时冻结,一被平均就消失,除非专门去查container_cpu_cfs_throttled_periods_total这个指标。报告里另有一个佐证:一个CPU密集型的启动过程(容器跑dotnet run要现场编译),去掉limit后耗时缩短了大约2倍;作者也提醒,如果启动阶段主要是等I/O而不是烧CPU,这个倍数会小很多,不要当成普适结论。
Postgres被单独点名,是因为它在自我调优(缓冲区大小、并行度)时压根不看cgroup配额——这意味着一个被limit卡住的Postgres容器,可能同时陷入"自己觉得资源够用、实际被内核偷偷限制"的双重错位。
论证留了几个口子,还没缝上
这份报告最扎实的地方是有配套的六份技术文档撑腰,从cgroup机制讲到成本模型再到灰度上线方案,看起来是一套系统论证。但细看会发现几处没兑现的承诺。
作者自己提到"有一种配置下limit确实有用",正文却没有展开是什么场景;反对意见文档的标题里列了噪声邻居、HPA/KEDA联动、QoS class变化、多租户隔离这四条最常见的质疑,摘录里同样只见标题不见论证;被反复引用的"每年省下数万美元"的成本模型,作者自己标注为"illustrative"(示例性质),不是真实账单;整套基准测试目前只有这一个仓库、一个作者跑出来的数字,还没有第三方复现的记录。
单一PoC能提出好问题,但还撑不起一条新的行业共识。
谁该现在就试,谁该再等等
- 结论.多数集群CPU利用率长期处于个位数到低两位数,可压缩的request-limit差值,本身就是真金白银的超配空间。
对平台工程和FinOps团队来说,这套论证最值钱的部分不是"limit有害",而是提醒他们回头看一眼自己集群的真实利用率——如果峰值也用不到request预留的一半,去掉limit、把request收紧,是能落地的省钱动作。报告给出的六步灰度方案,先补一个LimitRange兜底默认request,再逐步摘limit,算是一条相对稳妥的路径。
- 风险.多租户、强隔离要求的共享集群,去掉limit后的噪声邻居风险目前只有一份未经第三方复现的PoC背书,贸然照搬容易踩坑。
对跑在同一个共享集群、彼此不完全信任的团队而言,这个建议更适合先观察——等有人拿真实生产集群复现这组数字,或者官方文档更新指导原则,再决定要不要跟。
