深色模式
调度优先级与抢占 Preemption
摘要:本文面向生产 SRE / 平台工程师,解释 Pod priority 与 PriorityClass 如何影响调度队列排序,以及当高优先级 Pod 无法调度时,调度器如何通过抢占(驱逐低优先级 Pod)腾出空间。覆盖非抢占优先级、PDB 的 best-effort 限制、跨节点反亲和边界与安全防护。适用 Kubernetes v1.28+(优先级与抢占自 v1.14 稳定,非抢占自 v1.24 稳定)。
适用版本与前提
- Kubernetes:v1.28+(priority/preemption 稳定自 v1.14;
preemptionPolicy: Never稳定自 v1.24) - 工具:
kubectl、kubectl get priorityclass - 前提:集群启用了 PodPriority(默认开启),具备创建 PriorityClass 的 RBAC
背景与问题
当集群资源紧张、多个 Pod 同时 Pending 时,谁先被调度?当关键控制面组件或核心业务需要落地,而节点上已被低优 Pod 占满时,是否要"让位"?Pod 优先级与抢占提供了一套机制:
- 优先级决定调度队列中 Pod 的排队顺序(高优先排)。
- 抢占在高优 Pod 找不到可行节点时,驱逐低优 Pod 为其腾地。
但若不加约束,任何用户都能创建最高优先级 Pod,把别人的 Pod 全部挤掉——因此抢占必须与 RBAC / ResourceQuota 配合。
核心概念
- PriorityClass:非命名空间对象,把名称映射到整数优先级
value(范围约 -2147483648 ~ 1000000000)。系统保留system-cluster-critical(值 2000000000) 与system-node-critical(值 2000001000)。[版本相关] 具体保留值随版本变化,v1.37 文档给出上述数值,请以目标版kubectl get priorityclass为准。 - globalDefault:设为
true时,未指定priorityClassName的 Pod 使用该值;全局只能有一个globalDefault: true。 - preemptionPolicy:
PreemptLowerPriority(默认,可抢占)或Never(仅优先排队,不抢占)。
架构与原理
抢占流程(默认 PostFilter 插件):对 Pending 的高优 Pod P,遍历节点,寻找"移除其上部分低于 P 优先级的 Pod 后,P 即可调度"的节点。驱逐 victims 后,P 的 status.nominatedNodeName 被设为该节点,调度器优先重试该节点。
PDB 是 best-effort,非保证
调度器在抢占时尽量遵守 PodDisruptionBudget,但如果没有"不违反 PDB"的 victim 组合,抢占仍会发生,低优 Pod 会被超出 PDB 允许数量地驱逐。不要依赖 PDB 作为抢占的硬护栏。
生产实践一:定义 PriorityClass
yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "关键业务 Pod,允许抢占低优先级负载。"1
2
3
4
5
6
7
2
3
4
5
6
7
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: critical-api
spec:
replicas: 2
selector:
matchLabels:
app: critical-api
template:
metadata:
labels:
app: critical-api
spec:
priorityClassName: high-priority
containers:
- name: api
image: api:1.0.01
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
生产实践二:非抢占优先级(数据科学场景)
yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "优先排队但不驱逐已有 Pod,适合批处理作业。"1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
yaml
spec:
priorityClassName: high-priority-nonpreempting1
2
2
preemptionPolicy: Never 的 Pod 会排在低优 Pod 之前,但不会抢占;它只在集群自然腾出资源时被调度,且自身仍可能被更高优先级 Pod 抢占。非常适合"想插队但不愿牺牲已有计算"的数据科学 / 批处理作业。
故障排查
bash
# 查看 PriorityClass
kubectl get priorityclass
# 查看 Pod 实际优先级与 nominatedNode
kubectl get pod <pod> -o custom-columns=NAME:.metadata.name,PRI:.spec.priority,NOM:.status.nominatedNodeName
# 抢占 / 驱逐事件
kubectl get events --field-selector reason=Preempting1
2
3
4
5
6
2
3
4
5
6
| 现象 | 根因 | 处理 |
|---|---|---|
| 高优 Pod 仍 Pending | 无节点可经抢占腾出(含 PDB/反亲和阻断) | 检查反亲和与 PDB,扩容 |
| 低优 Pod 被批量驱逐 | 高优抢占触发 | 预期行为,评估 PriorityClass 分配 |
nominatedNodeName 与实际不符 | 抢占后被更高优 Pod 抢走 | 预期,调度器会重选 |
边界与失败模式
- 跨节点反亲和不触发跨节点抢占:若 P 需落在节点 N,但 N 上可行的唯一方案是抢占另一区节点上的 Pod Q(因 zone 级反亲和),调度器不会跨节点抢占,P 可能永久 Pending。建议仅对"等于或更高优先级"的 Pod 使用 inter-pod affinity。
- 优雅终止造成时间窗:victim 有
terminationGracePeriodSeconds宽限,期间 P 不能立即落位。把低优 Pod 的宽限期设小可缩短窗口(权衡数据安全)。 - 不保证全量驱逐:若移除"少于全部"低优 Pod 即可满足,则只驱逐必要部分;但前提是"移除所有更低优 Pod 后 P 可行"的判断为是。
生产危险
不可信多租户集群中,恶意用户可创建最高优先级 PriorityClass 挤掉他人 Pod。必须用 ResourceQuota 限制各命名空间可使用的 priorityClassName,并通过 RBAC 限制谁可创建/引用高优 PriorityClass。
回滚与清理
bash
# 删除 PriorityClass(已引用的 Pod 不受影响,但禁止再创建引用它的新 Pod)
kubectl delete priorityclass high-priority
# 回滚工作负载
kubectl rollout undo deployment/critical-api1
2
3
4
2
3
4
安全与合规
- 用 ResourceQuota 的
priorityClassNames字段限制命名空间可用的最高优先级。 system-前缀的 PriorityClass 名被保留,用户不可创建;避免与系统关键组件抢占冲突。- 抢占会真实终止 Pod,属于破坏性操作;在共享集群变更 PriorityClass 前应先灰度并通知租户。
性能、容量与成本
- 优先级影响排队顺序而非过滤;大量高优 Pod 同时抢占会触发节点上批量驱逐,造成短时抖动。建议:核心服务高优 + 批处理非抢占高优 + 普通负载默认零优先级的分层模型。
- 非抢占优先级能在"不破坏已有计算"的前提下提升批处理吞吐,合理使用可降低重算成本。
替代方案与权衡
| 需求 | 推荐 | 不推荐 |
|---|---|---|
| 关键负载优先落地 | PriorityClass + 抢占 | 手动删 Pod |
| 插队但不破坏 | preemptionPolicy: Never | 强抢占 |
| 软资源保障 | ResourceQuota / 多队列 | 仅靠优先级 |
FAQ
Q:删除 PriorityClass 会影响已运行的 Pod 吗? A:不会。已引用该名称的 Pod 保持原优先级;但之后无法再创建引用它的新 Pod。
Q:globalDefault: true 会改已有 Pod 优先级吗? A:不会。它只作用于之后创建的、未显式指定 priorityClassName 的 Pod。