找回密码
立即注册
搜索
发回帖 发新帖

5273

积分

0

好友

681

主题
发表于 23 分钟前 | 查看: 1| 回复: 0

一、痛点开头:核心服务半夜被“杀”,凶手竟是低优 Pod

去年双十一大促前的压测之夜,我们的核心交易网关 DemoApp-gateway 突然在凌晨被 OOMKilled 驱逐,业务监控一片飘红。第一反应是“节点内存泄漏了?硬件故障?”,于是连夜登录 node-01、node-02 排查,翻 dmesg、查 kubelet 日志,折腾两个小时毫无头绪。

直到有人执行了一条:

kubectl get events

真相才浮出水面:同一台节点上跑着一个离线批处理任务 your-app-batch,它把节点内存吃满,调度器为了给一个“高优先级”的 Pod 腾地方,直接把我们的核心网关给抢占(preemption)了。而那个高优 Pod,本不该比交易网关重要。

更扎心的是,复盘会上老板问:“你们不是配了 PriorityClass 吗?为什么没挡住?”这一问,直接把现场所有人问住了。后来发现,我们配的 PriorityClass 不仅没保护核心服务,反而因为 globalDefault 配置失误,让所有 Pod 都变成了高优先级,抢占逻辑彻底乱套。

这事儿在面试里也极高频:面试官一句“说说你们用 PriorityClass 解决过什么问题”,背后想听的就是你对抢占机制、QoS、PDB、系统关键类这套组合拳的理解深度。

二、一句话点透原理:PriorityClass 管“调度抢占”,QoS 管“节点驱逐”

先把最容易被混淆的点讲清楚:

PriorityClass 定义的是 Pod 的优先级数值(priority),数值越大越优先。当集群资源不足、高优 Pod 无法调度时,调度器会触发抢占:驱逐(删除)节点上优先级更低的 Pod,把资源腾出来给高优 Pod 落地。

但这里有个核心误区,面试必问:PriorityClass 的抢占,和节点压力驱逐(Node-pressure eviction)完全是两回事,而且顺序依据不同。

  • 抢占 Preemption:由 kube-scheduler 在“调度阶段”触发,依据的是 PriorityClass 的 priority 数值。它是“为了新 Pod 进来而赶走旧 Pod”。
  • 节点压力驱逐 Node-pressure eviction:由 kubelet 在“节点资源紧张”时触发,依据的是 QoS 等级(Guaranteed > Burstable > BestEffort)。它是“节点快挂了,kubelet 主动杀进程保命”。

一句话记忆:抢占看 priority 数值,节点驱逐看 QoS 档次。很多人以为“配了高 PriorityClass 就不会被驱逐”,这是错的——节点内存爆了,kubelet 照样按 QoS 杀你,PriorityClass 管不着。

再补一句调度器内部逻辑,面试加分:当高优 Pod 因资源不足调度失败时,调度器会进入“抢占候选”计算。它先找出所有“去掉低优 Pod 后能让高优 Pod 落地的节点”,然后在每个节点上选出一个最小牺牲集合(minimal set):即刚好腾出足够资源、且优先级最低的那批 Pod。

选好后,调度器会先把高优 Pod 绑定到该节点,并在其 status.nominatedNodeName 上记录“提名节点”,随后异步删除牺牲者。注意:提名不等于立即运行,若牺牲者删除慢、资源没及时释放,高优 Pod 仍可能短暂 Pending。这也是为什么有时你看到高优 Pod 显示在一个节点上却没起来——它在等抢占完成。

三、实战步骤:从定义到排障的完整命令

1. 定义 PriorityClass

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "核心交易链路高优先级类"
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 100
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "离线批处理低优先级类"

2. Pod 引用 priorityClassName

apiVersion: v1
kind: Pod
metadata:
  name: demoapp-gateway
spec:
  priorityClassName: high-priority
  containers:
  - name: gateway
    image: registry.example.com/your-app/gateway:v1.2.0
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"

3. 排查抢占事件的命令

查看全集群事件,按时间排序找抢占记录:

kubectl get events --sort-by=.lastTimestamp -A | grep -i preempt

查看某个 Pod 实际生效的 priority 数值与抢占策略:

kubectl get pod demoapp-gateway -n prod -o jsonpath='{.spec.priority}{" "}{.spec.preemptionPolicy}{"\n"}'

