找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4764

积分

0

好友

618

主题
发表于 2 小时前 | 查看: 7| 回复: 0

凌晨两点,告警炸了:订单服务(核心)挂了,几个无关的离线批处理任务却还活着。你一脸懵——节点明明只是内存压力,为什么"最该活的核心服务先死"?

这不是玄学,是 QoS(Quality of Service)在背后决定谁生谁死。

一句话点透原理

Kubernetes 给每个 Pod 打一个服务质量等级:Guaranteed > Burstable > BestEffort。节点资源紧张时,等级越低越先被干掉。你没给核心服务设 request/limit ,它就落到了 BestEffort ,于是在内存风暴里第一个被驱逐——看似随机,实则必然。

下面用面试题把这套机制彻底串透。

一、QoS 三类判定规则(对照表)

判定只看 CPU 和内存的 requests / limits ,与镜像、端口、别的服务质量都无关。

QoS 等级 判定规则 典型场景 驱逐优先级
Guaranteed 每个容器都设了 CPU 和内存的 request,且 request == limit 数据库、核心网关 最后死
Burstable 至少一个容器设了 request 或 limit ,但不满足 Guaranteed 普通业务服务 中间死
BestEffort 所有容器都没设 request 和 limit 离线任务、临时脚本 最先死

关键坑位:只设 limit 不设 request 时,K8s 会自动把 request 补成与 limit 同值,因此它也算 Guaranteed。很多人以为"设了 limit 没设 request 是 Burstable",错了。

补充一点:判定是逐容器的,Pod 里只要有任意一个容器不满足 Guaranteed 全规则,整个 Pod 就降级。比如主容器 request==limit 很标准,但 sidecar 容器啥都没设,这个 Pod 就会掉到 BestEffort 。所以"保核心"必须把 Pod 内所有容器的 request/limit 都对齐,漏一个 sidecar 就前功尽弃。

二、三组可抄的 YAML 示例

1. Guaranteed(request == limit)

apiVersion: v1
kind: Pod
metadata:
  name: qos-guaranteed
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        cpu: "200m"
        memory: "256Mi"
      limits:
        cpu: "200m"
        memory: "256Mi"

2. Burstable(设了 request 但 limit 不同或只设其一)

apiVersion: v1
kind: Pod
metadata:
  name: qos-burstable
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        cpu: "100m"
        memory: "128Mi"
      limits:
        memory: "512Mi"
   # 只设内存 limit,CPU 没设

3. BestEffort(什么都不设)

apiVersion: v1
kind: Pod
metadata:
  name: qos-besteffort
spec:
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "sleep 3600"]

验证 QoS 等级

kubectl get pod qos-guaranteed -o jsonpath='{.status.qosClass}'
# 输出:Guaranteed

kubectl get pod qos-burstable -o jsonpath='{.status.qosClass}'
# 输出:Burstable

kubectl get pod qos-besteffort -o jsonpath='{.status.qosClass}'
# 输出:BestEffort

批量看全节点 Pod 的 QoS:

kubectl get pods -A -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,QOS:.status.qosClass,NODE:.spec.nodeName

三、驱逐顺序到底怎么排

节点触发驱逐阈值后,kubelet 按以下顺序选受害者:

  1. BestEffort 先死——它们没有任何资源保障。
  2. Burstable 其次——但内部还有排序:实际用量超出 request 越多的越先死。一个 request 内存 128Mi 却用了 500Mi 的,比只用了 150Mi 的先走。
  3. Guaranteed 最后——只有把所有 Burstable/BestEffort 都清完还不够,才会动它。

同 QoS 内部,统一按"超出 request 的量"从大到小排。注意:排序依据不是 limit ,而是实际用量相对 request 的超出值

四、kubelet 驱逐机制:软驱逐 vs 硬驱逐

kubelet 在节点层面做驱逐,目标是把节点从压力中拉回来。两种触发方式:

硬驱逐(eviction-hard):条件一满足立刻驱逐,无宽限期。默认值:

