ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes CPU limits陷阱:为何它会导致应用性能骤降?

Kubernetes CPU limits陷阱:为何它会导致应用性能骤降? CPU limits 是 Kubernetes 里被讨论最多、也最容易踩坑的参数之一。它本意是限制容器能使用的 CPU 上限防止某个应用把节点资源占满但在实际生产环境里这个“保护”机制经常变成应用性能突然下降、接口延迟飙高、服务被无辜重启的元凶。如果你正在管理 Kubernetes 集群或者负责应用容器化改造这篇文章想和你认真聊一下为什么很多有经验的团队会建议你“不要随便设置 CPU limits”以及如果真的需要限制应该怎么做、判断标准是什么、常见坑在哪里。先说核心结论CPU requests 一定要设置而且要认真评估。CPU limits 能不加就不加除非你有明确的、可量化的理由并且充分理解 CFS 配额和内核节流机制。很多人第一次看到这个观点会不理解Kubernetes 官方文档里不是说得清清楚楚吗requests 和 limits 是一对limits 用来限制上限防止某个容器把节点耗尽为什么你们这些老油条又说不要用这里面的关键不在“限制”本身而在于 CPU 资源不同于内存它的事件驱动和可压缩特性决定了 limits 的实现方式会带来非常隐蔽的性能问题。1. 先把 CPU requests 和 CPU limits 的真实关系讲明白1.1 两个参数不是同一个维度的东西Kubernetes 里 CPU 相关的两个常用字段一个是spec.containers.resources.requests.cpu另一个是spec.containers.resources.limits.cpu。很多刚接触 Kubernetes 的人会这样理解requests 是最低保障limits 是最高上限。容器至少能拿到多少最多只能用多少。听起来像是一个范围区间对吧实际不是。requests 的主要作用是调度。它告诉调度器我这个容器需要多少 CPU请给我找一台能满足这个条件的节点。调度完成后requests 还会用于节点资源分配的计算、HPA 扩容判断、以及 kubelet 对节点压力驱逐的判断。它更像是一个资源预留承诺。limits 的主要作用是约束。当容器实际使用 CPU 超过了 limits 规定的配额时内核会通过 CFSCompletely Fair Scheduler完全公平调度器带宽控制机制对容器进程进行节流也就是限制它继续使用 CPU。这里面最微妙的地方在于如果一个容器设置了 requests500m、limits1000m它实际运行中最多能使用的 CPU 不是 500m而是可以达到 1000m但能否真的跑到 1000m取决于节点上是否有多余的空闲 CPU。如果有空闲 CPU它可以借用来突破 requests 限制如果没有空闲 CPU它的实际可用 CPU 会向 requests 靠拢。它最多不会超过 limits。换句话说limits 不是“最多能使用多少 CPU”的免费提示而是一道强制刹车。当这个刹车触发时容器中所有线程都会被暂停直到下一个调度周期重新获得配额。这种暂停对用户态的进程来说表现为 CPU 时间被莫名抽走而应用本身并没有做任何错误操作。1.2 空闲 CPU 为什么不能随便借给被 limit 的容器一个节点上有 8 个 CPU 核心跑了 3 个容器CPU requests 一共只用了 3 核那么节点上还有 5 核空闲。如果其中一个容器设置了 limits1 核它即使想利用空闲的核心也无法越过 1 核的限制超过之后就会被节流。而节点上的空闲 CPU 明明足够其他容器也没有抢占资源但这个容器依然会被强制放缓。这就是矛盾所在你设置 limits 的目的是防止某个容器拖垮节点但实际发生的情况是节点资源充足没有任何容器受到威胁你的容器却因为 limits 被内核强行压制了性能。更难受的是CPU 节流不会直接体现在 Kubernetes 事件里不会出现在 Pod 的重启原因里也不会让容器彻底退出。它只会让应用的响应时间变长、吞吐量下降、线上业务出现周期性的“卡顿”。这种问题极其隐蔽排查起来也特别容易走弯路。2. 为什么内核的 CFS 配额机制遇到多线程应用更容易出事2.1 CFS 配额和 CFS 周期的概念Linux 内核的 CFS 带宽控制工作原理可以简化成一句话在每一个 period 周期内一个进程组最多只能运行 quota 指定的 CPU 时间。默认的 period 一般是 100ms。比如一个容器设置了 limits.cpu2000m那么内核给这个容器分配的 quota 大约是 200ms 的 CPU 时间每 100ms 周期重置一次。也就是说在这个 100ms 的时间窗口里这个容器最多只能占用 2 个 CPU 核心的完整计算时间。看起来没什么问题对吧关键坑在于这是整个容器内所有线程共享的配额不是每个线程单独一份。如果你的应用是单线程程序它在一个周期内最多能运行 100ms如果超过配额就被强制休眠等下个周期。如果你的应用有 4 个线程每个线程各自都想跑 100ms那就超过了总配额内核会让所有线程全部暂停直到新的周期开始。这种机制天然对多线程、高并发、突发型应用不友好。因为线程数量越多在同一周期内累计消耗的 CPU 时间就越容易快速触顶。Linux 内核为了保证配额不超支会在周期结束时统一“算总账”算出来超了就整体冻结直到下个周期。对一个 Java 应用来说JVM 线程数量动辄几十个、上百个对 Node.js 应用也有 event loop 线程和线程池。只要高并发一上来线程对 CPU 的需求呈突发性增长CFS 配额很快被打满容器直接被节流。这不是应用代码 bug而是资源层的流速被切断了。我在实际项目里见过这样的现象某个服务平时响应时间 20ms某天高峰期响应时间突然变成 800ms去掉 limits 后响应时间恢复原样。代码没有任何变更内存也没有问题全部根因就是容器内线程触碰到了配额上限。2.2 为什么 CPU 节流比内存 OOM 更隐蔽内存超限时内核会直接触发 OOM Killer或者容器因为超过内存 limit 被 kubelet 杀死现象是 Pod 重启、Exit Code 137。这种问题虽然也烦但至少很直观一看监控就能定位。CPU 节流不会杀掉容器。它在内核时间片级别把进程暂停然后恢复不会产生任何错误日志不会触发重启不一定有事件。你只会看到应用指标里的延迟、吞吐量、CPU 用量出现周期性抖动。如果你平时没观察过 CPU throttling 相关指标确实很难第一时间想到是 limits 在作怪。尤其在使用 Kubernetes 的metrics-server或 HPA 时HPA 扩容判断用的是 CPU usage 相对 requests 的百分比而不是相对 limits。如果你把一个 Pod 的 requests 设得很低比如 50m但 limits 设成 2000m服务在高峰期已经触顶 limits、被疯狂节流HPA 可能完全无感因为按 requests 计算出来的负载率已经超过了扩容阀值导致集群里有一堆“假健康、真卡顿”的 Pod。更讽刺的是有些团队为了服务稳定会把 limits 设置得比 requests 大很多比如 requests500m、limits2000m结果原本是想给服务留出 CPU 突发空间实际却因为在突发时撞上了 CFS 配额而被强制节流。你预留的突发空间并没有变成服务的缓冲能力反而成为另一个限制线。3. CPU limits 引发问题时的典型表现和排查思路3.1 现象一响应时间周期性上升但 CPU 利用率并不高一个常见场景是服务端到端延迟每隔 100ms 就会有一个明显的峰值这个周期约等于 CFS period。你的 Pod CPU 使用率可能只有 60%看起来离 limits 还有距离但实际上这个 60% 是整个周期内的平均值。在周期初始时所有线程一起猛跑很快就耗尽了配额剩余时间全部被挂起到了下个周期又重新开始。所以平均 CPU 使用率并没有很高但应用实际的“可用计算时间”已经被切成了碎片导致任务被频繁暂停。通过kubectl exec进容器查看/sys/fs/cgroup/cpu/cpu.stat不同内核版本路径略有区别可能是cpu.stat能看到nr_throttled被节流的次数和throttled_time被节流的总时长。如果throttled_time占比明显基本可以确认节流正在发生。比如你跑了 5 分钟throttled_time达到了 30 秒说明有 10% 的计算能力被白白切掉了。即使 CPU 使用率不高应用也会觉得“CPU 不够用”。3.2 现象二Pod 频繁重启但日志里没有明显异常有些服务因为丢进了 limits 设置线程调度被延迟可能导致健康检查超时、锁等待超时、连接池耗尽等问题然后被 kubelet 判定为不健康触发重启。日志里可能看不到具体报错只在 restart count 上看到在增长。这种时候你要怀疑的往往不是应用能不能正常工作而是它在特定资源模型下没法正常工作。我遇到过的一个真实案例是某消息消费者应用设置了 limits.cpu1000m但是单条消息处理时会出现并发解压和批量发送瞬时 CPU 需求很高结果消费者经常出现心跳超时被判定为离群实例触发自动重启和重新平衡。移除 limits 后稳定运行了几个星期都没有再出现重启。3.3 排查顺序先从这三个地方找直接证据如果你收到了业务反馈说“服务变慢但没有崩溃”先不要急着改代码或加副本按这个顺序查查 CPU 节流指标container_cpu_cfs_throttled_periods_total、container_cpu_cfs_throttled_seconds_total这是最直接的判断依据。如果 throttled periods 占运行周期的比例高说明 CPU limits 确实在限制进程运行。查 Pod 实际 CPU 使用是否接近 limits通过 Prometheus 的container_cpu_usage_seconds_total计算容器实际使用的 CPU 量和 limits 做对比。如果实际使用量经常接近或触顶 limits那没问题如果实际使用量离 limits 很远但节流指标很高说明应用线程出现了突发性的 CPU 竞争比如在很短时间内把所有配额耗尽。查节点的 CPU 分配压力和空闲资源用kubectl describe node查看节点的分配情况。如果当前节点的 CPU requests 总和远远低于节点总容量说明节点完全不紧张那节流就不是因为资源不够而是 limits 本身在起作用。如果你发现节点 CPU requests 已经超卖严重那说明集群整体资源规划有问题移除 limits 后需要更谨慎。4. 不设置 CPU limits会不会真的把节点打爆4.1 需要区分“资源竞争”和“资源黑洞”很多人拒绝移除 limits是有充分理由的他们见过某一次事故某个应用因为代码 bug 或者异常流量把节点 CPU 直接吃满导致同节点上的其他应用全部受到影响。这个担心是合理的。CPU 不同于内存。内存是硬隔离一个容器申请到的内存页不会被另一个容器占用CPU 是共享的如果不对容器做任何限制完全依靠 Linux CFS 的默认调度策略来分配 CPU 时间。在多种进程竞争 CPU 时CFS 会根据优先级和权重尽量均衡分配但如果你不设置任何 limits同时也不设置 requests那么所有容器在同一层级下争抢 CPU确实会出现某些容器饿死其他容器的情况。这里的关键是你真正需要的不是“限制上限”而是“保障最低”和“合理划分优先级”。4.2 只设置 requests 的情况下CPU 如何竞争如果你给每个容器都设置了合理的 requests但故意不设置 limits那么容器之间会怎样竞争节点会将总 CPU 时间分配给所有容器。如果所有容器的 CPU 使用都没有超过自己的 requests那么每个容器都能按 requests 得到资源相安无事。如果某个容器试图超过自己的 requests 去抢占额外 CPUCFS 的权重机制会根据容器的 CPU shares即 requests 值来分配剩余空闲 CPU 时间。requests 高的容器在争抢空闲 CPU 时有更大权重。具体来说如果一个节点上有两个容器容器 A requests1000m容器 B requests500m当两者都在满负荷运行时容器 A 能拿到的 CPU 大约是容器 B 的两倍而不是均分。这其实就是为容器输入了“优先级”的概念。对运维来说最担心的是某个容器因为绕过 limits 而彻底吃掉节点全部 CPU。这个情况在只设置 requests 时基本不会发生因为 CFS 会在运行时按 requests 权重来分配时间。只有当你既没设 requests 也没设 limits 时所有容器才处于同一默认权重下容易出现某些异常容器在突发高 CPU 时抢占过多资源。所以必须设置 requests这才是资源隔离和保障的基础。4.3 低规格节点要不要额外保护用 Burstable 和 Guaranteed 的 Qos 来理解没有设置 limits 的 Pod 会被归入 Burstable 类当 requests 和 limits 不一致且全部设置了时也是 Burstable。如果你为 Pod 设置了 requests 和 limits 且两个值相等Pod 属于 Guaranteed 类。Guaranteed Pod 在节点资源紧张时被驱逐的优先级最低。如果集群节点资源不紧张Burstable 类 Pod 可以通过突发占用来提高响应速度如果节点资源紧张kubelet 会先驱逐 BestEffort没有任何 requests/limits的 Pod再考虑 Burstable。这个机制比硬性的 limits 更符合生产需求。换个角度理解limits 是把“最多能用多少”写进了内核而 CFS 权重调度是把“该给你的保障份额”以权重方式写进内核。前者更像一刀切的刹车后者更像灵活分配的比例。生产环境里我们更希望保障底线同时允许服务在有富余资源时把速度提上去。5. 如果一定要设置 CPU limits什么时候、怎么设、设多少5.1 什么场景下设置 CPU limits 是合理的确实有一些场景下设置 CPU limits 比不设更好。我总结了四个我会主动配置 limits 的场景场景一应用内部依赖了静态线程池或工作线程。比如旧版 Java 应用使用Executors.newFixedThreadPool(N)线程数量固定如果容器外没有线程数上限约束容器内线程过多时会导致线程阻塞、上下文切换开销飙升。这类应用设置 limits 可以避免线程池被突然打满时 CPU 被闲置消耗掉虽然这不是 Kubernetes 层面能根治的问题但 limits 可以起到兜底作用。场景二调度器需要计算“最坏情况下”的资源占用。有些团队在集群做容量规划时需要确保所有 Pod 的 limits 总和不超过节点容量否则担心极端情况下触发内核 OOM 或者节点不可用。如果你确实经常遇到节点资源被超卖到极限那么给关键业务设置严格的 limits 是主动行为不是在背地里限制性能而是在容量模型下有意为之。场景三多租户场景或对外提供资源配额。如果你的集群是给多个业务部门共用每个部门申领资源时需要一个上限承诺limits 就是承诺的载体。没有 limits 的话某个部门的应用如果突增会侵占其他部门应用的资源虽然后续可以通过 Quota 和 LimitRange 来限制但在请求时就带上 limits 更直观。场景四某些有状态中间件如 Java 富客户端、Cassandra、Elasticsearch 等。这类组件对线程数和CPU依赖特别强如果节点上 CPU 波动过大它们反而容易出问题。给它们一个相对稳定的 CPU 上限比让它们随时抢占更高因为抢占来的多 CPU 并不会稳定提升这些组件的性能反而会导致 GC 线程抖动、JVM 频繁暂停等问题。5.2 设置 limits 时最重要的一个参数不是配额值而是 period如果确定要设置 CPU limits请留意内核 CFS period 参数。Kubernetes 没有直接暴露 period 的配置字段但可以通过 kubelet 启动参数--cpu-cfs-quota-period调整一般默认是 100ms。如果你的应用是延迟敏感型、需要极低延迟但突发计算量不大可以把 period 调小例如 20ms 或 10ms。这样配额更频繁地重置线程更快获得新的 CPU 时间节流带来的“停顿感”会减轻。但调小 period 会增加内核调度开销要确认节点上容器数量是否很多。如果调大 period例如 500ms 或 1000ms在周期内能积累更多 CPU 时间对批处理类应用友好一些但延迟会显著增加。生产环境一般不建议轻易改 period因为它是节点级参数会影响所有容器。改之前要做充足的对比测试监控 CPU 节流率和响应时间变化。5.3 建议的设置原则把 limits 当作最后一道防线不要把它当默认配置我更推荐的原则是先设 requests不设 limits运行观察一段时间。如果监控稳定就不加 limits。如果确实遇到了资源抢占或容量规划问题再加 limits但要把值设在合理范围内并确保 limits - requests 的差值不能是“拍脑袋”填出来的。一个常见的比较稳妥的限制方式是把 limits 设为 requests 的 1.5 到 2 倍。比如 requests1 核limits2000m。这样应用能在短时间突发借助额外 CPU而不会被硬性掐死。但要注意这种配置下如果突发持续时间较长最终还是会节流所以要靠 HPA 或集群自动扩缩容来处理长期负载而不是依赖 limit 内的突发空间。limits 和 requests 一致时Pod 属于 Guaranteed 类稳定性最好但突发能力为零。所以如果你想要突发能力就接受 Pod 为 Burstable 类如果你想要绝对的调度确定性和稳定的资源占用就设成一致。5.4 CPU 压测是唯一可靠的判断方式我不太建议完全照着文档或者别人的经验来定 requests 和 limits因为每个应用的 CPU 代码路径、线程模型、垃圾回收策略差异非常大。可靠的判断方式很简单压测。找一个和生产环境接近的 Pod 配置压测三个版本requests500m无 limitsrequests500mlimits1000mrequests500mlimits2000m。压测指标重点看四个P99 延迟、吞吐量、CPU 节流率和线程阻塞情况。如果版本 2 和版本 1 相比P99 延迟明显抬高但吞吐量和 CPU 使用率又不高说明节流对性能损伤很大这个 limits 就不合适。如果版本 3 和版本 1 差别不大且你的容量规划允许可以保留一个相对宽的 limits 以便运维兜底。注意压测时要让负载有突发性不要用恒定低负载去压因为恒定低负载通常不会触顶配额。要模拟真实业务里的尖峰比如 10 秒内并发翻倍、单条消息计算量特别大等。6. 把 CPU limits 从现有环境里逐步移除的实操路径6.1 第一步用指标确认哪些 Pod 正在被节流不要直接全部删除 limits先收集证据。使用 Prometheus 查询 CPU 节流相关指标sum by (namespace, pod) ( rate(container_cpu_cfs_throttled_seconds_total[5m]) ) / namespace或者查看哪些 Pod 的节流时间占运行时间比例较高increase(container_cpu_cfs_throttled_periods_total[5m]) 0通过 PromQL 你可以找出在某段时间内发生了节流的 Pod 列表。再结合container_cpu_usage_seconds_total看实际使用率和 limits 的接近程度。如果实际使用率远低于 limits但节流次数很高说明应用的 CPU 请求是突发型的设置 limits 对它来说是纯粹的伤害。6.2 第二步按风险分组先从小流量服务开始移除 limits 这件事最好按风险从低到高逐步推进。优先级先动的是已经有独立监控、且节点容量充足的服务无状态服务扩容快能快速拉起新副本本来就依赖 HPA 自动扩容、且资源规划比较宽松的服务。暂时不要动的共享节点上还跑着其他服务、且没有用 requests 做资源隔离的服务没有合理设置 requests 的服务如果连 requests 都没有移除 limits 后出事的概率很大集群节点 CPU 超卖严重、且没有一个配套的资源评估和排障流程的情况。操作时建议通过 deployment yaml 移除 limits 字段把resources.limits整个删掉保留 requests。不要只把 limits 的值改大因为一旦填了 limits它仍然会成为节流阈值。6.3 第三步把自己的监控视图建好设置快速回滚方案没有 limits 之后最重要的监控不是 CPU 使用率而是节点的 CPU 压力。看这几个指标当前节点的 CPU requests 总和是否超过了节点总 CPU节点的 load average 是否长期高于核心数的 1.5 倍是否存在某几个 Pod 持续把 CPU 使用率拉到 70% 以上同时其他 Pod 响应变慢。如果发现移除 limits 后某个服务频繁抢占空闲 CPU导致同节点其他服务性能下降一般不是“移除 limits”这个动作的问题而是你没有给其他服务设置合理的 requests。此时不要急着把 limits 加回去先调整同类服务的 requests把资源分配粒度理顺。建议给每个服务配置一个资源变更的快速回滚方案比如把 limits 的配置单独放在一条注释或环境变量里或者用 Helm values 管理避免在出现问题时临时手改 yaml 导致配置漂移。6.4 第四步把节点容量规划从“CPU limits 总和”改成“CPU requests 总和 突发余量”传统做法里很多团队在估算节点容量时会看节点上所有 Pod 的 limits 总和不能超过节点 CPU 总量。这种做法给节点塞满了上限导致资源利用率极低。移除 limits 后容量规划应该换成另一种思路节点的 CPU requests 总和控制在节点 CPU 总量的 60% 到 80% 之间预留 20% 到 40% 的突发放量空间节点可以接受某些 Pod 在某些时刻借用空闲 CPU但如果长期超过这个水位就该扩容或缩容。这样做以后节点不需要再为每个 Pod 预留最高上限的空间资源利用率能提升不少。很多团队说 Kubernetes 节省了机器成本其实就是通过这种“有效的超卖”实现的。前提是把 requests 做准否则超卖会变成吃空。7. 关于 Linux 内核版本、Runtime 和云厂商托管集群的额外提醒7.1 不是所有内核和 CRI 版本的 CPU 限制行为都完全一样虽然 CFS 配额机制是 Linux 内核长期以来使用的通用机制但不同版本对带宽控制的具体实现、默认参数、统计口径可能有细微差别。例如较新的内核版本在 cgroup v2 下cpu.max和cpu.stat的路径就与 cgroup v1 不同。如果你用的是云厂商的托管 Kubernetes 集群例如托管版、Serverless 容器服务等kubelet 参数和 cgroup 驱动可能由云厂商统一管理。你在节点层面能调整的东西很有限但 Pod 层面的 limits 行为仍然成立。在使用容器运行时不指明 cgroup driver 的情况下要特别留意 cgroup v1 和 v2 对 CPU 节流行为的差异尤其是在 Pod 内读取cpu.stat时路径可能不同。如果你在自建集群上操作建议先确认内核版本和 cgroup 版本。如果用的是 cgroup v2CPU 限制相关的位置在/sys/fs/cgroup/cpu.max该文件里可以看到“quota period”两值节流统计在/sys/fs/cgroup/cpu.stat里面的nr_throttled和throttled_usec分别对应被节流的次数和时长。cgroup v1 的默认行为是采用 cpu.cfs_period_us 和 cpu.cfs_quota_us。7.2 尽量用 Namespace 和 ResourceQuota 控制总资源而不是靠 Pod limits如果你担心的不是单个 Pod而是某个命名空间下所有应用的总资源消耗不要试图通过给每个 Pod 设置 limits 来限制总使用量。Namespace 级别的正确做法是声明ResourceQuota对象。例如创建一个限制命名空间总 CPU requests 不超过 8 核的 ResourceQuotaapiVersion: v1 kind: ResourceQuota metadata: name: cpu-quota namespace: production spec: hard: requests.cpu: 8 limits.cpu: 12这样即使某些 Pod 不设置 limits命名空间整体的资源消耗也会在配额管理范围内不会无限扩张。ResourceQuota 常常和 LimitRange 配合使用LimitRange 可以给默认的 requests 和 limits 赋值但要注意这同样会引入 limits 的节流风险所以默认值要设计得合理。7.3 不要让 limits 成为唯一防线也要看 CPU manager 策略Kubernetes 还有CPUManager和TopologyManager这类静态分配能力。静态 CPU 管理策略可以让 Guaranteed 类 Pod 独占部分 CPU 核心避免和其他容器共享核心。如果你的服务对延迟要求很高而且是 Guaranteed 类可以开启静态 CPU manager 策略。这时设置 limits 反而有意义因为它是让 Pod 获得独占核心的条件之一Pod 必须是 Guaranteed QoS也就是 requests 和 limits 相等。所以如果你的应用足够关键、节点核心数足够多可以考虑用“静态独占核心”的方式替代普通 CFS 共享调度。这样性能和隔离性都要比简单的 limits 好很多但也意味着资源利用率会降低不适合大规模普适使用。8. 如果团队坚持要保留 CPU limits给几个入门的实用技巧8.1 用 Burstable 短突发而不是长时间满负载有些团队出于安全考虑最终决定保留 limits。如果确定要保留我建议把服务的请求模型和 limits 值对应起来对于突发型流量服务比如短期的脉冲、秒杀、编排任务可以把 limits 设置成 requests 的 2 到 3 倍留给突发空间对于长时间消耗型任务比如批量计算、数据处理limits 和 requests 保持一致避免突发占用来回抖动把负载压在一个相对稳定的水位对于延迟敏感型微服务能用静态 CPU 独占最好否则尽量不设 limits靠 requests 和调度策略保障。8.2 关注 throttled 指标之后配合调整应用线程池如果 CPU 节流无法避免地存在那么从应用侧下手也是一个可行思路。比如 Java 应用在容器里会自动识别 CPU 核数这个核数通常取自 cgroup 的 quota 值而不是节点核心数。如果你在配置里手动指定了-XX:ActiveProcessorCountN可以手动设置为比 quota 略低的值避免 JVM 创建过多线程。但要注意ActiveProcessorCount设小之后JVM 线程池、GC 线程、ForkJoinPool 的并行度都会变小需要压测验证。如果设置不对P99 延迟和吞吐量都可能下降。这种手段解决的是“线程数过多导致配额快速耗尽”的问题而 limits 带来的节流现象只是被缓解了而不是根除。8.3 用 HPA 配合而不是用超大 limits 硬抗如果你发现业务经常达到 limits 上限说明这个服务容量已经不足正确的做法不是调大 limits而是配置 HPA 让它扩容。HPA 根据平均 CPU 使用率进行扩容这个 CPU 使用率按 requests 计算因此只要 requests 设置准确HPA 会在负载升高时自动创建新副本。设置 HPA 时建议targetCPUUtilizationPercentage控制在 60% 到 80% 之间。太低了会导致扩容频繁太高了可能扩容不够及时。如果 HPA 扩容之后每个 Pod 的 CPU 使用率仍然接近或超过 limits说明新增副本的生产速度仍不够需要调整单副本的并发能力或业务处理逻辑而不是继续堆资源。9. 最后留几个我自己排查时会优先看的点如果你正在被 CPU 性能问题折磨又刚好接触过 Kubernetes先别急着调代码看看这几个地方有没有给 Pod 设置 CPU limits如果设置了立刻查一下container_cpu_cfs_throttled_seconds_total。十次里有八次问题就出在这里。requests 设置得是否合理有些团队为了提升节点部署密度把 requests 压得特别低例如 10m、50m这会导致调度器把你的 Pod 塞到特别繁忙的节点上。之后即使你给了 Pod 很大的 limits也没有节点能提供这么多空闲 CPU。这类问题移除 limits 也解决不了要先调整 requests。容器的 CPU 使用率为什么在整百毫秒附近波动如果是几乎可以断定是 CFS quota 耗尽导致的周期性节流因为默认 period 就是 100ms。HPA 扩容判断用的是 usage/requests不是 usage/limits。如果你设置的 limits 比 requests 大得多可能出现负载很高但 HPA 不扩容的情况因为按 requests 算出来的使用率已经触顶扩容却依然不够。此时需要同时观察 CPU usage 和 requests 的比例以及 limits 是否成为瓶颈。节点真的空闲吗不要只看节点 CPU 使用率很多节点 CPU 使用率不高但 CPU requests 已经分配满了。这种情况下新调度到该节点的 Pod 虽然能运行但会被 CFS 和其他 Pod 一起争抢时间片表现和 limits 导致的节流很相似。通过kubectl describe node能看到 allocatable CPU 和 requests 的分配情况。说到底CPU limits 本身不是一个邪恶的配置项它是一个有明确语义的内核资源控制机制。问题在于很多团队把它当成默认参数或者当成防止资源黑洞的唯一手段结果是应用在资源充足时被白白限速资源紧张时又没有真正解决问题。如果你现在正管理着一个稳定的 Kubernetes 集群我的核心建议是把 requests 当作资源承诺来做把 limits 当作特殊场景下的安全网来用不要把安全网当作日常约束。让 CPU 调度回归到 CFS 的权重公平分配比简单套一个配额限制要可靠得多。
返回列表