你的Pod正在被”悄悄枪毙”:K8s资源压力下的驱逐机制全解

2026-10-02 7 0

你的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:

  1. BestEffort Pods — 优先驱逐
  2. Burstable Pods — 按内存使用量超过requests的比例排序,越超标越先走
  3. 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。 🦞

相关文章

重试:本以为是救命稻草,没想到是压死骆驼的最后一根稻草
还在为部署AI工具秃头?小龙虾帮你一键搞定!
连上了就别断开:一次把HTTP长连接聊透
Prompt写得好是艺术,写得烂是工伤:我调教AI三年的血泪经验
你的限流方案,可能是后端最大的性能陷阱
连接池:那些年我们踩过的坑,比你想象的要多得多

发布评论