Kubernetes 的资源模型允许节点上所有 Pod 的 requests 之和超过节点实际容量,这种机制本意是提升资源利用率,但如果没有配套的管控手段,很容易演变成一场稳定性事故。很多团队在业务高峰期吃过亏:某个批量任务把内存吃到上限,内核 OOM Killer 直接杀掉了同节点上的核心数据库 Pod。要真正理解并治理资源超卖,需要从原理、风险、防护三个层面系统展开。

一、Kubernetes 资源模型与超卖的本质
先明确两个关键概念:requests 是调度时的依据,kube-scheduler 根据它决定 Pod 放到哪个节点;limits 是运行时的上限,kubelet 依据它对容器进行约束。超卖指的是节点上所有 Pod 的 requests 总和超过了节点实际的 Allocatable 资源,这种状态在 Kubernetes 中是合法的,调度器不会阻止。
需要注意的是,CPU 和内存在超卖场景下的表现完全不同。CPU 属于可压缩资源,容器超过 limit 后会被限流,表现为响应变慢,但进程不会被杀掉。而内存属于不可压缩资源,一旦容器实际使用内存超过 limit,内核的 cgroup 机制会直接触发 OOM Kill,容器被杀死重启。这就是为什么 CPU 超卖相对温和,而内存超卖往往是事故的直接导火索。
来看一个典型的资源配置,requests 低于 limits 就是超卖的常见形态:
resources:
requests:
cpu: "100m" # 调度时只占 0.1 核
memory: "128Mi" # 调度时只占 128MB
limits:
cpu: "500m"
memory: "512Mi" # 实际可用到 512MB
假设一台 8 核 16GB 的节点,Allocatable 大约是 7.6 核 15GB。如果调度器往上面塞了 20 个 requests 只有 100m CPU 的 Pod,requests 总和才 2 核,看起来很安全;但这些 Pod 的 limits 都设成 1 核,理论上限就是 20 核,远超节点容量。一旦多个 Pod 同时进入高负载,CPU 就会出现严重争抢。内存的情况更危险,如果 20 个 Pod 的内存 limits 都是 1GB,潜在需求 20GB,而节点只有 15GB,极端情况下内核 OOM Killer 会随机挑选进程杀掉,它可不管被杀的是不是核心服务。
二、资源超卖带来的四类典型风险
1. OOM Kill 导致服务异常重启
这是最直接的后果。内存超卖时,节点整体内存耗尽,kubelet 之前,内核会先触发全局的 OOM Killer,按照 oom_score 评分挑选牺牲者。内存占用越大的进程越容易被选中,而很多缓存类、数据库类服务恰恰是内存大户,被杀概率反而更高。服务重启期间请求失败、连接池断裂,影响会传导到上游。
2. 节点压力驱逐引发连锁反应
当节点出现 MemoryPressure 或 DiskPressure 时,kubelet 会按照驱逐优先级清理 Pod。被驱逐的 Pod 会被重新调度到其他节点,如果整个集群都处于高负载状态,这些 Pod 会加剧其他节点的压力,进而触发更多驱逐,形成雪崩式的连锁反应。尤其是 daemonset 类 Pod 被驱逐后,日志采集、监控 agent 缺失,问题排查会变得异常困难。
3. CPU 限流造成性能毛刺
CFS 配额机制下,容器在 100ms 周期内用完配额就会被 throttled。CPU 超卖叠加 limits 设置过低时,应用会周期性地被限流,表现为延迟毛刺和 P99 飙高。Java 应用尤其敏感,GC 线程被限流后停顿时间会明显拉长,甚至触发 GC 超时的假死。
4. 调度失衡与热点节点
requests 设置得过低还会让调度器误判。调度器认为某个节点还很空闲,实际运行时却已经满载。比如按 requests 均衡打散的策略下,一批重负载 Pod 可能集中落到 requests 虚低的节点上,形成热点。这种失衡很难通过常规监控提前发现,因为调度层面的使用率和运行时的真实使用率是两套数据。
三、构建多层防护栏的实践方案
第一层:规范 requests 与 limits 的设置
基础但最重要的一层。合理做法是参考容器真实的使用数据来设定:requests 取近一到两周 P95 使用量的 1.1 到 1.2 倍,limits 取 P99 或峰值的 1.3 倍左右,避免拍脑袋。可以利用 Prometheus 的 container_resource_usage 等指标分析历史用量。CPU 超卖比例建议控制在 1.5 倍以内,内存则尽量不做超卖,或者控制在 1.1 倍以内。
第二层:用 LimitRange 做命名空间兜底
团队里总有开发者忘记配置 resources,或者随手填个天文数字。LimitRange 可以为命名空间设置默认值和上下限,补齐管理漏洞:
apiVersion: v1
kind: LimitRange
metadata:
name: ns-default-limits
namespace: prod-app
spec:
limits:
- type: Container
default: # 未声明 limits 时的默认值
cpu: "500m"
memory: "512Mi"
defaultRequest: # 未声明 requests 时的默认值
cpu: "100m"
memory: "128Mi"
max: # 单容器允许的上限
cpu: "2"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
这样即使某个部署漏配了资源声明,也会被自动注入合理的默认值,同时防止个别服务申请超大资源挤占配额。
第三层:用 ResourceQuota 控制命名空间总量
LimitRange 管的是单个容器,ResourceQua 管的是总量。它限制一个命名空间内所有 Pod 的 requests 与 limits 总和,防止某个团队或业务线把整个节点池吃穿:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 60Gi
pods: "100"
配合多团队共享集群的场景,ResourceQuota 是隔离资源边界的核心手段。超出配额的创建请求会被 API Server 直接拒绝,从入口处就拦住了风险。
第四层:优先级与抢占保护核心服务
通过 PriorityClass 给不同等级的业务打标,当资源不足需要驱逐或抢占时,低优先级的批处理、压测任务会被先清理,核心链路得到保护:
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: critical-online value: 1000 globalDefault: false description: "核心在线服务,抢占批处理任务"
建议将在线服务设为高优先级,离线任务、训练任务设为低优先级甚至允许被驱逐,通过优先级差异实现错峰共存,这也是在离线混部方案的基本前提。
第五层:节点级与集群级保护
最后是兜底手段。一方面配置 --eviction-hard 让 kubelet 在节点内存可用率低于阈值时提前驱逐可回收的 Pod,避免硬性 OOM;另一方面利用 kube-reserved 和 system-reserved 给系统组件预留资源,并通过 taint 结合节点容量规划,控制单节点的实际超卖倍率。对于关键业务,还可以限制其只调度到非超卖专用节点上,实现物理隔离。
总结来看,资源超卖本身不是洪水猛兽,它是提升集群利用率、降低成本的正当手段,但前提是必须建立完整的防护栏体系:从单容器的 requests 与 limits 规范,到命名空间级的 LimitRange 与 ResourceQuota,再到优先级保护和节点级预留。多层防线叠加,才能在弹性与稳定之间找到平衡点。
Kubernetes资源超卖LimitRange修改时间:2026-09-09 21:40:57