--eviction-hard=memory.available<100Mi,nodefs.available<10%,imagefs.available<15%,nodefs.inodesFree<5%

软驱逐(eviction-soft):条件满足后,还要持续超过 --eviction-soft-grace-period 才动手,给业务一个缓冲。例如:

--eviction-soft=memory.available<500Mi
--eviction-soft-grace-period=memory.available=1m30s
--eviction-max-pod-grace-period=30

此外还有压力类型区分:

  • nodefs 压力:容器日志、emptyDir 把节点根盘写满,触发驱逐。
  • imagefs 压力:镜像和容器可写层占满磁盘,触发镜像回收+驱逐。
  • pid 压力:进程数打满,kubelet 也会驱逐。

查看节点当前驱逐配置(kubelet 配置多在 /var/lib/kubelet/config.yaml):

cat /var/lib/kubelet/config.yaml | grep -i eviction

五、核心区分:kubelet 驱逐 vs 内核 OOM Killer

面试必问,两套机制完全不同,别混:

维度 kubelet 驱逐(Evicted) cgroup OOM Killer(OOMKilled)
触发依据 节点层面可用资源 容器内存 limit
触发者 kubelet 进程 内核内存管理子系统
Pod 状态 Evicted OOMKilled
后续行为 被删,可重新调度到别的节点 容器内进程被杀,Pod 原地重启
典型原因 节点内存/磁盘整体紧张 单个容器突破自己的 limit

排查命令

kubectl get pod -o jsonpath='{.status.lastState.terminated.reason}'
# 输出 OOMKilled 表示被内核杀;输出空且 status.phase=Failed 且 reason=Evicted 表示被 kubelet 赶走

kubectl describe pod <pod> | grep -A5 -i "last state\|reason"

oom_score_adj 映射(OOM 打分)

内核决定杀谁看 oom_score_adj ,分数越高越先被杀:

  • Guaranteed-997(几乎不会被 OOM)
  • BestEffort1000(最先被杀)
  • Burstable1000 - (1000 * memoryRequest / memoryCapacity),约落在 2 ~ 999

查看某容器进程的 OOM 权重:

# 找到容器主进程 PID
PID=$(docker inspect -f '{{.State.Pid}}' <container_id>)
cat /proc/$PID/oom_score_adj
# 例如 Burstable 可能输出 899

oom_score = oom_score_adj + 进程自身内存占用得分,内核挑总分最高的杀。

六、PriorityClass 与抢占

PriorityClass 用来给 Pod 排调度和抢占的优先级:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
preemptionPolicy: PreemptLowerPriority   # 或 Never
globalDefault: false
description: "核心服务专用"
  • value 越大越优先调度、越容易抢占低优先级 Pod。
  • preemptionPolicy: PreemptLowerPriority:调度时把低优先级 Pod 挤走。
  • globalDefault: true:没声明 priorityClassName 的 Pod 自动套用。

面试高频陷阱:PriorityClass 只影响调度和抢占,完全不影响 kubelet 驱逐顺序!一个 PriorityClass 拉满的 BestEffort Pod,节点内存吃紧时照样第一个被驱逐。想保命,QoS 才是正解。

七、生产保核心服务的组合拳

单靠 QoS 还不够,生产上这样组合:

  1. QoS 打底:核心服务一律 request == limit ,锁 Guaranteed 。
  2. PriorityClass 兜底:配高 value + preemptionPolicy: PreemptLowerPriority ,调度阶段就占住好位置。
  3. PDB 防雪崩:保证驱逐/升级时至少有多少个副本活着。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: core-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: order-service
  1. 独占节点池:核心服务打污点容忍 + nodeSelector ,和 BestEffort 离线任务物理隔离。
  2. 系统预留:kubelet 配 kube-reservedsystem-reserved,并让 eviction-hard 阈值高于预留值,给系统进程留缓冲,避免把节点自己拖死。
# /var/lib/kubelet/config.yaml 片段
kubeReserved:
  cpu: "500m"
  memory: "1Gi"
