前言
在 Kubernetes (K8s) 集群中,服务 YAML 文件里的 resources 配置块往往是被开发者误解最深的区域。面对 requests 和 limits,许多人的做法是“凭感觉填”或者“抄隔壁项目的配置”。
但实际上,这短短几行 YAML 决定了你的微服务在面对突发流量时是安然度过、被无情限流(Throttling),还是直接崩溃(OOMKilled);也决定了你的 K8s 集群是能够极致降本增效,还是沦为资源浪费的黑洞。
要彻底搞懂参数该怎么设,我们必须先认清 K8s 调度的底层逻辑。
在k8s集群中,我们编写服务yaml的时候有个关键的resources的参数需要填写,但是具体应该怎样设置呢,以下就是我在实际使用过程中的一点思考:
resources:
requests: # 描述服务启动时所需的资源是多少
memory: "1Gi"
cpu: "50m"
limits: # 描述服务最大能使用多少资源
memory: "3584Mi"
cpu: "2"
核心基石:K8s 的“瞎眼会计”与 Linux 的“硬核保安”
在决定给服务分配多少资源前,必须理解 K8s 调度和清理资源的两个核心机制:
调度机制:只看账本,不看现实的“瞎眼会计”
- K8s 调度器(kube-scheduler)在分配 Pod 到具体 Node 节点时,唯一参考的指标是
requests(账面需求)。 - 它绝对不会去查看某台宿主机当前的物理真实 CPU 和内存利用率是 10% 还是 100%。只要
节点总容量 - 已调度 Pod 的 Requests 总和 > 新 Pod 的 Request,调度就会成功。 - 结论:
requests决定了集群的装箱率(调度密度)。设置得越高,节点能调度的 Pod 越少,资源浪费越严重。
清理策略:可压缩与不可压缩资源的冰火两重天
当节点物理资源真正不够用时,Linux 内核与 K8s 节点代理(kubelet)会介入,对 CPU 和内存采取完全不同的镇压手段:
- CPU(可压缩资源):集体变慢,绝不杀人。
- 如果物理 CPU 跑到 100%,系统会根据各个 Pod 的
CPU Request权重(底层转换为cpu.shares)重新分配时间片。所有服务都会变慢(RT 飙升),但绝对不会有服务因为 CPU 不够被强杀。
- 如果物理 CPU 跑到 100%,系统会根据各个 Pod 的
- Memory(不可压缩资源):无情击杀,精准或盲杀。
- 精准击杀(OOMKilled): 某个 Pod 的实际内存达到了它自己的
Memory Limit,内核的 OOM Killer 会直接杀掉该容器并重启。 - 节点驱逐/盲杀(Eviction/Node OOM): 如果大量 Pod 没达到自己的 Limit,但宿主机的物理内存总和被吃光了。Kubelet 会拉响警报,强制驱逐那些内存使用量超过自己 Request 的 Pod,以保护节点不死。
- 精准击杀(OOMKilled): 某个 Pod 的实际内存达到了它自己的
逐个击破:四大参数的设置法则
基于上述底层逻辑,我们来推演这四个参数到底该怎么填。
CPU Request:必须设置,且尽量贴近真实低谷
- 是否应该设置? 必须设置。 这是 K8s 调度的依据,也是在宿主机 CPU 满载时,你的服务能分到多少最低算力保障的“股权证明”。
- 设置多少? 建议通过 Prometheus 观察服务在日常平稳期的真实 CPU 消耗,设置一个偏低的值(例如 50m 或 100m)。这能极大释放账面调度空间。
- 如果你在 Kubernetes 中完全不设置 CPU Request,K8s 会根据你是否设置了 CPU Limit 触发两种截然不同的底层机制。
但结论可以先放在前面:这是一种极其危险的做法,会导致你的服务在集群高负载时“离奇假死”。
我们来拆解这两种情况的底层逻辑:
情况一:既不设 CPU Request,也不设 CPU Limit(最惨的“底层平民”)
当你两个都不写时,K8s 会认为这个 Pod 对 CPU 的需求是 0。
- 调度层面(瞎眼分配): 既然你需要 0 个 CPU,K8s 调度器(kube-scheduler)会认为随便哪个节点都能塞下你。它会把你当成没有任何计算需求的“空气”,闭着眼睛把你调度到可能 CPU 已经负载 100% 的节点上。
- 运行层面(完全丧失竞争权): 当你的 Pod 运行在宿主机上时,Linux 内核会给它分配一个全场最低的 CPU 权重(在 cgroups 底层,
cpu.shares默认只有 2,而 1 个核心的权重是 1024)。 - 致命后果 —— “饥饿假死”(Starvation):
- 平时(节点空闲): 你的程序跑得飞快,因为没人跟你抢,你可以占用节点 100% 的 CPU。
- 战时(节点满载): 当同节点的其他服务(那些设置了 Request 的“特权阶级”)也开始狂用 CPU 时,Linux 内核会严格按照权重分配时间片。你的服务因为权重是底层的底,几乎分不到任何 CPU 运算时间。
- 表现: 你的 Pod 依然显示
Running,没有报错,没有 OOM,甚至都没被重启。但是你的接口请求会全部卡住、疯狂 Timeout,整个服务处于一种“脑死亡”状态,直到宿主机的 CPU 空闲下来它才会突然“复活”。
情况二:没设 CPU Request,但设了 CPU Limit(触发 K8s 潜规则)
如果你只写了 Limit: 2,而把 Request 留空,K8s 为了防止逻辑冲突,会强制执行一条底层潜规则:自动将你的 CPU Request 填充为等于 CPU Limit 的值。
- 后果: K8s 会默默帮你把配置改成
Request: 2, Limit: 2。 - 这有什么坏处? 我们前面讨论过,CPU Request 设得太高(比如 2核),会严重吃掉账面调度空间。你可能本来只是想要个上限,结果无意中把 Request 也拉满了,导致大量算力在调度账面上被白白锁定,集群资源利用率暴跌。
架构师总结:为什么“10m”的低保也比“不设”强?
绝对不能让 CPU Request 留空。
哪怕你只给它设置一个极小的值,比如 requests.cpu: 10m 或者 50m,在底层意义上也是天壤之别:
- 不设置,权重是 2(随时被踩在脚底饿死)。
- 设置 50m,权重就是 51。虽然不多,但相当于你在这个节点上拿到了合法的“股权”和“最低生活保障”。
当宿主机 CPU 被打爆到 100% 时,这 50m 的 Request 能保证内核依然会硬性切出一小块时间片给你的服务。你的服务处理请求会变慢(比如从 50ms 变成 1s),但绝对不会假死停摆,依然能缓慢但坚定地响应请求。
CPU Limit:建议不设置(Unset)
- 是否需要设置? 现代云原生架构强烈建议:不设置(删除该字段)。
- 为什么? K8s 的 CPU Limit 依赖 Linux CFS Quota 机制。如果设置了 Limit(例如 2.0),即使宿主机当时有 10 个空闲的 CPU 核心,只要你的服务在 100 毫秒内用完了属于自己的配额,也会被内核强行暂停(Throttling)。这会导致极严重的 P99 延迟毛刺。
- 结论: 拔掉 CPU 限速器,让应用在突发计算时瞬间借用节点空闲算力秒级处理完毕,是降低延迟的最佳实践。
Memory Request:必须设置,生死攸关
- 是否应该设置? 必须设置。 K8s 调度时用来占坑,也是宿主机内存不足时,判定你是否属于“超用资源”并决定驱逐优先级的核心标准。
- 不设置的话他也是会任意调度到只要有资源的节点,有可能调度上去服务启动不起来。
Memory Limit:必须设置,且策略决定命运
- 是否应该设置? 必须设置。 内存不像 CPU,如果没有上限,一行有 Bug 的代码造成的内存泄漏,就能把整台宿主机拖死。
- 是否要与 Request 相等? 这里分化出了云原生架构中的两大流派:
两大流派:追求极致稳定 vs 压榨集群资源
在实际生产中,没有绝对的对错,只有业务场景的取舍。请根据你的服务重要等级对号入座:
流派 A:核心保命流(Maximum Stability)
适用场景: 核心交易链路、网关、有状态服务(数据库、中间件)、不支持重试的复杂业务。
配置公式: Memory Request == Memory Limit,CPU Limit 不设置。
- 原理解析: 当内存的 Request 等于 Limit 时,K8s 会赋予该 Pod 最高的服务质量等级(
Guaranteed)。这相当于拿到了免死金牌。 - 优势: 哪怕同节点的其他服务疯狂吃内存导致宿主机 OOM,K8s 也绝对不会杀你的服务。你的内存边界是极其清晰和确定的。
- 代价: 会产生“稳定性溢价”。例如你设置了 2G,平时只用 1G,剩下的 1G 在 K8s 调度账面上被永远锁死,造成物理内存的“账面浪费”。
流派 B:极限压榨流(Extreme Cost Squeezing)
适用场景: 无状态 Web 服务、异步计算 Worker、离线跑批任务、可随时安全重启的边缘服务。
配置公式: Memory Request < Memory Limit(例如 Req=500M, Lim=2G),CPU Request 极低,CPU Limit 不设置。
- 原理解析: 这属于
Burstable(突发性能)级别。账面上只占 500M 调度空间,让节点能塞进几倍数量的 Pod,极大提高装箱率。同时给予 2G 的 Limit,允许服务在突发流量时“借用”节点物理内存。 - 优势: 极致的降本增效。用最少的机器跑最多的微服务。
- 代价(风险): 节点级内存超卖。当多个突发流量重叠时,宿主机物理内存会被瞬间抽干。此时 K8s 会无情驱逐这些
Request < Limit的 Pod。服务会被频繁杀死重启。
总结与架构师建议
最后,为读者提供一份简单粗暴的速查表:
| 资源维度 | 参数配置 | 核心作用与架构意义 |
| ———— | ————————- | ———————————————————— |
| CPU | requests = 偏低真实值 | 必须设置。决定集群调度密度,保障 CPU 满载时的最低算力权重。 |
| CPU | limits = 不设置 (Unset) | 建议不设。消除 CFS 限流导致的延迟长尾,利用空闲算力应对突发。 |
| Memory | requests = 高位真实值 | 必须设置。决定在宿主机内存不足时,被驱逐(Eviction)的风险概率。 |
| Memory | limits = 贴近最大峰值 | 必须设置。防止内存泄漏拖垮整个物理节点。 |
| 核心服务 | Mem Req == Mem Lim | 牺牲一定的装箱率(账面资源),换取绝对不被系统无辜连累猎杀的稳定性。 |
| 边缘服务 | Mem Req < Mem Lim | 承担可能被节点驱逐的风险,换取集群机器成本的大幅度降低(超卖)。 |
最佳实践补丁: 如果你选择了“核心保命流”(Req==Lim)又心疼浪费的资源,请不要通过降低 Request 来制造差值,而是应该引入 HPA(水平自动扩缩容)。将单体内存压低,遇到流量洪峰时通过增加 Pod 副本数来抗压,这才是云原生架构“横向扩展”的终极奥义。
突破静态配置的死局:动态扩缩容(HPA & VPA)的底层逻辑
单靠静态设定 Requests 和 Limits,永远无法完美契合业务流量的波峰波谷。云原生真正的威力在于“按需变形”。
HPA (Horizontal Pod Autoscaler) – 横向分流,对抗高并发的终极武器
千万不要试图用单实例的 CPU 或内存极限去硬扛大促峰值,这既危险又昂贵。
- 它与 Resources 的强绑定关系: HPA 默认是基于资源的利用率触发的(比如 CPU 达到 80%)。注意这里的坑:K8s 计算利用率的公式是
当前真实使用量 / Pod 的 CPU Request。- 如果你不设置 CPU Request,基于 CPU 的 HPA 将彻底失效,因为分母为 0(无法计算百分比)。
- 如果你把 CPU Request 设得过低(比如 10m),只要流量稍微波动,利用率瞬间飙升到 500%,HPA 会被频繁触发,导致集群产生严重的“扩容抖动”。
- 架构师建议: 将单 Pod 的 Memory 限制死(
Req == Lim),剥夺 CPU Limit,然后将 HPA 的阈值设为 CPU Request 的 70% 或基于并发请求数(如 Knative RPS)进行秒级横向扩容。
VPA (Vertical Pod Autoscaler) – 垂直修正,治疗“拍脑袋配置”的良药
很多时候,开发人员根本不知道自己的 Java 或 Go 程序到底需要多少内存。
- 它的核心价值: VPA 组件会持续读取历史监控数据,自动帮你计算出最科学的
Requests和Limits。 - 工作模式与痛点:
- Recommend 模式(推荐使用): 只提供修改建议,不主动干预。你可以结合 CI/CD 流水线,在下次发布时自动应用这些建议。
- Auto 模式(谨慎使用): 自动修改配置。但在 K8s 1.27 版本之前,修改资源的代价是必须杀掉 Pod 重启。如果核心服务在高峰期被 VPA 重启,那就是人为制造的灾难。(注:K8s 1.27+ 引入了原地扩缩容
In-place Resize,未来这一痛点将被消除)。
- 冲突警告: 绝对不要在同一个指标(比如 CPU)上同时开启 HPA 和 VPA,它们会互相打架,导致 Pod 数量和单体配额同时疯狂震荡。
架构师的军火库:资源水位探测与超卖神器
明白了理论,落地时我们需要工具。针对你前面担忧的“为了稳定浪费内存(Req==Lim),为了省钱牺牲稳定(Req<Lim)”的死局,目前业界有以下几款顶级开源工具可以破局。
Robusta KRR (Kubernetes Resource Recommender) —— 监控数据的“提纯器”
这正是你之前导数据的工具。它不改变 K8s 的调度底层,而是作为你的“首席精算师”。
- 核心打法: KRR 从 Prometheus 提取真实历史水位,帮你打破“凭感觉填 YAML”的困境。
- 解决的问题:
- 它建议你删掉
CPU Limit,直接根除了 CFS Throttling(限流卡顿)问题。 - 它根据 P99 分位值建议你的
Memory Request和Limit,让你能够安心地使用Req == Lim的策略,把所谓的“浪费(稳定性溢价)”压到最低。
- 它建议你删掉
- 定位: 静态配置优化工具。适合日常巡检、CI/CD 准入拦截。
Koordinator (阿里开源) —— 终极
如果你对那 0.5G 被账面锁死的内存耿耿于怀,Koordinator 是解决这个问题的标准答案。它是阿里双十一大规模混部(Colocation)技术的开源版。
- 核心打法(QoS 重新定义): 它绕过了原生的 K8s 调度器,引入了更精细的优先级:
- LS (Latency Sensitive – 在线服务): 比如你的微服务,要求绝对稳定,设置
Req == Lim == 2G。 - BE (Best Effort – 离线任务): 比如日志压缩、数据跑批。
- LS (Latency Sensitive – 在线服务): 比如你的微服务,要求绝对稳定,设置
- 它是怎么压榨资源的? 你的微服务申请了 2G,但平时只用 1G。Koordinator 的底层组件(Koordlet)会实时计算出这 1G 的“真实物理空闲”,然后把这 1G 临时借给 BE 级别的离线任务用。
- 稳定性兜底(秒级驱逐): 当你的微服务突然来了流量,需要用到 1.8G 内存时。Koordlet 会在毫秒级反应过来,直接把占用内存的离线任务强杀掉,把物理内存还给你的微服务。
- 定位: 节点级物理资源压榨器。让“账面资源满载率”和“物理真实利用率”同时达到 80%+,而核心业务毫发无损。
Crane (腾讯开源) —— 具有预知能力的智能调度
如果你的集群跑在腾讯云 TKE 上,或者业务有明显的潮汐特性(比如外卖系统中午流量大),Crane 非常合适。
- 核心打法(时间序列预测): 基于历史监控,用算法预测未来 24 小时的流量走势。
- 解决的问题: HPA 是滞后的(流量打进来了,CPU 飙高了才扩容,此时往往已经卡顿)。Crane 的 EHPA(Effective HPA)可以在高峰期到来前 10 分钟,提前帮你把 Pod 扩容好。
- 防干扰调度: Crane 会识别出哪些节点物理 CPU 真正到了高水位,从而拦截 K8s 瞎眼调度器的分配请求,防止某些节点被活活拖死。
- 定位: 智能弹性与成本分析平台。
Goldilocks (Fairwinds) —— 轻量级 Baseline 工具
- 核心打法: 利用 VPA 的 Recommend 模式,结合一个非常直观的 UI 面板,给你的每一个 Deployment 展示一套推荐的 Request/Limit 基线。
- 定位: 如果觉得 KRR 的命令行不够直观,可以作为集群可视化的资源推荐补充工具。
终极总结:现代云原生微服务资源配置蓝图
作为集群的管理者,你可以按照以下路径构建你的资源防线:
- 第一层(配置基线): 借助 Robusta KRR 或 Goldilocks,为所有服务刷上基于历史数据的 Baseline。核心服务锁死
Mem Req == Mem Lim,拔掉CPU Limit。 - 第二层(弹性抗压): 依托科学的
CPU Request设定,全面铺开 HPA。用增加副本数来应对突发,而不是靠单 Pod 的内存硬扛。 - 第三层(极致压榨): 如果集群规模足够大,机器成本成为痛点,引入 Koordinator。将无状态后台任务与核心微服务混合部署,吃干榨净每一兆物理内存。
