【K8s运维实战】Kubernetes 资源需求与限制终极实战:从 requests/limits 到 QoS 驱逐,避开 80% 的配置陷阱

📅 2026/7/7 18:38:18
【K8s运维实战】Kubernetes 资源需求与限制终极实战:从 requests/limits 到 QoS 驱逐,避开 80% 的配置陷阱
一句话概括本文从 SRE 视角彻底讲透 Kubernetes 资源请求requests与限制limits的核心机制、Pod QoS 类的判定与驱逐玄机并特别梳理了 Pod 级别资源v1.34等新特性的正确落地方式。开篇那个被“团灭”的夜晚前两年我接手一个微服务集群每天凌晨 3 点定时出现大面积 503。查了半天发现罪魁祸首是一个监控 sidecar 容器——它没设 resources节点内存一吃紧BestEffort 类的它率先被驱逐。问题在于主容器和 sidecar 是“一损俱损”的 Podsidecar 一挂主容器的流量切不过来直接团灭。更气人的是开发者信誓旦旦地说“我配了 limit 啊怎么还被杀了”——他确实给主容器配了 memory limit但 sidecar 没配Pod 整体掉进了 Burstable而非 Guaranteed节点压力上来时照样优先被盯上。这就是我写这篇指南的初衷。搞懂 Kubernetes 资源管理不是背几个 YAML 参数而是看懂背后的调度逻辑、驱逐玄机以及新版本特性带来的便利与陷阱。适用环境与前置条件目标读者具备 Kubectl 基础操作的初级 SRE / 运维/研发工程师核心版本Kubernetes v1.36内容兼容 v1.28节点 OSLinuxcgroup v2自 v1.25 起稳定支持前置动作kubectl已配置能正常操作 Namespace一、核心概念拆解1. Requests 和 Limits调度决策与运行时囚笼在 K8s 中这两个字段属于resources下的子项缺一不可但很多人只配一个。requests保底诉求调度器Scheduler根据此值找“有这么多空闲资源”的节点。它决定了 Pod 被放在哪台机器。limits运行天花板kubelet 和容器运行时依靠此值进行运行时限制。它决定了 Pod能用多少。这里有一条隐蔽的默认值规则如果你只设置了limits而未设置requestsKubernetes 的 API 默认逻辑Defaulting Logic会自动将requests的值设置为与limits相同此行为自 v1.0 起即为默认不由任何特性门控控制。如果你只设置了requests而未设置limits则limits为空即无上限。2. CPU 与内存的“量刑”差异这是新手翻车第一站必须用大白话讲透CPUlimits——硬节流Throttling内核的 CFS 调度器直接动手。容器用超了 CPUlimits会被强制“压扁”使用率。你在kubectl top里看到 CPU 被一条线压得死死的那就是limits在起作用。优点是不会杀容器缺点是应用变卡。内存limits——软淘汰反应式 OOM Kill内核并不实时拦截内存分配。如果节点内存充足容器超过 memory limit 并不会立刻被杀内核允许其继续分配。一旦该容器的内存使用量触达 limit同时该 cgroup 层级发生内存分配失败即内核无法在 cgroup 限制内满足分配请求内核就会触发 OOM Killer杀掉这个超限最多的容器。从集群视角看这通常表现为节点内存进入回收压力状态。这也是为什么内存limits设得太紧应用会莫名其妙地崩掉因为有时候内存 spike 就那么一瞬间。3. 资源单位速查表资源写法示例本质特别注意CPU1、0.5、500m1000m 1 个 vCPU 核最小粒度 1m不能写0.005内存256Mi、1Gi、1GMi2²⁰,M10⁶强烈建议只用Mi/Gi避免十进制与二进制换算带来的容量误判比如 1G 1Gi4. 容器可见资源free -m 看到的一定是真话吗很多读者会问“我进容器执行free -m看到的总内存是宿主机的还是我的 limit”结论是取决于 cgroup 版本和容器运行时配置不一定是宿主机总内存。cgroup v1 环境容器内的/proc/meminfo大多直接继承自宿主机并未完全隔离。你看到的总内存极大概率是宿主机总内存。cgroup v2 环境推荐K8s v1.25 稳定容器运行时如 containerd 1.6会将 cgroup 的 memory 子系统挂载进容器此时容器内看到的/proc/meminfo可能已经被 cgroup 限定了上限。换言之它在某些环境下反映的是limits的值。因此不要依赖容器内free命令的结果来做容量规划。判断容器内存是否超限应通过kubectl describe pod看State: OOMKilled或通过kubectl top/ Prometheus 监控真实 RSS 占用。二、Pod 服务质量QoS类——决定“生死簿”的等级Kubernetes 根据 Pod 内所有容器的 requests/limits 配置自动为 Pod 打上 QoS 标签。这个标签不由你直接指定而是由系统自动计算。1. 三大 QoS 铁律QoS 类判定条件需同时满足等级Guaranteed1. 每个容器都配了 CPU request 和 limit且相等2. 每个容器都配了 Memory request 和 limit且相等3.数值大于 0最高最不容易被杀Burstable不满足 Guaranteed但至少有一个容器设置了 request 或 limit中等BestEffort没有任何一个容器设置了任何 request 或 limit最低优先被杀踩坑提醒Pod 级别资源引入后见下文进阶篇QoS 判定会优先参考 Pod 级别资源。如果 Pod 级别完整定义了 request 和 limit 且二者相等则整个 Pod 为 Guaranteed即使某个 sidecar 容器没配也不会拉低 QoS 等级。2. 驱逐优先级——很多人都漏看了这条细则当节点出现磁盘或内存压力时kubelet 会启动驱逐Eviction流程。顺序依次是BestEffort → Burstable → Guaranteed但先别急这里隐藏着一个极其重要的限定条件来自官方文档的原文When eviction is due to resource pressure,only Pods exceeding their resource requests are candidates for eviction.翻译成人话只有实际内存/CPU 使用量超过了其requests的 Pod才会被列入驱逐候选名单。举个例子你的 Pod 是 Burstable 等级requests.memory设为 1Gi实际用了 900Mi没超。隔壁 BestEffort Pod 全被驱逐了节点还是不够这时候会轮到你吗不会因为你的使用量没超过 requests即使你是 Burstable你也可以和 Guaranteed 一样安全。只有当你的内存飙到 1.1Gi超过 requests时你才进入候选名单。最后补充一句以防混淆节点驱逐Eviction是针对节点整体资源不足时删除 Pod 的行为由 kubelet 发起而OOM Kill是容器内存使用量超过limits且该 cgroup 层级发生内存分配失败时由 Linux 内核直接杀进程。二者触发主体和条件不同但 QoS 等级高Guaranteed的 Pod 在两种场景下都是最后被波及的。三、LimitRange 与 ResourceQuota命名空间层面的“防呆设计”光靠每个开发者自觉配 resources 不现实好的 SRE 懂得在集群层面“立规矩”。LimitRange单个容器的“框框”它负责约束单个 Pod/容器的资源配置范围还能自动注入默认值。apiVersion: v1 kind: LimitRange metadata: name: core-limits namespace: prod spec: limits: - default: # 若容器未设 limit自动补这个 cpu: 500m memory: 1Gi defaultRequest: # 若容器未设 request自动补这个 cpu: 200m memory: 256Mi max: # 硬上限超过则拒绝创建 cpu: 4 memory: 8Gi min: # 硬下限低于则拒绝创建 cpu: 50m memory: 64Mi type: Container⚠️补全逻辑LimitRange 的default和defaultRequest字段分别独立生效。如果容器未设置limits则注入default值如果容器未设置requests则注入defaultRequest值。因此即使容器设置了requests但未设limits只要 LimitRange 中定义了default仍然会自动补上limits。只有当 LimitRange 中未定义对应字段时才会保持“无上限”或“无下限”状态。校验时机LimitRange 只会在 Pod创建和更新Update时进行校验。如果一个 Pod 在创建时符合规范中途你通过kubectl edit把资源改超了控制平面会直接拒绝这次更新。ResourceQuota命名空间的“总量红线”防止某个 Namespace 里的 Pod 总和把整个集群的 CPU/内存吃光。apiVersion: v1 kind: ResourceQuota metadata: name: quota-prod namespace: prod spec: hard: requests.cpu: 10 requests.memory: 20Gi limits.cpu: 20 limits.memory: 40Gi pods: 50四、进阶话题Pod 级别资源Kubernetes v1.34 Beta 特性这一部分原本容易与基础概念混淆。我将它单独抽出来作为“进阶话题”。如果你还在用 v1.28可以跳过本节不影响对核心机制的理解。1. 解决了什么痛点在 v1.34 之前resources 字段只能放在containers下面容器级别。假设一个 Pod 里有主容器 3 个 sidecar每个 sidecar 都要单独配非常繁琐更难受的是调度器会分别加和这些 requests 找节点但运行时又是分别限流导致主容器空闲时也用不了 sidecar 的配额。Pod 级别资源允许你直接在spec.resources里为整个 Pod设定总量。2. 它是“覆盖”还是“聚合”千万别理解错了很多人在这一点上栽跟头。我用两句话讲透调度决策如果定义了 Pod 级别的requests调度器直接用这个值找节点不再聚合各容器的 requests。但如果 Pod 级别只有limits没有requests调度还是会用容器级别requests的聚合值。运行时限制关键Pod 级别的limits是所有容器总量使用的硬上限Hard Limit。即使你把主容器和 sidecar 的容器级limits加起来是 4Gi只要 Pod 级别limits设了 2Gi总内存超过 2Gi 就会触发 OOM Kill。容器级别的limits依然生效但总量不能超过 Pod 级别的天花板。QoS 判定如果 Pod 级别字段完整定义了 request 和 limit 且二者相等QoS 类判定优先依据 Pod 级别字段此时即使某个 sidecar 容器未设 resourcesPod 整体仍可能为 Guaranteed若 Pod 级别字段不完整如只有 request 没有 limit则回退到容器级别的聚合判定。OOM Score计算 OOM 优先级时Pod 级别资源和容器级别资源同时参考。所以如果有人说“Pod 级别资源覆盖容器级别”这是错误的。更准确的说法是Pod 级别资源是一个“聚合硬约束”它和容器级别资源共同作用且在调度和 QoS 判定中拥有更高权重。如果容器级别的limits之和小于 Pod 级别的limits运行时最终会取两者中的较小值作为该容器的实际上限即 Pod 级别只封顶总量不抬升单个容器的下限。这属于“木桶效应”Pod 级别资源是天花板容器级别资源是隔板。3. v1.36 补充能力原地垂直扩缩容已 Beta在 v1.34 时修改 Pod 级别资源需要重建 Pod。但在v1.36 中InPlacePodLevelResourcesVerticalScaling特性门控已升级为 Beta 且默认开启。这意味着你可以直接修改正在运行的 Pod 的spec.resources无需重建kubelet 会动态调整 cgroup 配置。当然修改 QoS 类比如从 Burstable 改成 Guaranteed依然会被 API Server 拦截。五、生产环境最佳实践我踩过的坑核心服务必须拿 Guaranteed别心存侥幸。把 CPU 和 Memory 的 request 和 limit 设成完全一致这是最硬的“保命符”。requests千万别设太低有些同学为了“超卖”把requests设得极低这是极其危险的。一旦节点资源紧张你的 Pod 会立刻成为驱逐候选还记得前面的细则吗超过 requests 就会被盯上。内存limits留 20%~30% 余量因为内存是“反应式”的瞬间尖刺很容易触发 OOM。压测后的平均内存占用 × 1.3是我惯用的公式。善用 LimitRange 兜底我在每个生产 Namespace 都强推 LimitRange强制 defaultRequest 至少 100m CPU / 128Mi 内存防止研发忘记配导致变成 BestEffort。尝试 Pod 级别资源v1.34如果你的集群版本支持强烈建议在 Istio 或 Dapr 这种有大量 Sidecar 的场景下试点 Pod 级别资源能极大简化配置并让资源利用更灵活。六、验证方法查看 Pod 的 QoS 等级kubectl get pod my-app -o jsonpath{.status.qosClass} # Guaranteed / Burstable / BestEffort查看节点 cgroup 版本SSH 上节点后执行stat -fc %T /sys/fs/cgroup/ # cgroup2fs v2推荐tmpfs v1验证 LimitRange 是否自动注入了默认值kubectl describe pod my-app | grep -A 5 Requests七、常见问题与真实报错含 GitHub 原文引用Q1修改 Pod 资源后报错拒绝更新提示 QoS 类变更报错原文Kubernetes API Server 返回Pod my-app is invalid: spec.containers[0].resources.requests: Forbidden: pod updates may not change the request of a container which would result in a change to the QoS class原因Kubernetes 不允许正在运行的 Pod 变更其 QoS 类比如从 Burstable 改成 Guaranteed。解决删除 Pod 重建或通过 Deployment 滚动更新kubectl rollout restart deploy/my-app。Q2Pod 被 OOMKilled但监控显示内存没超 limit报错原文kubectl describe pod输出State: TerminatedReason: OOMKilledExit Code: 137可能原因1内存瞬间尖刺监控采样粒度太粗没抓到2容器内进程如 JVM与 cgroup 的统计口径不同JVM 未正确识别 cgroup v2 限制。解决Java 应用务必添加 JVM 参数-XX:UseContainerSupportJDK 8u191 默认开启JDK 11 推荐明确指定。Q3Pod 级别资源和容器级别资源同时存在时调度混乱报错原文Kubernetes GitHub Issue #126662There is only a requests field (not limits) at the Pod level. Both requests and limits are set at the container level. The calculated pod limit is based on container limits, pod request may exceed pod limit leading to unexpected scheduling.参考原因Pod 级别的requests可能大于容器级别limits之和导致调度器认为节点资源充足但运行时总量受限。解决确保 Pod 级别requests≤ 容器级别limits之和或直接让二者对齐。