查看系统内置的关键 PriorityClass。这些是 K8s 自带的,别去覆盖:

kubectl get priorityclass
# 你会看到 system-cluster-critical (value: 2000000000)
# 以及 system-node-critical   (value: 2000001000)

查看某个 PriorityClass 的详细定义,确认 value 与 preemptionPolicy:

kubectl describe priorityclass high-priority

筛选当前集群里使用了指定优先级类的 Pod,验证配置是否真的生效:

kubectl get pods -A -o jsonpath='{range .items[?(@.spec.priorityClassName=="high-priority")]}{.metadata.namespace}{"/"}{.metadata.name}{"\n"}{end}'

查看某个节点上被提名抢占(nominated)的痕迹,定位“谁赶走了谁”:

kubectl get events -A --field-selector reason=Preempt | grep -i "preempt"

4. preemptionPolicy: Never 是什么

preemptionPolicy 默认为 PreemptLowerPriority,即允许抢占低优 Pod。设为 Never 时,该 Pod 仍保留自己的高 priority 数值,因此调度排序仍然占优,但绝不会主动抢占别人。

它只能“见缝插针”地调度到空闲资源上,若资源不足就一直 Pending。常用于“希望优先调度但不愿打扰他人”的场景,比如重要的批处理作业。

四、生产环境注意点:这 7 条踩过才懂

  1. 抢占不是无差别跨类。调度器只抢占优先级严格低于自己的 Pod,同优先级之间不会互相抢占。抢占还会尽量“最小代价”——优先杀能腾出最少资源满足需求的低优 Pod。

  2. value 取值范围与系统关键类的约定。用户自定义 PriorityClass 的 value 建议保持在 0 ~ 10亿(1,000,000,000) 之间,绝对不要超过系统关键类:system-cluster-critical=2000000000,system-node-critical=2000001000。一旦你的 value 比系统关键类还高,核心组件(如 kube-proxy、etcd 相关 Pod)反而可能被你的业务 Pod 抢占,后果是集群失控。

  3. PriorityClass 与 PodDisruptionBudget(PDB)的关系——这是高频坑。PDB 只约束自愿中断(voluntary disruption),比如你执行 kubectl drain、节点滚动升级、Deployment 缩容。但抢占(preemption)和节点压力驱逐(eviction)属于非自愿中断,PDB 完全挡不住。所以“我配了 PDB 核心服务就不会被抢占”是错误认知。

  4. PriorityClass 与 QoS 三档在节点驱逐上的区别。节点内存、磁盘压力上来时,kubelet 按 QoS 杀进程:先杀 BestEffort,再杀 Burstable,Guaranteed 最后。这个顺序和你的 PriorityClass 数值无关。想真正“防节点驱逐”,得把核心 Pod 配成 Guaranteed(requests == limits),而不是只配高 PriorityClass。

  5. globalDefault 的陷阱。若把某个 PriorityClass 设了 globalDefault: true,所有未显式指定 priorityClassName 的 Pod 都会继承它。曾有人把高优类设成 globalDefault,结果集群所有 Pod 都变成高优先级,抢占机制“全员平等”等于失效,节点压力一大照样乱杀。正确做法:只对明确需要的 Pod 打 priorityClassName,globalDefault 保持 false,或只给一个中低默认值。

  6. 抢占只在单个节点内发生,不是全局重排。调度器选中的牺牲者,一定是在“高优 Pod 即将落地的那台节点”上运行的低优 Pod;它不会为了腾资源而把别的 Pod 漂移到其他节点。所以如果你看到某节点频繁抢占、别的节点却很空,说明调度拓扑本身有问题,比如亲和性、资源碎片、taint 等。PriorityClass 救不了这种情况,要从调度约束入手。另外,PodPriority 自 Kubernetes 1.14 起已是 GA 能力,无需再开 FeatureGate,直接定义 PriorityClass 即可使用。

  7. 生产建议做三级分层,而不是二选一。经验做法是:核心链路(网关、交易)设 value 在百万级且配成 Guaranteed;普通在线业务用默认值或中低 value,例如 1 万,并配 Burstable;离线批处理、CI 任务设极低 value,例如 100,甚至 preemptionPolicy: Never。这样资源紧张时,离线任务最先被抢,核心服务靠 Guaranteed 加高 priority 双保险,既保调度也抗驱逐,整体韧性最稳。

