Intro
Kubernetes Priority Classes influence how the scheduler orders pending pods and which pods get evicted when a cluster runs out of resources. A misconfigured priority setup can cause critical workloads to starve, cause noisy preemption storms, or create hard-to-trace scheduling latency. This article shows you how to tune Priority Classes for better performance using practical examples, commands, and verification steps you can run on a live cluster or a local test environment.
You will learn how to inventory your current priority configuration, apply changes safely, diagnose scheduling behavior, handle common failure modes, and follow a repeatable operations checklist. Every example uses a concrete manifest, kubectl command, and expected output so you can reproduce the results.
Version and Environment Inventory
Before changing anything, confirm what you are running. PriorityClass is a stable API (scheduling.k8s.io/v1) available since Kubernetes 1.14. Check your cluster version:
kubectl version --short
Expected output shows both client and server versions. For example:
Client Version: v1.27.3
Kustomize Version: v5.0.1
Server Version: v1.27.3
Your cluster must have the PriorityClass admission controller enabled. In most managed Kubernetes services (EKS, GKE, AKS) it is enabled by default. To verify, run:
kubectl api-versions | grep scheduling.k8s.io
If you see scheduling.k8s.io/v1 in the output, you are ready. If not, check the kube-apiserver --enable-admission-plugins flag and ensure Priority is listed.
Next, list existing PriorityClasses to see the current landscape:
kubectl get priorityclass
Example output:
NAME VALUE GLOBAL-DEFAULT AGE
system-cluster-critical 2000000000 false 12d
system-node-critical 2000001000 false 12d
default 0 false 12d
high-priority 1000 false 2h
Note the VALUE column: a higher number means higher priority. The built-in system-cluster-critical and system-node-critical have very high values to protect system pods. The default PriorityClass with value 0 is applied to pods that do not specify a priorityClassName. If you see GLOBAL-DEFAULT as true, it means that PriorityClass is used when no priorityClassName is set and no default is defined otherwise.
To understand which pods currently use a particular priority, use:
kubectl get pods -A -o custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,PRIORITY:.spec.priorityClassName
This gives a quick view of priority distribution across namespaces. Example snippet:
NAME NAMESPACE PRIORITY
nginx-deployment-7d9f-abcde default high-priority
redis-0 redis <none>
A <none> entry means no priorityClassName is set, so the pod gets the global default if defined, otherwise priority 0.
Also check whether any nodes are under resource pressure that could trigger preemption:
kubectl top nodes
Example:
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
minikube 250m 12% 900Mi 62%
High memory or CPU pressure increases the chance of preemption events, which directly relate to PriorityClass behavior.
Blast Radius and Recovery Path
PriorityClass changes affect scheduling decisions cluster-wide. Always document the current state before modification. For example, save the existing PriorityClass manifests:
kubectl get priorityclass high-priority -o yaml > high-priority-backup.yaml
Review the backup to understand the exact fields you will change.
Safe Configuration Path
Modifying a PriorityClass or assigning a different priority to a workload can have immediate effects: pods may be evicted, rescheduled, or prevented from scheduling. Follow a controlled process.
Step 1: Create a Test PriorityClass
Start with a new PriorityClass rather than modifying an existing one. This limits impact and allows easy rollback by deleting the class.
Example manifest test-priority.yaml:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: test-priority
value: 100
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Temporary priority for testing scheduling behavior"
Apply it:
kubectl apply -f test-priority.yaml
Verify:
kubectl get priorityclass test-priority
Output:
NAME VALUE GLOBAL-DEFAULT AGE
test-priority 100 false 10s
Step 2: Deploy a Pod Using the Test PriorityClass
Create a pod that uses this priority:
apiVersion: v1
kind: Pod
metadata:
name: test-priority-pod
spec:
priorityClassName: test-priority
containers:
- name: nginx
image: nginx:latest
Apply and observe scheduling:
kubectl apply -f test-priority-pod.yaml
kubectl get pod test-priority-pod -o wide
Expected output shows the pod running and its assigned node:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-priority-pod 1/1 Running 0 15s 10.244.1.5 worker-1 <none> <none>
Check events for scheduling decision details:
kubectl describe pod test-priority-pod | grep -A5 Events
Look for a Scheduled event with no warnings.
Step 3: Test Preemption Behavior (Optional)
To see how priority affects preemption, create another PriorityClass with a higher value and a pod that can trigger eviction.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-test
value: 1000
preemptionPolicy: PreemptLowerPriority
Deploy a pod with this class that requests more CPU than currently free. For example, on a node with 2 CPUs total and 1.5 CPUs already used by other pods, request 1 CPU. The scheduler may evict lower-priority pods to make room.
apiVersion: v1
kind: Pod
metadata:
name: high-demand-pod
spec:
priorityClassName: high-priority-test
containers:
- name: stress
image: polinux/stress
resources:
requests:
cpu: "1000m"
After applying, watch for preemption:
kubectl get events --sort-by=.metadata.creationTimestamp | grep Preempted
If preemption occurs, you will see events like:
3s Normal Preempted pod/low-priority-pod Preempted by default/high-demand-pod on node worker-1
This verifies that the priority is being honored.
Step 4: Remove Test Resources
After verification, clean up:
kubectl delete pod test-priority-pod high-demand-pod
kubectl delete priorityclass test-priority high-priority-test
This is the safe path: create isolated resources, test, then delete.
Tuning PriorityClass Values for Performance
Priority value selection impacts scheduling latency and fairness. The scheduler processes pods in priority order; higher-priority pods are considered first, which can reduce their scheduling latency. However, setting values too high for non-critical workloads can delay critical pods.
Choosing Values
Kubernetes expects PriorityClass values to be less than or equal to 1,000,000,000 (one billion). Higher values are reserved for system-critical classes. A common pattern:
- 0: default user workload
- 100-1000: normal user workloads with slight differentiations (e.g., 100 for batch, 500 for web, 1000 for database)
- 10000-100000: high-priority user workloads (e.g., real-time API)
- 1000000: cluster-critical add-ons
- 2000000000 and above: system-node-critical and system-cluster-critical (built-in)
Avoid putting user workloads in the millions because they may preempt system components and cause instability.
Example: Adjusting a Production Deployment
Suppose you have a deployment api-server currently using default priority. You want to give it higher priority to reduce scheduling latency during peak times. First, observe its current scheduling behavior:
kubectl get pods -l app=api-server -o wide
Note the pod start times and any pending durations. Then create a new PriorityClass:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: api-high
value: 5000
preemptionPolicy: PreemptLowerPriority
Apply it and update the deployment:
kubectl apply -f priorityclass-api-high.yaml
kubectl patch deployment api-server -p '{"spec":{"template":{"spec":{"priorityClassName":"api-high"}}}}'
This triggers a rolling update. Watch the rollout:
kubectl rollout status deployment/api-server
Expected output:
deployment "api-server" successfully rolled out
Now measure scheduling latency before and after. You can use the Kubernetes scheduler metrics if you have Prometheus or use pod events timestamps. A quick way:
kubectl get events --field-selector involvedObject.name=api-server-xxxxx | grep Scheduled
Compare the time between pod creation and scheduled event.
Monitoring Preemption Impact
Preemption can cause cascading evictions. Monitor the rate of preemption events:
kubectl get events --all-namespaces --field-selector reason=Preempted -w
If you see a spike, consider whether your priority values are too aggressive or if cluster capacity needs scaling.
Verification and Diagnostics
After changing priorities, verify that the intended behavior occurs and diagnose any unexpected scheduling delays or failures.
Check Pod Scheduling Status
List pods sorted by priority class to see if high-priority pods are scheduled first:
kubectl get pods -A -o custom-columns=NAME:.metadata.name,PRIORITY:.spec.priorityClassName,STATUS:.status.phase --sort-by=.spec.priority
This is not directly supported; you can use kubectl get pods -A -o json and process with jq:
kubectl get pods -A -o json | jq -r '.items[] | [.metadata.name, .spec.priorityClassName, .status.phase] | @tsv' | sort
But if you have the priority value, you can join manually.
Diagnostic Commands
kubectl describe pod <pod-name>shows events, including preemption and scheduling failures.kubectl logs <pod-name> --previoushelps if the pod crashed after scheduling.kubectl get events --sort-by=.metadata.creationTimestampgives cluster-wide event stream for scheduling decisions.kubectl get nodes -o wideshows node capacity and allocatable resources.kubectl top nodesandkubectl top pods -Areveal resource usage that influences scheduling.
Example: Diagnosing a Pending Pod
A pod may remain pending despite high priority. Investigate with:
kubectl describe pod high-priority-pending-pod
Look for events like:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m default-scheduler 0/3 nodes are available: 1 Insufficient cpu, 2 node(s) had taint {dedicated: reserved}, that the pod didn't tolerate.
This indicates the priority alone does not override taints or resource shortages; preemption only evicts lower-priority pods if they are on eligible nodes and the new pod tolerates the taints.
Using Scheduler Logs
If you have access to the scheduler logs (e.g., in kube-system namespace), you can enable verbose logging to see priority decisions:
kubectl logs -n kube-system kube-scheduler-minikube -v=4
Look for lines like:
I0720 10:00:00.123456 1 schedule_one.go:243] "Attempting to schedule pod" pod="default/high-priority-pod"
I0720 10:00:00.125000 1 schedule_one.go:270] "Pod qualifies for preemption" pod="default/high-priority-pod"
This confirms the scheduler considered preemption for the high-priority pod.
Common Pitfalls and How to Avoid Them
Many issues arise from misunderstanding PriorityClass semantics or overlooking cluster conditions. Here are the most frequent mistakes.
Pitfall 1: Setting globalDefault Incorrectly
What happens: If you set globalDefault: true on a high-value PriorityClass, all pods without priorityClassName get that high priority, causing unexpected preemption and possibly evicting system pods.
Why it happens: Misunderstanding that globalDefault applies cluster-wide when no priority class is specified.
How to avoid: Only one PriorityClass can have globalDefault: true. If you need a default, use value 0 or a low value. Verify with kubectl get priorityclass -o wide and check the GLOBAL-DEFAULT column.
Recovery: Update the PriorityClass to set globalDefault: false (or delete it), then restart affected pods so they pick up the correct default.
Pitfall 2: Too Many Priority Levels
What happens: Creating dozens of PriorityClasses with fine-grained values can make scheduling behavior unpredictable and hard to reason about.
Why it happens: Teams assign a unique priority per microservice without a global policy.
How to avoid: Define a small set of priority tiers (e.g., 0, 100, 1000, 10000) and document their intended use. Enforce via policy or review.
Recovery: Consolidate PriorityClasses, update deployments to use the consolidated tiers, then delete unused classes.
Pitfall 3: Ignoring Preemption Policy
What happens: A pod with preemptionPolicy: PreemptLowerPriority evicts lower-priority pods even when the cluster could have scaled instead, causing disruption.
Why it happens: By default, preemption is enabled, and operators may not realize the impact.
How to avoid: For non-critical workloads, set preemptionPolicy: Never to prevent them from preempting others. Or use cluster autoscaling to add nodes before preemption.
Recovery: Change the PriorityClass preemptionPolicy to Never and reapply; evicted pods may not be restored automatically, so you may need to reschedule them.
Pitfall 4: Not Considering Pod Disruption Budgets (PDBs)
What happens: Preemption can evict pods even if it violates PDBs, but the scheduler tries to avoid that. If PDB is too strict, high-priority pods may remain pending.
Why it happens: PDBs limit voluntary disruptions, including preemption, and the scheduler respects them to a degree.
How to avoid: Align PDBs with priority tiers. For example, allow some disruption for low-priority pods, but protect high-priority with minAvailable.
Recovery: Adjust PDB or temporarily allow disruption, then retry scheduling.
Pitfall 5: Resource Requests Not Set
What happens: Preemption is based on resource requests, not actual usage. If pods have no requests set, the scheduler cannot determine if eviction will free enough resources, so preemption may not occur.
Why it happens: Developers omit resources.requests thinking limits are enough.
How to avoid: Always set requests for pods that may be preempted or that may need preemption. Use LimitRange to enforce defaults.
Recovery: Add requests to pod specs and redeploy.
Failure Modes and Recovery
PriorityClass tuning can lead to cluster instability if not handled carefully. Here are specific failure scenarios and recovery steps.
Scenario 1: High-Priority Pod Cannot Schedule
Symptoms: Pod remains Pending, kubectl describe shows FailedScheduling with "Insufficient" resources despite preemption enabled.
Diagnosis: Check if there are lower-priority pods on nodes that qualify. Run:
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.priorityClassName!="" and .spec.priorityClassName!="system-node-critical" and .spec.priorityClassName!="system-cluster-critical") | [.metadata.namespace, .metadata.name, .spec.priorityClassName, .spec.nodeName] | @tsv'
Also check kubectl get events for preemption attempts.
Recovery: Manually delete some lower-priority pods to free resources, or add a new node. Ensure the high-priority pod's requests are accurate.
Scenario 2: Preemption Storm
Symptoms: Rapid evictions of many pods, cluster thrashing.
Diagnosis: Run kubectl get events --field-selector reason=Preempted -w to see continuous preemption.
Recovery: Immediately lower the priority value of the offending PriorityClass or set preemptionPolicy: Never on it. Scale up cluster nodes if possible. Reapply the changes and monitor.
Scenario 3: System Pods Being Preempted
Symptoms: Critical system pods (e.g., kube-proxy, coredns) get evicted, causing network or DNS issues.
Cause: User-defined PriorityClass with value higher than system priorities (e.g., > 2000000000) or misconfigured globalDefault.
Recovery: Delete or modify the offending PriorityClass to have a lower value. System pods will be rescheduled automatically if their controllers are running. Ensure system PriorityClasses are not altered.
Rollback Strategy
Always keep a backup of PriorityClass manifests before changes. To rollback:
kubectl apply -f backup-priorityclass.yaml
For pods, you may need to recreate them to pick up the priority change (deleting a pod causes its controller to recreate it).
Operations Checklist
Use this checklist when tuning PriorityClass performance in a production cluster.
- [ ] Record current PriorityClasses:
kubectl get priorityclass -o yaml > priorityclasses-backup.yaml - [ ] Identify the workload and its required priority tier. Assign an owner: e.g., Priya Shah, Engineering Lead, reviews quarterly.
- [ ] Create a new PriorityClass with a test value and
preemptionPolicy: PreemptLowerPriority(orNeverif appropriate). Apply it. - [ ] Deploy a test pod using the new PriorityClass in a non-production namespace first.
- [ ] Verify scheduling:
kubectl get pod test-pod -o wideandkubectl describe pod test-podfor events. - [ ] Test preemption if applicable: create a lower-priority pod, observe eviction events.
- [ ] Measure scheduling latency before and after using pod event timestamps.
- [ ] Update the production workload with the new
priorityClassNamevia rolling update. - [ ] Monitor rollout:
kubectl rollout status deployment/<name>. - [ ] Watch for preemption storms:
kubectl get events --field-selector reason=Preempted -w. - [ ] Validate resource requests on all affected pods.
- [ ] Confirm PDBs are not blocking necessary preemption.
- [ ] Clean up test resources.
- [ ] Document the change in the cluster runbook.
Responsible party: Platform Engineering Lead (or designated SRE) must approve priority changes for production namespaces. This decision review happens at least monthly or after any incident related to scheduling.
Conclusion
Tuning Kubernetes PriorityClass is about balancing workload criticality with cluster stability. By following a structured process of inventory, safe configuration, verification, and recovery, you can improve scheduling performance without causing unintended preemption.
Start with observation, make incremental changes, and always have a rollback plan. The examples and checklists in this article give you a practical foundation to optimize priority behavior in your cluster.