request 和 limit 设错的代价:CPU throttling 与 OOMKilled 的分界 我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录request 和 limit 设错的代价CPU throttling 与 OOMKilled 的分界一、两个参数管的是完全不同的事二、CPU 和内存的超限行为完全不同三、CPU throttling最隐蔽的性能杀手它长什么样怎么看怎么改四、OOMKilled内存超限的代价判定逻辑request limit 的灰色区间QoS 三个等级五、request 怎么定六、带走物request 和 limit 设错的代价CPU throttling 与 OOMKilled 的分界一、两个参数管的是完全不同的事resources:requests:cpu:500mmemory:512Milimits:cpu:1000mmemory:1Girequestlimit作用对象调度器内核cgroup语义“我至少要这么多”“最多只能用这么多”超了会怎样不影响运行只影响能否调度CPU 被限流 / 内存被杀没设会怎样调度器按 0 算Pod 可能被塞满可以无限用危险关键认知request 决定 Pod 能调度到哪台机器limit 决定 Pod 运行时能被杀/被限流的边界。调度器看的是节点上已分配的 request 总和不是实际使用量。所以会出现节点 CPU 只用了 20%但新 Pod 就是调不上去——因为 request 已经被预留在 90% 了。二、CPU 和内存的超限行为完全不同这是最容易搞混的地方两者的处理机制完全不一样超过 request超过 limitCPU可以用抢空闲资源被限流throttling进程变慢但不会死内存可以用但节点压力大时优先被杀立刻 OOMKilled容器被杀重启CPU 是可压缩资源超了就是慢一点不会致命。内存是不可压缩资源超了只能杀进程没有别的选择。所以两者的调优思路是反的CPU limit可以设得比 request 高甚至有人建议不设让 Pod 用空闲 CPU内存 limit建议设成和 request 一样消除那个可以被杀的灰色区间。三、CPU throttling最隐蔽的性能杀手它长什么样现象通常是服务 P99 突然变高但 CPU 使用率看起来并不高。比如limits.cpu: 1000m1 核应用在某个瞬间需要 1.5 核。cgroup 的 CFS 调度会以100ms 为一个周期做配额控制周期内最多用 100ms 的 CPU 时间 用完之后进程被 throttle暂停直到下一个周期结果是应用在一个 100ms 周期里只能用前 100ms… 不对是只能用 100ms 中的 100m/1000m 10%不对。准确地说cpu1000m意思是每 100ms 周期里可以用 100ms CPU 时间即 1 核满载。cpu500m是每 100ms 里可以用 50ms。问题的关键在于配额是按 100ms 平均的但你的请求可能集中在几毫秒内一个请求需要 30ms 的 CPU 时间 如果这段时间内配额耗尽 → 进程被暂停剩下时间等下一个周期 → 这个请求的耗时从 30ms 变成 130ms所以你会看到平均 CPU 使用率只有 30%但 P99 高得离谱。平均使用率看不出问题因为被 throttle 的时间不算使用。怎么看# 容器内的 throttling 统计cat/sys/fs/cgroup/cpu.stat# 或者 cgroup v2cat/sys/fs/cgroup/cpu.stat|grep-Ethrottled_time|throttled_periods# 用 kubectl 看需要 metrics-server 部分实现支持kubectltoppodpod--containers更直接的指标Prometheus cAdvisor# throttled 时间占比超过 5% 就该警惕 rate(container_cpu_cfs_throttled_periods_total[5m]) / rate(container_cpu_cfs_periods_total[5m])这个比值是我判断CPU limit 是不是设小了的第一指标。如果它持续 5%说明应用在频繁被限流。怎么改三种选择各有代价方案做法代价调大 limitlimits.cpu: 2000m节点超卖率上升只设 request 不设 limit删掉limits.cpu应用可以吃完节点所有 CPU有噪声邻居风险优化应用减少单次请求的 CPU 消耗需要真的改代码我的倾向对延迟敏感的服务API 网关、在线接口不设 CPU limit只设 request。这样它既保证有最低配额又能在空闲时借用资源不会被 throttle。代价是它可能吃掉共享节点的空闲 CPU——所以这种做法的前提是节点上有合理的 request 预留而不是随便乱跑。四、OOMKilled内存超限的代价判定逻辑容器内存超过limits.memory→ 内核 OOM Killer 杀掉容器进程 → 容器退出码137→ kubelet 重启 →CrashLoopBackOff。kubectl describe podpod|grep-A4Last State# Last State: Terminated# Reason: OOMKilled# Exit Code: 137注意OOMKilled 看的是容器的 limit不是节点内存。节点内存还很充裕时容器照样会 OOM——只要它自己超了自己的 limit。request limit 的灰色区间这是最容易被忽略的风险requests:memory:512Milimits:memory:2Gi这个 Pod 的用量在 512Mi 到 2Gi 之间时它正在使用超出自己申请量的资源。当节点内存紧张时kubelet 会优先杀这类 Pod它们的 QoS 等级是Burstable。超出 request 的那部分内存是借来的随时可能被收走。QoS 三个等级K8s 根据 request/limit 的组合给 Pod 分三个等级决定节点资源不足时先杀谁QoS条件被杀优先级Guaranteed所有容器的request limit且都设了最低最后才被杀Burstable至少一个容器设了 request 或 limit但不满足上面中等BestEffort什么都没设最高第一个被杀kubectl get podpod-ojsonpath{.status.qosClass}对内存敏感、不能被随便杀的服务数据库、缓存、有状态服务建议设成request limit拿到 Guaranteed。# Guaranteed 示例resources:requests:cpu:1000mmemory:2Gilimits:cpu:1000mmemory:2Gi五、request 怎么定request 定小了Pod 会被塞满一个节点调度密度过高节点压力大时全都被杀定大了节点利用率低成本高。正确做法是用历史数据定不是拍脑袋# 过去 7 天 CPU 使用的 P95 quantile_over_time(0.95, container_memory_working_set_bytes{pod~api-.*}[7d] )一个实用的经验值request 过去 7~14 天用量的 P50 ~ P70 保证基本可用 limit 内存和 request 一致Guaranteed或 P99 × 1.2 CPU 可以不设或 P99 × 1.5另外记得配LimitRange和ResourceQuota防止有人忘了设# namespace 默认限制apiVersion:v1kind:LimitRangemetadata:name:default-limitsspec:limits:-type:ContainerdefaultRequest:{cpu:100m,memory:128Mi}default:{cpu:500m,memory:512Mi}六、带走物现象根因动作P99 高但 CPU 使用率低CPU throttling看cfs_throttled_periods占比调大 limit 或去掉 CPU limit容器退出码 137、OOMKilled内存超 limit调大 limit内存敏感服务设request limit节点很闲但 Pod Pendingrequest 预留满了看节点Allocated resources不是top节点内存紧张时总被杀QoS 是 Burstable/BestEffort设request limit拿 Guaranteed调度密度过高request 定太小按历史 P50~P70 重定一句话结论CPU 超 limit 会变慢内存超 limit 会死request 影响能不能调度上去limit 影响运行时会不会被杀——这两件事不要混在一起调。