Kubernetes QoS 与驱逐实战:Guaranteed/Burstable/BestEffort 决定谁先被赶下节点

📅 2026/8/22 2:44:15
Kubernetes QoS 与驱逐实战:Guaranteed/Burstable/BestEffort 决定谁先被赶下节点
Kubernetes QoS 与驱逐实战:Guaranteed/Burstable/BestEffort 决定谁先被赶下节点节点内存告急时,kubelet 会开始驱逐 Pod 腾资源。奇怪的是,有时候被赶走的偏偏是你最重要的服务,而一堆无关紧要的任务却安然无恙。这不是随机的——Kubernetes 用QoS(服务质量)等级决定谁先死。而 QoS 等级完全由你怎么写requests和limits决定,很多人根本没意识到自己给核心服务判了优先驱逐。这篇讲清楚三种 QoS 怎么来的、驱逐顺序怎么排,以及怎么保住关键 Pod。三种 QoS 等级是怎么定出来的Kubernetes 不需要你手动声明 QoS,它根据 Pod 里所有容器的requests/limits自动推导,规则就三条:Guaranteed(有保障):每个容器的每种资源(CPU 和内存)都设了limits,且requests limits(不写 requests 时默认等于 limits)。BestEffort(尽力而为):所有容器都没设任何 requests 和 limits。Burstable(可突发):上面两种都不满足——只要设了至少一个 request/limit,但又不满足 Guaranteed 的条件,就是它。绝大多数 Pod 都落在这一档。看三个具体例子。Guaranteed:resources:requests:cpu:500mmemory:256Milimits:cpu:500m# 和 requests 完全相等memory:256Mi# CPU 和内存都设了且相等 → GuaranteedBurstable(最常见,requests 小于 limits):resources:requests:memory:128Mi# 只保证 128Milimits:memory:512Mi# 允许突发到 512Mi → Burstable# 注意:这里没写 cpu,所以不满足 GuaranteedBestEffort(什么都不写):# resources 字段整个省略 → BestEffort查一个已运行 Pod 的 QoS 等级:kubectl get podpod-name-ojsonpath{.status.qosClass}# 输出 Guaranteed / Burstable / BestEffort驱逐顺序:BestEffort 先死,Guaranteed 最后当节点出现内存压力(memory 是不可压缩资源,不够就得杀 Pod),kubelet 按这个顺序驱逐:先杀 BestEffort:没设任何 request,系统认为它可有可无。再杀 Burstable 中超用最多的:实际用量超过其 memoryrequest越多的,越先被杀。最后才动 Guaranteed:只要它用量没超过自己的 limits,几乎不会因为节点压力被驱逐(除非系统级组件也保不住)。这里有个关键细节:Burstable 之间的排序看的是实际使用量超出 request 的部分,而不是绝对用量。也就是说,一个 request 设得很低、实际却用很多的 Pod,比一个老老实实按 request 用的 Pod 更危险。实战踩坑:给核心服务判了死刑却不自知最典型的错误是这样:核心 API 服务为了省资源、能弹性,requests 设得很低、limits 设得很高:# 反面教材:核心服务这么配,节点一紧张它就被驱逐resources:requests:memory:64Mi# 保障值极低limits:memory:2Gi# 实际经常用到 1.5Gi这个 Pod 是 Burstable,而且实际用量(1.5Gi)远超 request(64Mi),节点内存一紧张,它在 Burstable 队列里排最前面,第一个被赶走。你以为限制高优先级高,恰恰相反。正确做法:关键服务把requests设到接近真实用量,让保障值托底。要最高保障就做成 Guaranteed:# 核心服务:requests 贴近真实峰值,limits 相等 → Guaranteed,几乎不被驱逐resources:requests:cpu:1memory:1536Milimits:cpu:1memory:1536Mi别混淆:QoS 驱逐 ≠ OOMKilled两者都和内存有关,但机制完全不同,排查时别搞混:QoS 驱逐(Evicted):是kubelet因为节点整体内存不够,主动挑 Pod 赶走。Pod 状态变Evicted,describe里能看到The node was low on resource: memory。OOMKilled:是Linux 内核因为单个容器用量超过了自己的 memorylimit,把容器进程杀掉。describe里容器State显示OOMKilled,exit code 137。# 看是不是被驱逐kubectl get podname-ojsonpath{.status.reason}# Evicted# 看容器是不是 OOMkubectl describe podname|grep-A3Last State# OOMKilled, Exit Code: 137一个是节点没资源、挑软柿子捏,一个是你自己超了自己的额度。想减少 OOMKilled 就调高 memory limit;想让服务不被节点压力驱逐,就调高 memory request(或做成 Guaranteed)。保命组合:关键系统 Pod 用 PriorityClass 加双保险QoS 决定资源不够时谁先走,而PriorityClass是另一维度:调度和抢占时谁优先。生产上关键组件建议两者都配,给核心服务加一个高优先级:apiVersion:scheduling.k8s.io/v1kind:PriorityClassmetadata:name:high-priorityvalue:1000000globalDefault:falsedescription:关键在线服务专用---# Pod spec 里引用它spec:priorityClassName:high-priority高priority的 Pod 在节点资源不足调度不下时,还能抢占(驱逐)低优先级 Pod 给自己让位。QoS 管 kubelet 的资源回收顺序,PriorityClass 管调度器的抢占顺序,两者配合才是完整的保命方案。小结QoS 等级不用手动声明,由requests/limits自动推导:都相等Guaranteed,啥都不写BestEffort,其余Burstable。节点内存压力时驱逐顺序:BestEffort → 超用最多的 Burstable → Guaranteed。最大的坑:核心服务 request 设太低、limit 设很高,会被排到 Burstable 驱逐队列最前面——关键服务要把 request 贴近真实用量。Evicted(kubelet 节点级驱逐)和 OOMKilled(内核单容器超 limit)是两回事,看status.reason和 exit code 137 区分。关键组件再叠一层PriorityClass,QoS 管资源回收、Priority 管调度抢占,双保险。一句话记忆点:给核心服务设够 requests,才不会在节点紧张时被当成第一个牺牲品。