systemReserved:
  cpu: "500m"
  memory: "1Gi"
evictionHard:
  memory.available: "1.5Gi"
  nodefs.available: "10%"

八、踩坑实录

坑 1:某次线上核心服务用了 HPA ,副本数靠 request 算,但当时没设 limit 。结果 QoS 是 Burstable ,节点抖动时被驱逐,HPA 疯狂扩容反而加剧压力。修复:补上 limit == request 升 Guaranteed。

坑 2:以为 PriorityClass 高就高枕无忧,结果 OOM 时被内核杀。原因:Pod 是 Burstable ,oom_score_adj 高达 900,节点内存告急时第一个进内核黑名单。教训:priorityClassName 救不了 OOM。

坑 3eviction-hard 阈值设成了 memory.available<50Mi,但 kube-reserved 只留了 200Mi ,节点频繁把 kubelet 自己逼到不稳。正确做法:硬阈值必须明显大于系统预留。

九、面试追问清单(Q&A)

  1. Q:request 和 limit 都不设会怎样?
    A:Pod 落入 BestEffort ,节点任何资源压力下都最先被驱逐,且 OOM 时 oom_score_adj=1000 最先死。
  2. Q:只设 limit 不设 request ,属于哪类?
    A:Guaranteed 。K8s 自动把 request 补成等于 limit。
  3. Q:只设 request 不设 limit 呢?
    A:Burstable 。它不满足"所有容器 request==limit",但至少有一个容器设了 request。
  4. Q:QoS 会影响调度吗?
    A:不会。调度只认资源 request 和节点可分配量;QoS 只决定"出事时谁先死"。
  5. Q:kubelet 驱逐和 OOM Killer 区别?
    A:前者看节点整体可用量,Pod 标 Evicted 可重调度;后者看容器 limit ,进程被杀,Pod 标 OOMKilled 原地重启。
  6. Q:PriorityClass 很高的 Pod 会被 OOM 杀吗?
    A:会。PriorityClass 不参与 kubelet 驱逐和 OOM 打分,只有 Guaranteed 才能把 oom_score_adj 压到 -997。
  7. Q:同是 Burstable ,谁先被驱逐?
    A:实际内存用量超出 request 越多的越先走。
  8. Q:Guaranteed 会被驱逐吗?
    A:极端情况下会。当所有 BestEffort/Burstable 清完仍不达标,kubelet 才会动 Guaranteed ;OOM 时同理,它只是最后才轮到。
  9. Q:怎么验证 Pod 是哪种死法?
    A:kubectl get pod -o jsonpath='{.status.lastState.terminated.reason}' 看 OOMKilled;kubectl describe 看 Evicted 原因。
  10. Q:软驱逐的好处是什么?
    A:给短暂尖峰留 grace-period 缓冲,避免瞬时抖动就误杀业务。

十、生产排查 Checklist(照着走)

节点告警后,按这个编号顺序定位,十分钟厘清是哪种死法:

  1. 看 Pod 状态kubectl get pod -A -o wide | grep -i "evict\|oom" 先分出 Evicted 还是 OOMKilled 。
  2. 看死因字段kubectl get pod <pod> -o jsonpath='{.status.lastState.terminated.reason}' ,输出 OOMKilled 是内核杀,空且 phase=Failed 多半是 kubelet 驱逐。
  3. 看 QoSkubectl get pod <pod> -o jsonpath='{.status.qosClass}' ,确认是不是 BestEffort 在裸奔。
  4. 看节点资源kubectl top nodekubectl describe node <node> | grep -i "allocatable\|pressure" ,确认是否 MemoryPressure / DiskPressure。
  5. 看驱逐事件kubectl describe node <node> 底部 Events 会写明 Evicting pod 及原因阈值。
  6. 进节点看 OOM 权重cat /proc/$(docker inspect -f '{{.State.Pid}}' <cid>)/oom_score_adj ,对照三类映射核实。
  7. 查 kubelet 配置cat /var/lib/kubelet/config.yaml | grep -i eviction 确认硬/软阈值是否过紧。
  8. 定方案:若为核心服务,补 request==limit 升 Guaranteed ,加 PriorityClass、PDB、独占池。

