Most scheduling questions on the CNCF exams start with a full cluster and one stuck pod. You have drilled taints, tolerations, and affinity — the levers that decide where a pod is allowed to run. Pod priority and preemption answer a different question: when there is nowhere left to put a pod, whose pod gets bumped so a more important one can run? It is the one scheduling mechanism that can delete a running pod to make room, and both the CKA (Workloads & Scheduling) and CKAD candidates are expected to configure it and reason about the outcome.
This guide is the missing companion to the broader CKA workloads and scheduling guide, which covers the scheduler, affinity, taints, and requests. Here we go deep on the two objects the exam actually tests — the PriorityClass and the pod field that references it — plus the preemption behaviour that trips people up, and the commands to prove what happened.
The Problem Priority Solves
The scheduler places pods onto nodes that have enough free CPU and memory. When every node is full, a new pod sits in Pending — there is simply no room. That is fine for a batch job, but not for a critical control-plane add-on or a customer-facing service that must run even if it means displacing something less important.
Pod priority attaches an integer importance to each pod. It changes scheduler behaviour in two ways:
- Queue ordering — among pending pods, the scheduler considers higher-priority pods first.
- Preemption — if a high-priority pod cannot fit anywhere, the scheduler may evict lower-priority pods from a node to make space, then schedule the high-priority pod there.
Without priority, every pending pod is equal and a critical workload can be stuck behind a queue of batch jobs. With it, importance becomes explicit and the scheduler enforces it.
The PriorityClass Object
Priority is not set as a raw number on the pod. You define a PriorityClass — a cluster-scoped (non-namespaced) object — and pods reference it by name:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "For business-critical services that may preempt batch jobs."
The fields that matter on the exam:
| Field | Meaning |
|---|---|
value | Integer importance. Higher = more important. The scheduler compares these numbers. |
globalDefault | If true, pods with no priorityClassName get this priority. Only one PriorityClass in the cluster may set this. |
preemptionPolicy | PreemptLowerPriority (default) lets the pod evict others; Never means it jumps the queue but never preempts. |
description | Free text for humans. |
Because a PriorityClass is cluster-scoped, you do not put a namespace on it, and kubectl get priorityclass lists them cluster-wide.
The Reserved System Range
Two PriorityClasses ship built in and carry enormous values:
system-cluster-criticalsystem-node-critical(the higher of the two)
These are reserved for critical cluster and node add-ons (CoreDNS, the CNI, etc.) so they are never preempted by your workloads. Your PriorityClasses must use a value below 1,000,000,000 (one billion) — the range at and above one billion is reserved for these system classes. Picking a sane value like 1000 or 1000000 for app workloads keeps you well clear of the reserved band. A common exam distractor is a made-up huge value; the safe answer is a modest integer.
Assigning Priority to a Pod
Reference the class by name in the pod spec (or a Deployment’s pod template):
apiVersion: v1
kind: Pod
metadata:
name: payment-api
spec:
priorityClassName: high-priority
containers:
- name: app
image: nginx:1.27
When the pod is admitted, the Priority admission controller resolves priorityClassName into the integer spec.priority field. The resolution rules:
- If
priorityClassNameis set, the pod gets that class’svalue. - If it is empty and a
globalDefaultclass exists, the pod gets the default value. - If it is empty and there is no default, the pod gets priority 0.
Verify the resolved number quickly — a favourite exam check:
kubectl get pod payment-api -o jsonpath='{.spec.priority}{"\n"}'
How Preemption Actually Works
Say payment-api (priority 1,000,000) is Pending because every node is full of batch pods at priority 100. The scheduler runs its preemption logic:
- It looks for a node where evicting one or more lower-priority pods would let
payment-apifit. - It picks the node that requires the least disruptive set of victims (fewest pods, lowest priority).
- It deletes the victim pods, giving them their graceful termination period (
terminationGracePeriodSeconds). - It sets
status.nominatedNodeNameonpayment-apiso that node is earmarked for it. - Once space frees up,
payment-apischedules there.
A few behaviours the exam probes:
- Only lower-priority pods are candidates. A pod is never preempted to make room for one of equal or lower priority.
- PodDisruptionBudgets are respected on a best-effort basis. The scheduler tries to avoid violating a PDB, but if there is no other way to schedule the high-priority pod, it may preempt anyway. PDBs make preemption prefer other victims — they do not make a pod un-preemptible.
- Preemption is not a reservation. After victims terminate, the scheduler still has to place the pending pod;
nominatedNodeNamesignals intent, but scheduling is re-evaluated. - Cross-node: preemption only helps if evicting pods on some single node lets the pending pod fit there. If the pod’s requests exceed any node’s total capacity, preemption cannot help — it stays
Pending.
preemptionPolicy: Never
Set preemptionPolicy: Never on a PriorityClass and its pods still jump ahead in the scheduling queue by priority, but they will never evict running pods. This suits work that is important enough to schedule first when capacity appears, but not important enough to justify killing someone else’s pod — data-science or large batch jobs are the textbook case.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
Priority/Preemption vs QoS Eviction — Don’t Confuse Them
This is the single most common conceptual mix-up, and the exam rewards keeping the two mechanisms straight:
| Priority & Preemption | QoS-based eviction | |
|---|---|---|
| Who acts | The scheduler (control plane) | The kubelet (on the node) |
| When | A pending pod cannot be scheduled (cluster is full) | A node is under resource pressure (out of memory/disk) |
| What it does | Deletes lower-priority pods to place a pending one | Evicts pods to reclaim node resources |
| Primary factor | Pod priority value | QoS class (BestEffort → Burstable → Guaranteed), then priority |
In short: preemption is a scheduling-time decision about a pending pod; QoS eviction is a runtime decision by the kubelet when a node is starving. They can interact — under node memory pressure the kubelet evicts BestEffort pods first, then Burstable, and uses priority as a tiebreaker — but they are different subsystems firing at different times. The requests, limits, and QoS guide covers the eviction side; this page is the scheduler side. If you see “node ran out of memory and killed a pod,” that is QoS/kubelet eviction, not preemption.
Observing It on the Cluster
The workflow you should be able to execute under the clock:
# 1. Create a PriorityClass
kubectl apply -f high-priority.yaml
kubectl get priorityclass # cluster-scoped list
# 2. Assign it (Deployment template or pod spec) and confirm the resolved number
kubectl get pod payment-api -o jsonpath='{.spec.priority}{"\n"}'
# 3. Watch preemption happen when the cluster is full
kubectl get events --sort-by=.lastTimestamp | grep -i preempt
kubectl describe pod payment-api # look for Preempted / nominated node
When preemption fires, you will see events like Preempted by <namespace>/payment-api on node <node> on the victim pods, and a scheduling event on the high-priority pod referencing the nominated node. Those events are your evidence for a “why did this pod get killed?” question.
Decision & Signal-Word Table
| Scenario / cue | What it points to |
|---|---|
| ”Critical service must run even when the cluster is full” | High-value PriorityClass with default PreemptLowerPriority |
| ”Schedule this first, but never kill running pods” | PriorityClass with preemptionPolicy: Never |
| ”All pods without a class should get priority X” | One PriorityClass with globalDefault: true |
| ”Node ran out of memory and evicted a pod” | QoS / kubelet eviction, not preemption |
| ”Pod is Pending and the cluster is full” | Candidate for preemption (or add nodes) |
| “Protect a set of pods from being disrupted” | PodDisruptionBudget (best-effort against preemption) |
| “Cluster has no room — add capacity instead of evicting” | Cluster Autoscaler, not preemption |
Note the last row: preemption reshuffles existing capacity; it never adds nodes. When the right answer is “grow the cluster,” that is the Cluster Autoscaler, which is a complementary mechanism.
Common Pitfalls
- Forgetting
priorityClassName. A pod with no class (and no global default) is priority 0 — it will be the first victim, not the protected workload. Always confirmspec.priority. - Using a value in the reserved range. Keep app PriorityClasses below one billion; the top range belongs to
system-node-criticalandsystem-cluster-critical. - Two global defaults. Only one PriorityClass may set
globalDefault: true; a second is rejected. - Expecting
Neverto preempt.preemptionPolicy: Neverreorders the queue but evicts nothing — do not use it for a workload that must displace others. - Assuming a PDB blocks preemption entirely. PDBs are honoured on a best-effort basis; a high-priority pod can still preempt across a PDB if there is no alternative.
- Confusing preemption with QoS eviction. Scheduler-preempts-pending-pod vs kubelet-evicts-under-node-pressure are different events with different causes.
Practice Under Real Exam Conditions
Priority and preemption are easy to read and easy to misapply under a two-hour timer. The exam does not just ask you to write a PriorityClass — it hands you a full cluster and asks you to predict, or produce, which pod ends up where. That reflex only comes from doing it on a live cluster, not from memorising YAML.
The KubeAstronaut (CKA + CKAD + CKS + KCNA + KCSA) Mock Exam Bundle gives you 15 performance-based mock exams in a browser terminal against real clusters — the same hands-on format as exam day — with scheduling scenarios (priority, preemption, affinity, taints, resource pressure) among the tasks across the CKA and CKAD portions. If you are focused on a single exam, the Certified Kubernetes Administrator (CKA) Mock Exam Bundle targets the Workloads & Scheduling domain directly with five full-length, lab-based exams. Pair either with the CKA exam guide to see how scheduling is weighted against the rest of the syllabus.
Conclusion
Pod priority and preemption give the scheduler a way to enforce importance when capacity runs out. You define a cluster-scoped PriorityClass with an integer value (kept below one billion, since the top range is reserved for system add-ons), optionally mark one as the globalDefault, and reference it from a pod with priorityClassName. Higher-priority pending pods are considered first, and when they cannot fit, the scheduler preempts lower-priority pods — deleting them gracefully, respecting PodDisruptionBudgets on a best-effort basis — to make room. Use preemptionPolicy: Never for work that should queue-jump without evicting anyone. Above all, keep preemption (a scheduler decision about a pending pod) separate from QoS eviction (a kubelet decision under node pressure). Nail those distinctions and the “full cluster, one stuck pod” questions become fast, confident points.
For adjacent scheduling topics, review taints, tolerations, and affinity (deciding where pods may run), requests, limits, and QoS (the eviction side of the story), node maintenance and PodDisruptionBudgets (protecting pods during disruption), and the broader workloads and scheduling guide. When a pod is stuck Pending, the CKA troubleshooting guide walks the diagnosis.
Frequently Asked Questions
What is a PriorityClass in Kubernetes?
A PriorityClass is a cluster-scoped object that maps a name to an integer priority value. Pods reference it via priorityClassName, and the admission controller resolves it into the pod’s spec.priority. Higher values are more important, and the scheduler uses them to order the pending queue and to decide preemption.
How does pod preemption work?
When a high-priority pod cannot be scheduled because the cluster is full, the scheduler looks for a node where evicting lower-priority pods would let it fit, picks the least disruptive set of victims, deletes them gracefully, and nominates that node for the pending pod. Only lower-priority pods can be victims.
What does preemptionPolicy: Never do?
It lets a pod jump ahead in the scheduling queue by priority but prevents it from evicting any running pods. The pod will schedule as soon as capacity is available on its own, without displacing other workloads — useful for high-priority batch or data-science jobs.
What value should I give my PriorityClass?
Any integer below 1,000,000,000 (one billion). The range at and above one billion is reserved for the built-in system-cluster-critical and system-node-critical classes used by essential add-ons. Values like 1000 or 1000000 are safe for application workloads.
Is preemption the same as the kubelet evicting a pod under memory pressure?
No. Preemption is a scheduler decision that deletes lower-priority pods so a pending pod can be placed. QoS-based eviction is a kubelet decision that removes pods when a node is out of resources, driven primarily by QoS class (BestEffort first) with priority as a tiebreaker. Different subsystems, different triggers.
Does a PodDisruptionBudget stop preemption?
Not completely. The scheduler honours PDBs on a best-effort basis and prefers victims that do not violate a budget, but if there is no other way to schedule a higher-priority pod, it may still preempt pods covered by a PDB.
How do I check a pod’s resolved priority?
Run kubectl get pod <name> -o jsonpath='{.spec.priority}'. The Priority admission controller fills this integer in from the referenced PriorityClass (or the global default, or 0 if neither applies).