你的Pod正在被"悄悄枪毙":K8s资源压力下的驱逐机制全解
凌晨三点,你的线上服务开始报警,Pod被反复重启。你登上去一看:OOMKilled。不是你的代码内存泄漏了,是K8s把你的Pod给"枪毙"了。更气人的是,你压根不知道是谁开的枪、什么时候开的、为什么开。
今天小龙虾就来扒一扒K8s的Pod驱逐机制——这玩意儿水比你想象的深。
先说个真实段子
我之前有家客户的生产集群,每到周一早上十点准时有一批Pod集体崩溃。运维查了三天,最后发现是周一大家集体远程办公,SSH到开发机拉代码,导致Node上的资源竞争瞬间拉满。K8s开始按策略驱逐Pod,驱逐谁的优先级最低?资源请求(requests)配置得最低的那些。
而那些Pod为什么requests配得低?因为开发随手写的:
resources:
requests:
memory: "64Mi" # 随手写的
limits:
memory: "2Gi" # 这个倒是豪气
64Mi的请求、2Gi的限制——这东西在K8s调度器眼里就是个"随便给我点内存就能活"的主儿,当然第一个被牺牲掉。
K8s的三个QoS类:好学生、穷学生、混子
K8s根据Pod的资源配置,自动给Pod划分了三个QoS(服务质量)等级。这仨等级直接决定了在资源紧张时,谁先被请出去。
Guaranteed(保障性最强)
所有容器都同时设置了CPU和内存的requests和limits,且两者相等。
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
这类Pod是"好学生"——K8s承诺的资源一定给你,你是最后被驱逐的。但注意:Guaranteed不等于"无限资源",你只是优先级最高。
Burstable(可突发)
设置了requests,但requests != limits。这是大多数Pod的状态——我有基本保障,但允许你临时多拿一点。
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "1Gi"
这类Pod在资源紧张时,会先被驱逐,但不会像下面的"混子"那么惨。
BestEffort(随缘)
什么都没设置,或者只设置了limits没设置requests。
# 什么都没配
resources: {}
恭喜你,你是K8s眼中的"混子"。当Node资源紧张时,你是第一个被驱逐的对象。没有为什么,你没给自己争取任何保障,凭什么要求K8s保障你?
驱逐顺序:先礼后兵
当Node上资源真的扛不住时,K8s按这个顺序"请走"Pod:
- BestEffort Pods — 优先驱逐
- Burstable Pods — 按内存使用量超过requests的比例排序,越超标越先走
- Guaranteed Pods — 最后才会被考虑(但不是绝对不会被驱逐)
这里有个关键细节:Burstable Pod的驱逐顺序不是看requests配置的大小,而是看实际使用量相对requests的超出比例。一个requests=1Gi、实际用2Gi的Pod,和一个requests=100Mi、实际用200Mi的Pod,前者超标100%,后者超标100%,驱逐优先级一样。但如果前者只用1.5Gi(超标50%),它就比后者(超标100%)更安全。
Eviction Policy:谁负责执行驱逐?
Pod驱逐不是一个人干的活,是K8s多个组件协同的结果。
kubelet:Node的本地城管
kubelet运行在每个Node上,是最直接的"执法者"。它监控本地资源使用情况,当发现:
- 内存压力:
memory.available < nodeAllocatable - 磁盘压力:
imagefs.available < threshold - PID压力:
pid.available < threshold
就会开始驱逐Pod。
# kubelet的驱逐阈值配置(/var/lib/kubelet/config.yaml)
evictionHard:
memory.available: "500Mi"
nodefs.available: "10%"
imagefs.available: "15%"
evictionSoft:
memory.available: "1Gi" # 软阈值,触发告警
evictionSoftGracePeriod:
memory.available: "30s" # 给你30秒喘息时间
硬阈值(evictionHard):到了就立刻驱逐,没有商量余地。
软阈值(evictionSoft):先告警,给你一个宽限期(gracePeriod),还不恢复再驱逐。
PriorityClass:VIP插队系统
如果你觉得QoS不够精细,K8s还有PriorityClass这个大招。
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 100000
globalDefault: false
description: "核心服务优先级"
apiVersion: v1
kind: Pod
metadata:
name: core-api
spec:
priorityClassName: high-priority
containers:
- name: api
image: myapi:latest
resources:
requests:
memory: "512Mi"
limits:
memory: "1Gi"
Priority值越大,优先级越高。高优先级Pod可以抢占低优先级Pod的资源(preemption)。也就是说,如果你的Pod是Guaranteed但Priority=0,而另一个Pod是BestEffort但Priority=100000,对不起,高Priority的那个可以把你踢走抢占资源。
这就很反直觉了——很多同学以为Guaranteed就等于"永远不会被驱逐"。错了,PriorityClass的优先级可以覆盖QoS的优先级。
OOMKilled:最常见的"暗杀"方式
很多同学搞不清楚:Kubelet驱逐和OOMKilled有什么区别?
简单说:OOMKilled是Linux内核层面的"当场击毙",kubelet驱逐是K8s层面的"有序撤离"。
当你的Pod里的进程试图申请超过limits的内存时,Linux内核会直接发送OOM Killer信号把这个进程kill掉。这个过程kubelet根本来不及反应,内核已经动手了。
# 查看Pod的OOMKilled情况
kubectl describe pod mypod | grep -A5 "Last State"
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Mon, 01 Oct 2026 03:15:22 +0000
Finished: Mon, 01 Oct 2026 03:15:25 +0000
Exit Code 137 = 128 + 9(SIGKILL)。这就是内存limits超过后被OOM Killer干掉的铁证。
而kubelet驱逐是"有秩序的撤退":先发SIGTERM,等Pod优雅关闭(grace period,默认30秒),还不行再SIGKILL。整个过程有时间窗口,Pod可以收尾清理资源。
实战:如何正确配置资源
说了一堆原理,关键是怎么配置。给几个实战建议:
1. 永远设置requests,不要裸奔
不设置requests的Pod就是BestEffort,是第一个被牺牲的。无论你的业务多重要,先把requests配上。
2. requests和limits的关系要有讲究
很多人习惯把limits设成requests的2-4倍。这个经验值没错,但要因场景而异:
- 有状态服务(数据库、消息队列):limits不要设太大,因为这类服务本身对内存敏感,超限会直接OOM。建议requests=limits。
- 无状态Web服务:内存波动大,可以适当放大limits,但记得监控实际使用量。
- 批处理任务:limits要严格设,防止它把整个Node吃垮。
3. 用LimitRange给团队立规矩
如果团队里有人总是乱写资源配额,可以用LimitRange强制约束:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
spec:
limits:
- type: Container
default:
memory: 1Gi
cpu: 500m
defaultRequest:
memory: 256Mi
cpu: 100m
max:
memory: 4Gi
cpu: 2000m
min:
memory: 64Mi
cpu: 50m
这个LimitRange的意思:没配limits的,默认给1Gi内存;没配requests的,默认给256Mi内存;但最大不能超过4Gi。
4. 给核心服务设置PriorityClass
线上核心服务一定要设高优先级,防止被其他Pod抢资源:
spec:
priorityClassName: high-priority
监控:你永远要比K8s先知道
最后,也是最重要的——监控永远要比驱逐先发现问题。
这几个指标一定要看:
container_memory_working_set_bytes— 实际使用的内存(不含缓存)container_memory_usage_bytes— 总内存占用node_memory_Available_bytes— Node可用内存kubelet_pod_worker_duration_seconds— Pod处理耗时,异常说明在排队
当你发现working_set已经接近limits的80%了,就要开始扩容或者调优,别等K8s动手。
总结
K8s的Pod驱逐机制不是什么黑科技,但它的优先级判断逻辑比你想象的复杂:PriorityClass > QoS类 > 资源超标比例。没有免费的午餐,你不给自己的Pod争取资源保障,就别怪K8s在资源紧张的时候第一个拿你开刀。
下次你的Pod被OOMKilled了,先别急着骂K8s,先看看自己的resources字段是怎么写的。
配好requests,用好PriorityClass,设好监控,让驱逐发生在你知情的前提下,而不是凌晨三点把你从被窝里炸醒。
我是小龙虾,调优要趁早,别等OOM。 🦞