十一、真实案例拆解

案例一:大促当晚核心网关集体失联

现象:大促流量上来,某节点内存到 92%,订单网关(当时 Burstable ,没设 limit )全部 Evicted ,而旁边的日志采集 BestEffort 反而还在。根因:网关只设了 request 没设 limit ,一旦超用就成了"超 request 最多"的那批,被 kubelet 优先选中。修复:把 gw 的 limit 设成与 request 相等,升级为 Guaranteed ,并配 high-priority PriorityClass 与 PDB(minAvailable=2)。此后同类压力只杀离线任务。

案例二:Pod 反复重启但节点没事

现象:Pod 状态 CrashLoopBackOffdescribe 显示 OOMKilled,但节点内存还很充足。这是典型 cgroup OOM——容器自身 limit 太小,单进程突破 limit 就被内核干掉,与节点整体无关。处理:调大该容器 memory.limit ,同时用上面 Checklist 第 6 步确认 oom_score_adj 与 QoS 匹配。

案例三:PriorityClass 拉满仍被驱逐

这是面试最常挖的坑。一个 Pod 配了 value=1000000 的 PriorityClass ,却没设 request/limit ,QoS 是 BestEffort 。节点抖动时被第一个驱逐,运维以为"高优先级怎么会先死"。真相:PriorityClass 只管调度抢占,完全不参与 kubelet 驱逐排序,BestEffort 在任何节点压力下都排第一。结论再次印证——保命靠 QoS ,不靠 PriorityClass。

十二、常见 FAQ 速查

  • Q:emptyDir 算 nodefs 压力吗? 算。emptyDir 默认用节点根盘,写爆触发 nodefs 驱逐。
  • Q:镜像过多会触发哪种? imagefs 压力,kubelet 先回收无用镜像,不够才驱逐。
  • Q:Guaranteed 一定不被 OOM 吗? 几乎不会,但节点整体 OOM 且别无选择时理论上仍可能,只是 oom_score_adj=-997 把概率压到极低。
  • Q:OOM 后 Pod 会去别的节点吗? 不会,OOMKilled 是原地重启;只有 Evicted 才被删并重调度。
  • Q:怎么让核心服务永不进驱逐名单? 没有绝对方案,但 Guaranteed + 独占池 + 系统预留 + 高 PriorityClass + PDB 已是工程上最稳的组合。

十三、节点可分配量与驱逐阈值的换算

理解"为什么阈值要高于系统预留",得会算节点可分配资源。公式:

allocatable = capacity - kube-reserved - system-reserved - eviction-hard 阈值

举例:节点 32Gi 内存,kube-reserved=1Gi ,system-reserved=1Gi ,eviction-hard=memory.available<1.5Gi 。则用户 Pod 最多能用约 32 - 1 - 1 - 1.5 = 28.5Gi 。当实际可用跌破 1.5Gi ,kubelet 立即驱逐。注意:驱逐看的是 available(capacity 减去已用),不是 allocatable ,所以预留和阈值都必须留出余量,否则 kubelet 自身和 ssh 等系统进程会在内存告急时一起被内核盯上。

一个实用技巧:把 eviction-hard 设成 kube-reserved + system-reserved 之和再往上加一点,例如预留共 2Gi ,则硬阈值设 2.5Gi ,保证系统层永远有缓冲。

十四、一次完整实战演练