五、踩坑实录:4 个真实翻车现场

坑 1:误设 globalDefault 让全部 Pod 高优先级。 某次为“默认保护业务”,把 high-priority 设为 globalDefault: true。结果离线任务、调试 Pod 全变高优,节点一紧张,高优之间无法互相抢占,kubelet 只能按 QoS 硬杀,核心服务照样被波及。修复:globalDefault 改回 false,按命名空间显式打标。

坑 2:value 与系统关键类冲突。 一个团队把自研调度器辅助 Pod 的 value 设成 2000000000,和系统关键类撞值。虽然没直接抢占核心组件,但同优先级不互抢的语义被打破,排障时一度怀疑是 kubelet bug。教训:自定义 value 永远留在 10 亿以下。

坑 3:以为 PriorityClass 能防节点驱逐。 核心服务只配了高 PriorityClass,但 QoS 是 Burstable,也就是 limits 大于 requests。某次节点内存爆满,kubelet 按 QoS 把它和别的中优 Pod 一起杀了。结论:防驱逐靠 Guaranteed,PriorityClass 只管“调度抢占”,两件事别混为一谈。

坑 4:preemptionPolicy 误解导致预期外不抢占。 有人给高优 Pod 设了 preemptionPolicy: Never,以为“高优先级”就稳了,结果资源紧张时它一直 Pending,因为从不抢占别人、只等空位。调试半天才发现是策略语义理解错——Never 是“不主动赶人”,不是“自己永不等待”。

六、总结与面试 Checklist

PriorityClass 是 Kubernetes 解决“资源争抢”的核心手段,但它只管调度阶段的抢占排序,管不了节点压力驱逐(看 QoS),也管不了自愿中断约束(看 PDB)。三者各司其职,面试能讲清这条边界,基本就过关了。

最后给一个实战小技巧:面试或复盘时,如果对方让你“现场排一次抢占故障”,标准动作就是先执行 kubectl get events -A | grep -i preempt 拿到被抢占的 Pod 列表,再用 kubectl get pod <pod> -o jsonpath='{.spec.priority}' 对比双方 priority 数值,最后用 kubectl describe node <node> 看 Allocated/Free 资源,确认是否真的资源见底。这套三步法能让你在白板上显得极其老练,也是线上真正救命的流程。

面试高频追问 Checklist,建议背熟:

  • 抢占完整流程:高优 Pod 调度失败 → 调度器找牺牲者(低 priority) → 模拟删除腾资源 → 确认能调度 → 真实删除低优 Pod → 高优落地。
  • 抢占与 QoS 的区别:抢占看 priority 数值,由调度器触发;节点驱逐看 QoS 档次,由 kubelet 触发,二者独立。
  • 与 PDB 的关系:PDB 只挡自愿中断,如 drain、升级;挡不住抢占和节点驱逐。
  • 系统关键类 value 约定:system-cluster-critical=2000000000;system-node-critical=2000001000;自定义 value 建议小于 10 亿。
  • globalDefault 陷阱:设 true 会让所有未指定类 Pod 继承,容易全员高优导致抢占失效,默认应 false。
  • preemptionPolicy: Never 语义:保留高 priority 排序优势,但绝不主动抢占他人,资源不足时只能 Pending。
  • 防节点驱逐正解:把核心 Pod 配成 Guaranteed,即 requests 等于 limits,而非只依赖 PriorityClass。

附:PriorityClass 高频参数速记

参数 默认值 作用
value 无默认,必填 Pod 优先级数值,越大越优先
globalDefault false 是否作为未指定 priorityClassName 的默认类
preemptionPolicy PreemptLowerPriority 是否允许抢占低优 Pod
description 无 人类可读描述

补充说明:文中 your-app-batch、DemoApp-gateway 等项目名保留了原文写法,便于复现场景。若你实际集群使用不同名称,命令中的资源名需要对应替换。Kubernetes 不同版本对 PriorityClass 的展示与事件输出可能有细微差异,本文命令以较新的 scheduling.k8s.io/v1 API 为准。




上一篇:新能源重卡渗透率已超50%,为何长途电动化还不到1%?
下一篇:全栈必备的 14 个 AI 技能:前端、后端、数据库与 CI/CD
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

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

GMT+8, 2026-11-11 16:36 , Processed in 0.076521 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

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