Kubernetes 资源 requests / limits 实战:不会把生产搞挂的设法
我见过生产里最常见的 K8s 事故:CPU limit 设太紧,应用被节流到地板。第二常见:内存 limit 忘设,一个 pod 把整个 node 吃了。这是不进这两种统计的实战指南。
requests vs limits
requests 是调度器给你预留的。是地板 —— 保证有这么多。
limits 是天花板 —— 超过内核会节流或杀掉你。
resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi
两个常见误解:
- "requests 在运行时锁住容量。" 不对,requests 是调度提示。运行时 pod 可以用到上限的任何可用量。
- "没 limit 就是无限。" 技术上是,但你把 node 暴露在风险里。一个泄漏 pod 能 OOM 整个 node,把其他 pod 全带下来。
QoS 类 —— 它们真正控制什么
K8s 按 requests / limits 给每个 pod 分配 QoS 类:
| 类 | 条件 | 驱逐顺序 |
|---|---|---|
Guaranteed | requests == limits (所有容器) | 压力下最后被驱逐 |
Burstable | 至少一个容器 requests < limits | 在 Guaranteed 之前驱逐 |
BestEffort | 没设 requests 或 limits | 最先被驱逐 |
node 内存吃紧时,kubelet 先驱 BestEffort,再 Burstable,最后 Guaranteed。你在乎的工作负载要保持不挂,做成 Guaranteed。
实操中多数生产负载该是 Burstable + 合理上限 —— limits ~2× requests 是好起点。
CPU 是软的;内存是残酷的
这是本文最重要的一句话。
CPU:触到上限,内核节流(CFS 带宽节流)。代码慢但不死,烦但能撑。
内存:触到上限,内核杀你(OOMKilled)。进程没了。容器重启。连接掉。
含义:激进 CPU limit 对尾延迟不好(节流会引起神秘 p99 尖峰)但很少灾难。激进内存 limit 是灾难 —— 稍微低估应用内存就能把服务搞下来。
CPU 实际怎么设
第 1 步:先不设 CPU limit,设合理 request(笔记本 benchmark 看典型负载的值起步)。
第 2 步:生产观察稳态 CPU 几天。
第 3 步:request 设到接近 p50 用量。limit 设到 p99 用量的 2-3 倍,或者跳过 limit 如果信任 node 总体容量。
为什么跳过 CPU limit?因为触到的主要代价是节流,悄悄拖慢延迟。许多团队(包括字节,他们的故事广为流传)对无状态服务故意不设 CPU limit。
设的理由:吵闹邻居场景的可预测性,以及强制自动伸缩在已知阈值触发。
内存实际怎么设
内存不饶人,要保守。
第 1 步:profile 你的应用。JVM、Go、Node 内存行为差很多。跑代表性负载测一下。
第 2 步:request 设到真实峰值(不是均值 —— 峰值)。内存在 GC、突发负载、长尾请求时会尖。
第 3 步:limit 设到 request 之上约 30-50%。更紧风险大,更松浪费容量但安全。
第 4 步:监控 OOMKilled 事件。看到就是 limit 太低。不要只是上调 —— 找出为什么应用短暂需要那么多内存。
JVM 应用还要设 -XX:MaxRAMPercentage=75(或类似),让 JVM 堆守在 cgroup 限制下。容器里 JVM 默认堆大小经常是错的。
失败的模式
反模式:跨服务复制粘贴 limits。 cpu: 1, memory: 1Gi 模板被到处 cargo cult。某些服务超配 10×,某些挨饿。
反模式:内存 limit "以防万一" 设很高。 一开始好用,但破坏调度。每个 pod 申请 4Gi 实际用 200Mi 时,会浪费 node 容量,集群看着"满"但其实没满。
反模式:设 limit 不监控。 没可观测性的 limit 是盲触发器。要 CPU 节流和接近-OOM 事件指标才知道 limit 紧还是松。
VPA、HPA、自动伸缩层
VPA 按观测用量调整 requests/limits。理论很好。实操有粗糙边:重启 pod 才能改资源,扰动大。多数团队手动用 VPA 推荐,不开自动应用。
HPA 按 CPU/内存利用率扩缩副本数。配合合理 requests:requests 太高,HPA 永不触发;太低,触发太频繁。
稳健模式:设真实 requests,HPA 扩副本,观察稳态,按实际数据每季度调一次 requests。避免每次发布"优化"的冲动。
实战清单
任何新 Deployment manifest 合并前:
- ✅ requests 和 limits 都设(或显式跳 limit 且写明理由)。
- ✅ 内存 limit 至少高于真实峰值 30%。
- ✅ CPU request 接近典型用量,不是离谱高估。
- ✅ JVM 应用设了
-XX:MaxRAMPercentage。 - ✅ pod 有
livenessProbe卡死时被重启。 - ✅ pod 有
readinessProbe重启前停流量。 - ✅ 有 Grafana 面板显示该服务的 CPU 节流与内存。
任何一项打叉,你在赌运气。
多租户考虑
多团队共享集群时,requests/limits 不再可选,是契约。namespace 配额(ResourceQuota)、默认 LimitRange、准入策略(Kyverno、OPA)成为执行层。
团队的错误:吵闹邻居出事后才上这些。更好的是第一天就设 LimitRange 默认 —— 哪怕默认值朴素,也能挡住最差情况。
TL;DR
- requests = 地板(调度),limits = 天花板(运行时)。
- CPU 节流烦,内存 OOM 灾难。内存上要保守。
- 多数生产服务该是 Burstable +
limits~2×requests。 - 许多团队对无状态服务跳过 CPU limit —— 可辩护。
- 内存 limit 设在真实峰值之上 30-50%;更紧风险 OOMKill。
- 监控节流和接近-OOM 事件。没可观测性的 limit 是盲触发器。
- JVM 应用还要配堆百分比,让 JVM 尊重 cgroup。
调任何东西之前花一下午读自己的真实指标。对的 limit 是基于你数据的,不是别人博客的(包括这篇)。
相关阅读
TypeScript 类型体操:什么时候值,什么时候在炫技
务实的 TypeScript 高级类型指南 — mapped types、conditional types、template literal types 真正能给你什么,什么时候用,什么时候应该退回到朴素代码。
Vue 3 vs React 2026:下个项目的诚实对比
2026 年 Vue 3 与 React 的诚实对比 — Composition API vs Hooks、性能、生态、TypeScript 表现,以及真正决定选型的标准。
Rust 所有权深入:借用、生命周期,以及那个真正帮你的编译器
一份务实的 Rust 所有权指南 — move 与 borrow、共享借用 vs 独占借用、生命周期省略、让 borrow checker 错误消失的模式,以及让 Rust 突然通透的心智模型。