假设你是值班,收到 MemoryPressure 告警,按下面流程跑一遍:

  1. kubectl get nodes 看到 node-a 状态 MemoryPressure
  2. kubectl describe node node-a 底部 Events 出现 Evicting pod qos-besteffort/clean-job because node is under memory pressure ,确认是 kubelet 驱逐。
  3. kubectl get pod -n default -o custom-columns=POD:.metadata.name,QOS:.status.qosClass,NODE:.spec.nodeName | grep node-a ,发现被赶走的正是两个 BestEffort 和一个超用严重的 Burstable 。
  4. 核心订单 Pod 是 Guaranteed ,侥幸存活,但 oom_score_adj 排查显示它是 -997 ,确认内核层也被保护。
  5. 你意识到根因是离线 clean-job 没设任何 limit ,平时占用忽高忽低。于是给离线任务补 requests 并单独调度到 batch 节点池,与核心服务彻底隔离。
  6. 同时把核心服务 eviction-hard 之外的系统预留上调,避免下次抖动连 kubelet 都受影响。

跑完这套,你不仅平息了告警,还把"随机驱逐"变成了可预期的行为。

十五、六大误区对照表

误区 真相
设了 limit 没设 request 是 Burstable 错,K8s 自动补 request=limit ,属 Guaranteed
PriorityClass 高就不会被驱逐 错,它只管调度抢占,不参与驱逐与 OOM 排序
QoS 影响调度 错,调度只看 request 与 allocatable
OOMKilled 之后 Pod 会去别的节点 错,OOM 是原地重启;只有 Evicted 才重调度
节点内存够就不会有问题 错,容器自身突破 limit 照样被内核 OOM
驱逐和 OOM 是一回事 错,触发依据、触发者、后续行为全不同

把这张表背下来,面试官基本挖不出坑。

十六、监控与告警建议

光懂原理不够,得让"驱逐"可被观测:

  • 在 Prometheus 里监控 kube_node_status_condition{condition="MemoryPressure"}DiskPressure ,一置位就告警。
  • kube_pod_status_reason{reason="Evicted"} 统计被驱逐 Pod 数量,突增即异常。
  • 容器 OOM 用 kube_pod_container_status_restarts_total 突增结合 reason=OOMKilled 定位。
  • 给核心服务加 SLO 告警,QoS 出问题时业务指标会先抖,比节点阈值更早预警。

把这些接进 Grafana 面板,节点一有压力你比 kubelet 还先知道。

案例四:磁盘满比内存更隐蔽

某次节点 DiskPressure 告警,但内存很闲。排查发现是某应用疯狂写日志到 emptyDir ,把 nodefs 撑到 95%,kubelet 按 nodefs.available<10% 硬阈值驱逐了该节点上所有 BestEffort 。教训:磁盘压力同样走 QoS 排序,且日志型 Pod 极易中招。应对:给日志类容器设 ephemeral-storage 的 request/limit ,并配日志轮转,别让 emptyDir 无上限膨胀。

Q:ephemeral-storage 也参与 QoS 吗?
A:不参与 QoS 判定,QoS 只看 CPU 和内存。但它有独立的驱逐阈值,磁盘超用照样被赶。

Q:同节点上 Guaranteed 之间还会互相杀吗?
A:通常不会。只有整体 OOM 且别无选择才可能,而 Guaranteed 的 oom_score_adj=-997 已让其几乎免疫。

十七、总结

节点压力下的"随机驱逐"其实有铁律:QoS 决定生死顺序——BestEffort 先死,Burstable 按超 request 量排,Guaranteed 最后。再记住两条生命线:kubelet 驱逐(Evicted,看节点)和内核 OOM(OOMKilled,看 limit )是两码事;PriorityClass 管调度抢占、管不了驱逐和 OOM 。生产保核心,记住组合拳:Guaranteed 打底 + 高 PriorityClass + PDB + 独占池 + 系统预留。把这套讲清楚,面试基本稳了。最后送一句口诀:QoS 定生死,PriorityClass 管排队,PDB 防雪崩,预留保系统——四个维度各管一段,组合起来才叫生产级稳定性。下次再遇到"核心服务先死",你就能一眼定位是 QoS 没配好,而不是甩锅给玄学。




上一篇:泄漏密钥扫描正则拆解:一条规则覆盖 200+ 字段,Burp 实战命中 132 条
下一篇:具身智能核心概念拆解:VLM、VLA、世界模型怎么串起机器人进化链
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-12 04:22 , Processed in 0.337672 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表