拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Kubernetes CPU limits为何会导致延迟飙升?正确管理容器资源的方法

在 Kubernetes 集群里排查性能问题有一个非常反直觉的现象某个服务的 CPU 使用率明明只有 500 毫核配额给了 1 核按理说余量充足但接口延迟就是居高不下。这时候很多人的第一反应是去看 GC、看 SQL、看 Redis折腾一圈之后才发现真正的瓶颈藏在limits里。Kubernetes 社区有一篇文章的标题把这件事说得很直白For the love of god, stop using CPU limits on Kubernetes。这个标题看起来像气话但背后是一个讨论了很多年、依然被大量团队踩中的真实问题CPU limits 不是普通的“资源上限提醒”它会触发内核级的 CPU 节流而节流对延迟敏感型服务的影响相当隐蔽。这篇文章会讲清楚 requests 和 limits 的本质区别用一个最小实验演示 limits 为什么会拖慢服务再给出生产环境里到底该不该设 limits 的判断方法。读完你会明白正确的做法不是无脑去掉 limits而是理解资源模型之后把它放回它该在的位置。1. 为什么社区一直劝你“停止使用 CPU limits”先看一个高频场景。团队在 Kubernetes 上部署了一套 Java 网关为了保证稳定性给每个 Pod 设置了resources.limits.cpu: 1同时requests.cpu: 500m。在机器负载不高的时候一切正常一旦流量上来CPU 总使用率还不到节点的一半服务却开始出现大量超时。查看监控Pod 的 CPU 使用率曲线带着明显的锯齿每隔一段时间就被“压平”而 GC 时间、线程池指标都很正常。这不是业务代码的锅而是 limits 触发了 CFS 配额限制。Kubernetes 把 limits 下发到底层容器运行时最终会写到 Linux cgroup 的 CPU 配额上。cgroup 会严格限制容器在每个调度周期内最多使用的 CPU 时间超过部分不是排队而是直接冻结线程等技术术语叫 throttle。对 Web 服务、API 网关这类对延迟敏感的进程来说线程被冻结几十毫秒反映到用户端可能就是一次 P99 超时。更麻烦的是这种节流不会杀掉进程不会报错日志里看不出异常让排查方向完全跑偏。所以社区里喊出的所谓“停止使用 CPU limits”针对的不是“资源管理”本身而是“把 limits 当成万能保险丝”这种默认习惯。CPU 本来就是可压缩资源超出 limits 不会导致进程被杀只会让线程在配额耗尽时被暂停。如果请求量本身有长期峰值节流会成为一个持续性的性能瓶颈如果只是偶尔超过配额节流则是间歇性的不确定性来源。对延迟敏感的业务这种不确定性比 OOM 更难受因为它无法通过重启恢复也很难通过日志定位。那是不是所有场景都应该去掉 limits也不是。后面会讲到批处理、离线任务、测试命名空间仍然需要 limits 来防止负载失控。真正的核心是搞清楚 requests、limits、QoS、cgroup 节流之间的关系而不是把某个配置项一刀切地禁止。2. 先把基础概念对齐requests、limits、QoS很多 Kubernetes 面试题都会问 requests 和 limits 的区别但真实项目里能讲清楚的人并不多。如果只是背定义很容易产生一个误解requests 是给调度器看的limits 是给运行时看的两者互不影响。这个理解不算错但漏掉了最重要的部分——它们在节点上落地时走的是完全不同的机制。2.1 requests 与 limits 分别管什么requests 是容器对资源的“需求声明”。调度器为 Pod 选节点时会把所有容器的 requests 加起来判断节点剩余可分配资源是否够用。它还会转换成 cgroup 的 CPU 权重也就是 CPU shares。当节点 CPU 充足时这个权重几乎不会产生可感知的影响当多个 Pod 竞争 CPU 时内核按权重分配时间片。所以 requests 决定了“你需要多少”以及“CPU 紧张时你能分到多少”。limits 是容器对资源的“上限声明”。调度器不会把 limits 当作调度依据Node 上的可分配资源不包含 limits但它会转换成 cgroup 的 CPU 配额。Linux CFS 调度器按周期检查配额假设一个周期是 100mslimits 设为 1 核那么容器在这个周期内最多只能使用 100ms 的 CPU 时间。无论容器里有多少个线程、多少个进程共享的都是这个配额。一旦用完剩余时间要么等待下一个周期要么被强制暂停。用表格看会更清楚维度requestslimits主要作用调度器选节点的依据cgroup 对 CPU 使用的硬性上限内核机制CPU shares 权重CFS quota 配额CPU 充足时几乎无影响会持续限制超限即节流CPU 紧张时按权重参与竞争超限线程被冻结是否导致进程被杀否否但会导致 latency 飙升2.2 QoS 分级Guaranteed、Burstable、BestEffort根据 requests 和 limits 的组合方式Pod 会被划分到三个 QoS 等级。这个等级直接影响节点资源不足时 Pod 的驱逐优先级。QoS 等级设置方式特点Guaranteedrequests 等于 limits且内存、CPU 都设置资源保障最高默认最不容易被驱逐Burstable至少有一个容器设置了 requests但 requests 不等于 limits最常见如果 limits 大于 requests就存在超用可能BestEffort没有设置任何 requests 和 limits系统尽力而为资源紧张时最先被驱逐很多团队把 Pod 都配成 Guaranteed理由是“这样最稳定”。但如果 CPU limits 设置得过低Guaranteed 的代价就是每个周期都可能发生节流。QoS 保障的是“不被邻居抢走资源”它并不能缓解“你自己给自己设了上限”。2.3 CPU 是可压缩资源不等于可以随便超用Kubernetes 把资源分成可压缩和不可压缩两类。内存是不可压缩资源超过 limits 的后果是 OOM Kill进程直接死掉重启后恢复问题定位起来反而直接。CPU 是可压缩资源超过 limits 时进程不会被杀而是被内核节流。这种“看起来还活着实际时不时被暂停”的状态在监控图表上往往表现为使用率被削平、延迟升高非常具有欺骗性。还有一个常见误区是认为“单个容器只用 500mlimits 给 1000m肯定够”。CFS 配额是全体进程共享的。如果一个 Java 应用触发了 Full GC或某个线程池突发创建了大量线程瞬时 CPU 需求超过配额所有线程都会被一起暂停。也就是说limits 并不只限制“超出的那部分”它会在配额耗尽的瞬间冻结整个容器的执行流包括那些本来只用了很小 CPU 的线程。3. CPU limits 的副作用为什么这么隐蔽如果你只是写了一个每秒钟请求量很低的内部服务CPU limits 带来的影响可能很难察觉。但一旦服务对延迟敏感或者行为存在周期性峰值问题就会暴露出来。理解副作用需要从 Linux CFS 配额原理说起。3.1 CFS 配额与节流的原理在 cgroup v2 环境下容器对应的 cgroup 目录里有一个cpu.max文件格式是$MAX $PERIOD。比如100000 100000表示每个 100ms 周期最多运行 100ms也就是 1 核。如果改成50000 100000表示每个周期最多 50ms对应 0.5 核。当 cgroup 内所有进程在一个周期内累计运行时间达到$MAX后剩下未用完的时间片会被回收进程进入 throttle 状态直到下一个周期开始。这个机制本意是公平分配 CPU但它没有感知业务优先级。对一个实时性要求较高的 Web 服务来说线程在请求处理中途被冻结等待重新调度这就是延迟抖动的主要来源。一个容易忽略的细节是CFS quotas are accounted at the cgroup level, not per-thread level。也就是说多线程应用的线程之间共享配额。即便你的应用把线程数开到 200在配额耗尽时 200 个线程会被同时暂停而不是每个线程单独分配一份 CPU 时间。3.2 节流对延迟敏感服务的影响延迟敏感型服务的典型特征是短请求、高并发、对 tail latency 敏感。这类服务平时 CPU 使用率低但偶尔会有短促的峰值。如果 limits 设置得恰好低于峰值需求就会在流量抖动时触发节流制造大量 P99 超时。团队通常会先怀疑代码性能、依赖组件问题花很长时间排查后才发现是资源配额造成的。更麻烦的是节流与 CPU 使用率不是同步的。假设 limits 是 2 核应用实际平均使用 1 核但其中有一个 50ms 的突发使用 3 核配额就会超支。监控看到的平均使用率不高看不出问题只有在更细粒度的 CPU 时间维度上才能发现节流计数在增长。3.3 对 Java 这类多线程运行时的额外影响Java 的垃圾回收器、线程池、异步框架都依赖 CPU 时间。发生节流时如果 GC 线程和业务线程被一起暂停可能导致一系列连锁反应GC 暂停时间变长、线程池任务堆积、健康检查响应变慢。Kubernetes 的存活探针一旦因为这种抖动连续失败Pod 还会被反复重启造成恶性循环。这也是很多 Java 服务在物理机上运行正常迁移到 Kubernetes 后反而“变慢”的原因之一。物理机没有 cgroup 配额约束容器环境里配置不当的 limits 成了新的隐藏瓶颈。3.4 limits 与资源配额、利润率的关系还有一种情况团队给所有 Pod 都设置了 limits并不是因为业务需要而是因为 Kubernetes 的ResourceQuota要求资源必须有上限。比如命名空间配置了 quotas而配额按 limits 计算。在这个前提下去掉 CPU limits 会导致 Pod 创建失败。这种配置需要调整思路ResourceQuota 是管理命名空间整体资源的手段可以限制所有 Pod 的 requests 总大小而 limits 是单个容器的资源上限。两者不是一回事。下面会专门讲生产环境怎么通过配额、LimitRange 来替代“给每个容器都写 limits”的做法。4. 什么场景该用、什么场景不该用 CPU limits社区争论“stop using CPU limits”多年最终的正确结论绝不是“永远不设置”而是“不要默认设置”。判断标准应该围绕业务特征展开这个服务对延迟是否敏感是否存在资源失控风险是否能接受 Pod 的平均性能被压平场景建议原因延迟敏感型在线服务API、网关、RPC优先不设 CPU limits只设置精确 requests避免节流造成的尾部延迟抖动离线批处理、数据流水线可以设置 limits甚至设得较小任务对绝对完成时间不敏感要防止占满节点测试/开发命名空间设置 limits 兜底测试代码可能有 bug需要隔离风险共享集群的多租户环境必须设置 limits 或配额防止单个应用拖垮整个节点无法快速扩容的在线服务先设置 limits 并持续监控后续评估调整极端情况下保护节点稳定性优先这不是一个简单的“设或不设”的选择而是一个“看得懂代价”之后的决定。设置 limits等于接受节流风险不设置 limits等于相信应用的资源使用是可控的。生产环境里更稳妥的做法是用监控和自动扩缩容来管理在线服务的 CPU 使用而不是把希望寄托在一个静态的 limits 上。5. 环境准备与验证思路下面做一个最小实验目标是让节流现象“肉眼可见”。你只需要一个能运行的 Kubernetes 集群minikube、kind 或者任何测试集群都可以。这里不限定具体版本和发行版因为实验依赖的是 Linux 内核和容器运行时通用的 cgroup 行为。需要准备的工具一个可用的 Kubernetes 集群且有权限创建 Deployment。本机安装kubectl并已配置好 kubeconfig。一个方便执行压测命令的容器镜像。推荐包含yes和bash的基础镜像比如 busybox 或你正在用的业务镜像。如果想查看 cgroup 文件需要能进入容器执行命令如果容器内看不到 cgroup 路径可以改用节点视角但需要相应权限。实验分为三个步骤创建一个设置了 CPU limits 的 Deployment并压测触发节流。在容器内或节点 cgroup 里观察配额文件和节流周期计数。创建一个只设置 requests、不设置 limits 的 Deployment对比同样压测下的表现。这个设计能让每个现象都有对应的配置和观测方法而不是停留在“理论上会节流”的层面。6. 最小实验观察 CPU 节流现象6.1 创建带 CPU limits 的 Deployment先创建一个带 limits 的 Deploymentrequests 设为 500mlimits 设为 1 核。文件内容如下# 文件路径deploy-with-limits.yaml apiVersion: apps/v1 kind: Deployment metadata: name: demo-with-limits labels: app: demo-with-limits spec: replicas: 1 selector: matchLabels: app: demo-with-limits template: metadata: labels: app: demo-with-limits spec: containers: - name: demo image: busybox:1.36 command: [sleep, 3600] resources: requests: cpu: 500m memory: 128Mi limits: cpu: 1 memory: 256Mi这个配置的含义是调度器按 500m 判断节点资源是否足够运行时 cgroup 配额按 1 核限制。创建后确认 Pod 正常运行kubectl apply -f deploy-with-limits.yaml kubectl get pods -l appdemo-with-limits -o wide6.2 压测触发节流进入容器启动多个yes /dev/null进程来制造 CPU 负载。yes命令会不断输出字符输出重定向到/dev/null后单进程可以打满一个逻辑核。kubectl exec -it deploy/demo-with-limits -- sh # 在容器内执行 yes /dev/null yes /dev/null yes /dev/null 启动三个进程后容器的需求大约为 3 核而 limits 只有 1 核。此时如果查看容器的 CPU 使用率会看到它被限制在大约 1 核附近而不是真正的 3 核。这就是节流发生的直观证据。6.3 观察 cgroup 配额和节流周期在容器内查看 cgroup 文件能够得到配额信息# cgroup v2 路径 cat /sys/fs/cgroup/cpu.max # 预期输出类似100000 100000 表示 1 核配额周期 100ms # 如果 limits 为 500m则输出类似50000 100000同一路径下还有cpu.stat可以看到节流相关的统计信息cat /sys/fs/cgroup/cpu.stat输出中nr_throttled表示容器被节流的次数throttled_usec表示累计被节流的时间。压测开始后多观察几次这两个值会不断增长。如果配置了 Prometheus也可以通过指标查询节流情况对应的指标名是rate(container_cpu_cfs_throttled_periods_total{namespacedefault, pod~demo-with-limits.*}[1m])这个指标统计的是容器在每个周期里实际发生节流的周期数是判断 limits 是否成为瓶颈最直接的依据。6.4 对比去掉 limits 后的表现创建一个只设置 requests、不设置 limits 的 Deployment# 文件路径deploy-requests-only.yaml apiVersion: apps/v1 kind: Deployment metadata: name: demo-requests-only labels: app: demo-requests-only spec: replicas: 1 selector: matchLabels: app: demo-requests-only template: metadata: labels: app: demo-requests-only spec: containers: - name: demo image: busybox:1.36 command: [sleep, 3600] resources: requests: cpu: 500m memory: 128Mi limits: memory: 256Mi同样进入容器执行三个yes /dev/null 。此时没有 CPU 配额容器可以充分使用节点上的空闲 CPU。如果你观察cpu.max会看到类似max 100000的输出表示没有上限cpu.stat里也不再出现持续的节流增长。这个对比实验并不复杂但它把“limits 会改变容器的 CPU 行为”这件事从理论变成了可观测的事实。你的业务镜像可能比 busybox 复杂得多但底层机制是完全一样的。7. 生产环境里如何正确管理 CPU 资源实验做完之后很多人的第一反应是把所有 Deployment 的 CPU limits 都删掉。这个动作本身没有错但没有配套措施就直接删等于把一个可能出问题的机制换成了另一个可能出问题的机制。生产环境需要的是系统性调整而不是一次性改动。7.1 先定好 requests用监控校准requests 是整篇文章里真正需要认真对待的值。它不应该照抄 limits也不应该随便填一个小数而应该来自过去一段时间实际运行数据的统计。如果使用 Kubernetes HPA可以根据业务指标自动调整副本数requests 偏大会导致资源浪费和调度失败requests 偏小节点过载时 CPU 争用会加剧。推荐的做法是选取业务高峰期的一周数据统计 CPU 使用率的中位数或 P95再乘以安全余量。不要直接把当前使用率的上限当作 requests这样在性能优化、流量突增时没有缓冲空间。7.2 用 ResourceQuota 和 LimitRange 兜底如果担心“不设置 limits 之后某个没写好代码的应用会打爆节点”可以不在 Pod 级别加 limits而在命名空间级别配置 ResourceQuota# 文件路径namespace-quota.yaml apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 20 requests.memory: 64Gi这个配额限制的是命名空间内所有 Pod 的 requests 总量。它不会限制单个容器的 CPU 峰值但能控制整体资源申请规模。再配合 LimitRange可以约束每个容器的默认 requests# 文件路径namespace-limitrange.yaml apiVersion: v1 kind: LimitRange metadata: name: default-requests namespace: dev spec: limits: - max: memory: 4Gi defaultRequest: cpu: 100m memory: 256Mi type: Container这种组合的好处是把“防止资源失控”的职责交给平台层把“性能与延迟”的职责留给业务层。平台层保证不会有太多请求挤进命名空间业务层则可以更灵活地管理单容器资源。7.3 真正需要保留 CPU limits 的少数情况延迟敏感型在线服务不设 CPU limits不代表所有工作负载都要这么做。批处理任务和数据流水线通常有固定预算设置 limits 可以把资源消耗限制在可控范围内避免任务之间相互干扰。测试环境更是如此测试代码可能存在死循环或内存泄漏limits 是安全兜底。在共享集群中如果多个团队共用一个节点池而节点规格固定、无法快速扩容CPU limits 仍然有它的价值。此时更像是一个运维取舍接受部分延迟波动换取整体稳定性。这种场景下limits 应该写得有依据比如根据压测结果确定合理的峰值而不是随手填一个2000m。7.4 批量调整的逐步升级策略生产环境批量去掉 limits 时不要一次性修改几百个 Deployment。更稳妥的是按服务重要性分组先选一个非核心、且有完善监控的服务做试点。观察对象包括CPU 使用率、P99 延迟、错误率、Pod 重启次数、节点 CPU 使用率。如果试点运行一周后服务延迟没有劣化节点资源没有失控再逐步扩大到其他服务。每次变更都用标准发布流程走代码评审、灰度发布、监控验证、回滚预案。如果某个服务在去掉 limits 后出现资源占用过高的问题第一反应不是立刻加回 limits而是分析它的资源使用曲线调整 requests 或 HPA 指标。8. 常见问题与排查思路社区和实际项目中围绕 CPU limits 的问题高度集中。下面整理几个最常见的场景帮助快速定位。问题现象可能原因排查方式解决方案CPU 使用率不高P99 延迟高limits 触发 CFS 节流查看container_cpu_cfs_throttled_periods_total是否增长调整或移除 CPU limits优先保证延迟Pod 内cat /sys/fs/cgroup/cpu.max看到大量配额cgroup v2 配额设置与 limits 一致计算 limits 对应周期值与 YAML 对照按需调整 limits或改用 requests-only去掉 limits 后单个 Pod 占满节点 CPU应用本身存在资源失控风险检查应用线程数、死循环、流量突增配置 HPA、调整 requests必要时在命名空间加配额Pod 反复重启但内存正常存活探针因节流连续失败查看事件和探针日志确认节流时间窗口增加探针超时同时评估 limits 是否合理想给 Pod 设置 limits 但创建失败ResourceQuota 限制查看命名空间 quota 和 LimitRange调整配额策略不要让业务配置被平台限制反向绑架节点 CPU 跑满但容器实际影响不明显多个容器竞争shares 权重在起作用查看节点指标和各 Pod CPU 时间调整 requests保证核心服务权重更高这里特别说明一下“去掉 limits 会不会导致节点被打满”。即使所有在线服务都去掉 CPU limits节点上仍然存在 CPU shares 竞争机制。当 CPU 资源不足时内核按各容器 requests 对应的权重分配时间片。这个机制比静态 limits 更贴近真实资源需求但前提是每个容器的 requests 设置得准确。如果 requests 全部默认填 500m而实际需要的只有 50m权重分配就会失真。9. 总结与后续学习方向这篇文章讨论的核心对象是 Kubernetes 中经常被误用的 CPU limits。它真正想传递的判断是limits 在 Kubernetes 里不是一个无脑的保护措施而是一个会改变容器运行时行为的硬性约束尤其是在你对延迟敏感、对性能稳定性有要求的服务里由 limits 引发的 CPU 节流会带来比资源超用更隐蔽的负面影响。正确的管理思路是先设置准确的 requests用监控校准再按业务场景决定是否保留 limits在线核心服务优先考虑去掉 CPU limits用 HPA 和命名空间配额代替硬编码上限批处理、测试环境、共享集群则仍然保留 limits 做安全兜底。如果这篇文章让你对 requests、limits、QoS、cgroup quota 之间的关系有了新的理解下一步可以继续研究几个更深入的方向cgroup v2 的cpu.max与带宽控制参数、Kubernetes 节点级的 CPU Manager 策略对 QoS 的影响、HPA 与 VPA 在 CPU 资源管理中的配合。把这些内容吃透遇到 CPU 相关的性能问题时你就会有比“改配置”更完整的判断能力。建议先拿一个非核心服务做实验把第三节和第六节里的观测方法跑一遍让节流数据说话再决定你的集群里到底该怎么处理 CPU limits。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门