CKA备考练到Pod调度这块时,PriorityClass(优先级)这个知识点很容易被一带而过。很多人觉得它就是给Pod分个三六九等,考试时背个yaml就完事了。但根据2025年CKA新题的走向,PriorityClass不仅单独出题,还经常跟资源配额、节点亲和、驱逐策略组合在一起考,用来考查你对调度器完整决策链路的理解。从题目变化来看,光会创建PriorityClass已经不够了,你得真正理解它什么时候生效、怎么和抢占配合、优先级数值设成多少才合理,以及当系统报出NoPreemptionVictims这类事件时该怎么收场。
这篇文章不适合零基础从头看起,更适合已经了解Pod基本调度逻辑、正在搭CKA练习环境刷题的考生。我会从机制原理讲到实操验证,再到排错经验,全程用我在练习环境里实际踩过的坑来说话,尽量把这块内容一次讲透。
1. 为什么PriorityClass突然成了CKA热门考点
先说一个观察:近几年CKA题库迭代的速度明显加快,尤其是调度相关的题目,从“能创建资源”进化到了“能控制资源如何被调度”。PriorityClass在旧题库里确实不算主角,顶多是考你kubectl apply一个yaml,然后在Pod里指定priorityClassName。但2025年新题明显变了味道——它开始考察你对抢占机制的理解,甚至要求你在集群资源紧张的情况下,通过调整优先级来触发Pod驱逐。
1.1 调度器眼中“排序”这件事到底有多重要
Kubernetes调度器本质上是一个排队系统。集群里几百上千个Pending的Pod排队等着被调度到合适的节点上,调度器不可能随机乱挑,它要按某种规则排序。这个排序规则就是优先级。没有优先级时,所有Pod在调度队列里都是平起平坐;有了PriorityClass,高优先级的Pod就能插队。
但“插队”只是表面效果,内部实际发生过两件事:
- 调度队列排序:高优先级Pod进入调度队列后,排到更靠前的位置。
- 抢占式调度:如果队列前面的Pod找不到可用节点(比如资源不够),调度器不会干等着,而是直接尝试干掉节点上低优先级的Pod,把资源腾出来给高优先级Pod用。
CKA新题考的就是这个第二点。以前的题目只要你写出“优先级高的Pod会被先调度”这种理论答案就能拿分,现在它可能给你一个资源已经占满的节点,再提交一个高优先级Pod,问你接下来会发生什么,或者让你通过kubectl describe去排查为什么这个高优先级Pod还是Pending。
1.2 题目从“会写yaml”转向“会排错”
我备考时刷到一道模拟题,创建了一个PriorityClass并指定给一个Pod,但Pod始终Pending。用kubectl describe pod查看事件,发现了No preemption victims found for incoming pod这样的错误提示。当时第一反应是“优先级设得不够高”,后来仔细排查才发现,节点上的现存Pod全是静态Pod和DaemonSet托管的Pod,根本不参与抢占。
这就是新题出题的方向:不只是问你怎么做,而是给你一个已经做好的环境,让你去发现问题并修复。这跟你实际工作中维护生产集群的场景非常接近——线上Pod调度不上去,你总不能只看一眼yaml就交差,得会看事件、查调度器日志、分析节点资源。
1.3 PriorityClass在考试大纲里对应的知识点集群
从CKA官方大纲来看,PriorityClass归属于“调度、超卖与驱逐”这一大块。涉及的子知识点包括:
- 调度器如何根据优先级排序Pod
- PriorityClass的定义、修改和删除
globalDefault的影响范围- 抢占机制与
preemptionPolicy设置 - 与ResourceQuota、LimitRange的协作关系
- 节点资源压力下的驱逐顺序
这些知识点在练习环境里是可以串成一个完整实验的。下文我会从搭建环境开始,一步步带你完成从创建PriorityClass到触发抢占的完整链路。
2. PriorityClass的核心机制:优先级、抢占和调度器三者怎么配合
想练好这个实验,先得把底层机制嚼碎。PriorityClass看起来只是个简单的key-value配置,但它背后牵动着调度器、kubelet和API Server三方的协作。
2.1 PriorityClass资源的本质:一个全局的“价目表”
PriorityClass是一个集群级别的资源,不属于任何Namespace。它的yaml长这样:
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000000 globalDefault: false description: "用于生产环境核心业务的高优先级类" preemptionPolicy: PreemptLowerPriority几个关键字段逐一解释:
- value:取值范围1到1000000000(十亿),数值越大优先级越高。
- globalDefault:布尔值,标记这个PriorityClass是否是集群默认优先级。一个集群里最多只能有一个
globalDefault: true,如果创建了多个为true的,后创建的会被API Server拒绝掉。注意,当集群里不存在任何globalDefault为true的PriorityClass时,Pod如果不指定priorityClassName,优先级默认是0。 - preemptionPolicy:默认值是
PreemptLowerPriority,表示这个优先级支持抢占低优先级的Pod;改成Never则只提高调度队列位置,不触发抢占。
这里插一句,很多生产场景为了避免高优先级Pod把节点上的业务Pod全干掉,会把preemptionPolicy设为Never,只让优先级参与调度排序。这个细节在CKA新题里出过,我当时就差点写错。
2.2 调度器排队:priority只是排序规则之一
调度器为Pod打分排序时,优先级不是唯一标准,但它是第一层过滤器。有心人可以看一下调度器源码里PrioritySort这个插件,它实现了最基础的排序逻辑——把待调度的Pod按优先级从高到低排列,再一个个送入调度管线。
这里的重点是:排序发生在调度管线之前。高优先级Pod会被更早地尝试调度,但并不意味着它一定能被调度成功。如果集群没有任何节点能满足它的资源要求,它还是一样会Pending。优先级只能保证“优先尝试”,不能保证“一定成功”。
2.3 抢占:真正让优先级“硬起来”的机制
当高优先级Pod进入调度管线后,如果没有节点满足它的资源需求,调度器会尝试寻找一个节点,通过抢占该节点上低优先级Pod来腾出资源给高优先级Pod。抢占流程大致分四步:
- 选受害者:调度器在所有节点上找“部分Pod”可以被抢占后、剩余资源刚好满足高优先级Pod的节点。
- 模拟驱逐:选出受害者Pod后,调度器会先模拟一遍删除这些Pod后节点是否满足需求。
- 提前占坑:调度器会立刻在API Server里抢占这个节点的资源配额(通过
nodebinding.kubernetes.io/taint这类污点机制),防止其他Pod“截胡”。 - 真正驱逐:受害者Pod被优雅终止,高优先级Pod随后完成调度。
整个过程看起来比较粗暴,但设计上有一个关键底线:不会抢占比待调度Pod优先级更高的Pod。也就是说,如果节点上有同优先级或更高优先级的Pod,这些Pod不会成为受害者。这个规则,考试里经常拿来出判断题。
写到这里就不得不提No preemption victims found for incoming pod这个报错。它的完整含义是:高优先级Pod确实尝试抢占,但当前所有节点上找不到可以被抢占的低优先级Pod。常见原因有三个:
- 节点上的Pod都是同优先级或更高优先级
- 节点上的Pod全是DaemonSet、静态Pod这类un-daemon的Pod,调度器默认不抢占它们
- 抢占虽然找到了受害者,但模拟驱逐后发现资源仍然不够
遇到这个事件,你先别急着改PriorityClass的数值,应该先排查受害者的候选对象,判断是不是节点上根本没有低优先级Pod可选。
2.4 kubelet层面的驱逐:另一种“优先级”
这里还要区分清楚调度器的抢占和kubelet的驱逐。前者发生在调度阶段,解决的是“Pod还没跑起来、找不到位置”的问题;后者发生在节点运行阶段,解决的是“节点内存或磁盘资源耗尽、需要牺牲部分Pod保集群稳定”的问题。
当节点内存(或磁盘)触发驱逐阈值时,kubelet会按Pod的优先级排序,优先杀掉低优先级Pod。这个优先级也是来自Pod的.spec.priority字段,而该字段正是由Pod指定的PriorityClass换算出来的。所以你在kubectl get pod xxx -o yaml里看到的priority: 1000000,就是这么来的。
3. 练习环境准备:搭一套多节点集群来验证优先级行为
PriorityClass的很多行为,尤其是抢占,单节点minikube玩不出效果,因为抢占需要“另一个Pod占着资源不撒手”。我建议至少准备一个两节点的集群,你可以在任意一台Linux机器上用kubeadm现搭一套最简集群,或者用云厂商的托管集群都行。我自己的练习环境是:1台2C8G的控制节点,2台2C4G的工作节点,Kubernetes版本选的1.29.4稳定版。
3.1 准备两个不同优先级的PriorityClass
先把实验要用的PriorityClass一次性准备好。
# low-priority.yaml apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority value: 100 globalDefault: false description: "低优先级测试类" preemptionPolicy: PreemptLowerPriority # high-priority.yaml apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000 globalDefault: false description: "高优先级测试类,用于CKA练习" preemptionPolicy: PreemptLowerPriority数值差距拉开一个量级就够了,不要一上来就设成十万、百万级,虽然系统允许,但不利于观察行为差异。在练习环境里,100和1000的差值足够触发抢占。
创建并验证:
kubectl apply -f low-priority.yaml -f high-priority.yaml kubectl get priorityclasses正常能看到两个PriorityClass加上Kubernetes自带的system-cluster-critical和system-node-critical,一共四个。
3.2 如何正确查看Pod绑定后的优先级
创建完PriorityClass后,写一个引用它的Pod试试:
apiVersion: v1 kind: Pod metadata: name: test-high-priority spec: priorityClassName: high-priority containers: - name: nginx image: nginx:1.25应用后查看细节:
kubectl get pod test-high-priority -o yaml | grep -A2 'priority'你会在spec里看到priority: 1000和priorityClassName: high-priority两个字段。如果写的是describe命令,输出里也会直接显示PriorityClass名称和Priority数值。
3.3 globalDefault的认知盲区,别在这里翻车
CKA新题很喜欢把globalDefault作为一个陷阱点。我在一个练习群看到有同学问:“我创建了一个名称为high-priority的PriorityClass,设置了globalDefault为true,为什么集群里已有的Pod优先级还是0?”
原因其实很简单:globalDefault只在Pod创建时生效。已存在的Pod如果要变更优先级,只能重建,不能动态更新。另外,如果集群中本来就有globalDefault: true的Policy,你新创建的另一个globalDefault: true会被API Server直接拒绝。这道题我在模拟考试时做错过,后来自己在环境里试了两次才记住。
还有更隐蔽的一点:globalDefault为true的优先级作用于所有没有显式指定priorityClassName的Pod。但如果你同时存在globalDefault: true和Pod显式指定了某个PriorityClass,显式指定的优先级优先。这是个基本规则,但混在一起就容易出错。
4. 完整实操:创建一个被抢占的实验场景
概念说完了,进入正题。下面我在练习环境里从头走一遍“低优先级Pod占资源,高优先级Pod触发抢占”的完整实验。这套实验脚本,算是我备考CKA时觉得用途最大的一套素材。
4.1 用Deployment创造资源占用
首先创建一个能占住节点资源的低优先级Deployment,用来充当“被抢占者”。我这里用的是一个requests配置得很死板的容器镜像:
# resource-hungry.yaml apiVersion: apps/v1 kind: Deployment metadata: name: resource-hungry namespace: default spec: replicas: 2 selector: matchLabels: app: hungry template: metadata: labels: app: hungry spec: priorityClassName: low-priority containers: - name: stress image: polinux/stress command: ["stress"] args: ["--cpu", "2", "--timeout", "600s"] resources: requests: cpu: "2" memory: "4Gi"这里有两个细节需要留意:
- 必须写
requests:调度器只认Pod的requests,不认limits。你写limits再大,调度器也不关心,因为limits只是运行时限制。所以想要把节点的可分配资源占满,就要把requests写足。 - replicas数量要匹配节点资源:我这里的测试节点每台有4C8G,但这个Deployment创建在单节点上,请求2×2=4个CPU和2×4=8G内存,可以直接吞掉一个小节点的大部分资源。
我会人为指定Pod调度到某个特定节点,方便观察后续行为。操作方式是在Pod模板里加nodeSelector:
nodeSelector: kubernetes.io/hostname: worker-1如果不知道节点名,先跑一下kubectl get nodes --show-labels确认。
4.2 观察低优先级Pod的调度结果
应用Deployment后,确认Pod处于Running状态:
kubectl get pods -o wide你会看到两个resource-hungry-xxxx都调度到了worker-1节点。这个节点剩余的可分配资源应该被压得很低。你可以用kubectl describe node worker-1 | grep -A10 "Allocated resources"来确认。
这里再分享一个我实践时用的小技巧:直接在describe node里看Allocated resources下面的CPU Requests和Memory Requests。它会显示占用的百分比,如果接近100%,说明节点的资源确实已经被吃掉了。如果百分比低于预期,检查一下是否还有其他系统组件占用了,或者你的requests数字没有写对。
4.3 提交高优先级Pod,触发抢占
接下来创建要抢占的高优先级Pod:
# winner.yaml apiVersion: v1 kind: Pod metadata: name: winner spec: priorityClassName: high-priority nodeSelector: kubernetes.io/hostname: worker-1 containers: - name: nginx image: nginx:1.25 resources: requests: cpu: "1" memory: "1Gi"创建后立刻查看:
kubectl get pods -o wide正常情况下,你会看到resource-hungry有一个Pod被终止(Terminating或直接消失),而winner变成了Running。这就是抢占成功的效果。
让我解释一下为什么被杀的是一个Pod而不是全部:高优先级Pod需要1个CPU和1G内存,节点上只要挤掉一个低优先级Pod就能腾出2个CPU和4G内存,调度器不会多杀。它会选择最少“牺牲”的那个Pod来驱逐。
4.4 用事件和历史记录复盘整个抢占过程
如果你想知道到底发生了什么,不要只盯get pods。要看Events:
kubectl get events --sort-by=.lastTimestamp | tail -30 kubectl describe pod winnerdescribe pod里会有类似这样的事件记录:
Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Preempting 3s default-scheduler Preempting other pods based on priority Normal Scheduled 3s default-scheduler Successfully assigned default/winner to worker-1 Normal Pulling 2s kubelet Pulling image "nginx:1.25" Normal Pulled 2s kubelet Successfully pulled image "nginx:1.25"这行Preempting other pods based on priority,就是调度器替你执行抢占动作的痕迹。实际生产环境中,这种事件一旦出现在核心业务Pod上,说明集群资源已经到了相当紧张的地步,该考虑扩容或给低优先级任务降配了。
4.5 注意:抢占不是瞬时的
很多人做这个实验会有一个错觉,以为提交高优先级Pod的瞬间,低优先级Pod就立刻消失。实际上抢占过程有几步要走:
- API Server记录Pod更新
- 调度器发现无法正常调度
- 调度器计算抢占方案并驱逐低优先级Pod
- kubelet异步处理Pod删除
整个过程通常在秒级完成,但如果节点负载高或者API Server响应慢,可能会持续几十秒。我在练习时偶尔会碰到winner卡在Pending状态长达一分钟的情况。排查方法还是看事件,确认它是在等待抢占还是真的无法调度。
5. 常见报错与排查链路:NoPreemptionVictims不是世界末日
备考进入刷题模式后,我遇到最多的报错就是No preemption victims found for incoming pod。这里单独开一节讲排查链路,因为这个问题非常典型,而且CKA的新题大概率会以它作为“故障场景”来出。
5.1 完整排查链路:从事件到根因的五步走
当Pod无法被调度时,我的排查顺序固定是:
第一步:看Pod状态和事件
kubectl describe pod <pod-name>关注Events区域有没有类似FailedScheduling或UnexpectedAdmissionError的信息。如果出现No preemption victims found for incoming pod,说明调度器已经尝试过抢占,但失败了。
第二步:看节点资源是否真的不足
kubectl describe node重点看Allocated resources这一栏。有时候你的高优先级Pod其实只要0.1个CPU就能跑,但节点确实一丁点资源都不剩了,抢占了也没用。
第三步:看目标节点上有没有可被抢占的对象
列出节点上所有Pod,筛掉DaemonSet和静态Pod:
kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=worker-1 kubectl get daemonsets --all-namespaces如果节点上的Pod全是kube-system里由DaemonSet管理的,调度器默认不会抢占这些Pod。原因很简单:它们是集群基础设施的一部分,优先级很高(system-cluster-critical或system-node-critical),普通PriorityClass设计的Priority值根本不会超过它们。
第四步:看是否因为preemptionPolicy: Never禁止了抢占
有些考生会把PriorityClass的preemptionPolicy误设成Never,然后又期望它触发抢占。这本身不矛盾,但如果你明确指定了preemptionPolicy: Never,调度器只会参与队列排序,绝不会去抢占任何Pod。这时候即使节点满负荷,高优先级Pod也只会老老实实Pending。
第五步:看调度器日志(高级排查)
如果上述步骤都查不出问题,直接去控制平面容器里看调度器日志:
kubectl logs -n kube-system $(kubectl get pods -n kube-system | grep kube-scheduler | awk '{print $1}') --tail=200CKA考试环境一般允许你查看控制平面Pod日志,这个排错动作本身就是题目考察点之一。
5.2 我踩过的一个典型案例
有一回我在练习环境里模拟一个“高优先级Pod抢占低优先级任务”的场景。无论我PriorityClass的value调到多高,Pod始终Pending。折腾了快40分钟,最后发现问题是节点被加了一个NoSchedule污点,而我创建的Pod没有指定对应的容忍度。
这个案例特别适合作为New Question的素材,因为它把污点容忍度和优先级两个知识点混在一起考。调度器是先检查污点容忍,再检查资源,最后才轮到抢占逻辑。你优先级再高,如果容忍度不匹配,连调度管线都挤不进去。
5.3 学会看优先级类型的Owner
还有一个小细节容易被忽略——system-cluster-critical和system-node-critical这两个内置PriorityClass是不能删除的,强行删除会导致API Server报错。考试时有道题让我确认某核心组件的PriorityClass,我一开始以为是自己创建的,后来kubectl get priorityclass system-cluster-critical -o yaml才发现它的owner是kube-apiserver自己。这提醒我们:生产环境里动内置PriorityClass要格外谨慎,先确认owner再操作。
6. 备考建议:PriorityClass怎么和场景结合出题
单会创建PriorityClass只是入门,CKA新题更看重的是“在一个完整场景里解决问题”的能力。所以我建议在练习环境里多做这几个方向。
6.1 组合实验一:PriorityClass + ResourceQuota
在某个Namespace里创建一个ResourceQuota,单位为requests.cpu,总量设置为1。然后在同一个Namespace里分别创建三个Deployment,优先级从低到高。观察高优先级Pod能否抢占配额内的资源,还是直接被Quota拒绝。
这个实验考察的核心是:PriorityClass决定调度顺序,ResourceQuota决定能不能创建。两者不是同一个层面的机制。很多CKA考生在这个地方犯迷糊。正确的理解是:ResourceQuota在你创建Pod的时候就会校验,跟调度器的优先级排序没有直接关系。即使PriorityClass再高,如果超出该Namespace的Quota,Pod也会直接被拒绝,连调度的机会都没有。
6.2 组合实验二:PriorityClass + PodDisruptionBudget
创建一个PDB,设置minAvailable: 1,关联到某个工作负载的Pod selector。然后触发节点排水(kubectl drain),看看PDB限制下的驱逐行为,会不会因为优先级而区别对待。
CKA新题里,PDB不会阻止抢占是一个容易踩的坑。PDB定义的是“自愿驱逐”时的最低可用Pod数,但PriorityClass的抢占是一种非自愿性操作,不遵循PDB限制。举个例子:你有个低优先级Deployment,3副本,PDB设置minAvailable为2。当一个高优先级Pod要抢占时,调度器可以把这3个副本全部干掉,只为了给高优先级Pod腾地方。PDB在这时候是不提供保护的。这个设计逻辑很多人觉得不合理,但Kubernetes就是这么工作的。
6.3 组合实验三:PriorityClass + Taint与Toleration
给一个节点添加dedicated=high-priority:NoSchedule污点,同时给高优先级Pod添加对应的容忍,低优先级Pod不添加容忍。观察调度结果和抢占行为。
这个实验可以帮你建立“容忍度是第一道门槛”的直觉。如果高优先级Pod没有容忍度,它永远无法被调度到这个节点上,无论PriorityClass有多高。
6.4 从考试角度看PriorityClass的“反向出题”
2025年CKA新题还有一个趋势:反向出题。不再问“这个PriorityClass会让Pod怎么样”,而是给你一个生产环境的现象描述,让你反推需要创建什么优先级的PriorityClass。
比如:
- 场景一:kube-system里有个核心组件经常被低优先级Pod挤掉资源,需要保证它永远优先调度。→ 解决方案:给该Pod关联
system-cluster-critical或更高优先级的自定义PriorityClass。 - 场景二:批处理任务拥进来,把节点资源全部吃光,导致有状态服务无法调度。→ 解决方案:给有状态服务设置高优先级PriorityClass。
- 场景三:测试环境的开发Pod优先级不能太高,但也不能被别人随便抢占,还想让它们参与调度排序。→ 解决方案:优先级中等,
preemptionPolicy: Never。
备考到最后,我会这样检验自己:不看答案,直接在白纸上把“高优先级Pod → 调度器排队 → 资源不足 → 查找受害者 → 模拟驱逐 → 真正的驱逐 → 调度成功”这条链路画一遍,每个环节说出涉及的组件和可能的失败原因。能完整画下来,说明这部分内容是真的掌握了。
7. 最后再分享两个小技巧
实验做到这里,该掌握的知识点都过了一遍。最后补充两个我自己练习中使用频率最高的操作习惯,对CKA备考和日常排查都有用。
第一,养成看Pod最终yaml里priority字段的习惯。很多人创建了PriorityClass却不知道Pod是否真的生效,直接在kubectl get pod -o yaml里搜priority两个词就能确认,比反复看describe更直观。
第二,练习结束后记得清理环境。如果创建了globalDefault为true的PriorityClass,清理时要格外小心,直接删除PriorityClass不会影响已经运行的Pod,但新创建的未指定优先级的Pod会回到0。练习环境无所谓,生产环境就必须先在低峰期操作,确认没有Pod依赖这个默认值再动手。
PriorityClass不是什么复杂机制,但它处在Pod调度的关键路径上,值得你在练习环境里多花半个小时把抢占行为、事件日志、边界条件都摸透。CKA考试里遇到相关题目,至少能保证不慌。