QoS Class — Understanding Kubernetes Pod Eviction Priority
What is QoS Class in Simple Terms?
QoS Class answers a single critical question: when this node runs out of memory and the kubelet must kill some pods to survive — which pods die first?
Kubernetes assigns every pod one of three QoS classes automatically by looking at how its resource requests and limits are configured. You never set the QoS class directly — it is always derived from your resource configuration. Getting resource configuration right means getting eviction priority right.
The Three QoS Classes
+------------------------------------------+| GUARANTEED || requests.cpu == limits.cpu || requests.memory == limits.memory || (for EVERY container in the pod) || || Eviction priority: LAST || Use for: databases, payment processors |+------------------------------------------+ | v+------------------------------------------+| BURSTABLE || At least one container has requests set || requests != limits (or only requests) || || Eviction priority: SECOND || Use for: most application workloads |+------------------------------------------+ | v+------------------------------------------+| BESTEFFORT || No containers have any requests || or limits set at all || || Eviction priority: FIRST || Never use in production |+------------------------------------------+How Each QoS Class Gets Assigned — Decision Logic
+------------------------------------------+| Does EVERY container have: || requests.cpu == limits.cpu AND || requests.memory == limits.memory? |+------------------------------------------+ | | YES NO v v+------------------+ +------------------------------------------+| GUARANTEED | | Does ANY container have requests set? |+------------------+ +------------------------------------------+ | | YES NO v v +------------------+ +------------------+ | BURSTABLE | | BESTEFFORT | +------------------+ +------------------+Checking QoS Class of Pods
# Check QoS class of a specific podkubectl get pod payment-api-7d9f8c-xkp2q -n production \ -o jsonpath='{.status.qosClass}'# Output: Guaranteed | Burstable | BestEffort # View QoS class for all pods in a namespace at oncekubectl get pods -n production \ -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass# NAME QOS# payment-api-7d9f8c-xkp Guaranteed# order-api-6b8d4a-ab1cd Burstable# analytics-worker-zx9lp BestEffort <- this should not be in production # Find all BestEffort pods cluster-wide (these need fixing)kubectl get pods -A -o json | \ jq -r '.items[] | select(.status.qosClass=="BestEffort") | [.metadata.namespace, .metadata.name] | @tsv' | column -t # Find all Guaranteed pods cluster-widekubectl get pods -A -o json | \ jq -r '.items[] | select(.status.qosClass=="Guaranteed") | [.metadata.namespace, .metadata.name] | @tsv' | column -tHow Eviction Actually Works During Node Memory Pressure
+------------------------------------------+| Node: mumbai-prod-node-2 || Total memory: 16Gi || Available memory drops to 150Mi || (below eviction threshold of 200Mi) |+------------------------------------------+ | v+------------------------------------------+| kubelet eviction begins: || || STEP 1: Kill all BestEffort pods || -> analytics-job killed (exit 137) || -> cache-warmer killed (exit 137) || || Is memory now above threshold? |+------------------------------------------+ | | YES NO v v+------------------+ +------------------------------------------+| Eviction stops | | STEP 2: Kill Burstable pods || Pods stay alive | | Kills pod using most ABOVE its request |+------------------+ | -> order-api (380Mi used, 256Mi request) | | killed first | +------------------------------------------+ | Is memory now above threshold? | NO v +------------------------------------------+ | STEP 3: Kill Guaranteed pods | | Kills pod using most of its request | | (last resort — cluster is in crisis) | +------------------------------------------+The Connection Between QoS Class and Node Pressure
# Check current node memory pressure conditionskubectl describe node mumbai-prod-node-2 | grep -A 5 Conditions# Conditions:# MemoryPressure False <- Good — no evictions happening# DiskPressure False# PIDPressure False# Ready True # If MemoryPressure = True, check recent evictionskubectl get events -n production | grep -i evict# LAST SEEN REASON MESSAGE# 2m Evicted The node was low on resource: memory.# analytics-worker was evicted (BestEffort)Quick Reference — Resource Config to QoS Class
| Resource Configuration | QoS Class Assigned |
|---|---|
| No requests, no limits on any container | BestEffort |
| Requests set, no limits on any container | Burstable |
| Requests set, limits different from requests | Burstable |
| Requests set, limits same on some containers but not all | Burstable |
| Requests exactly equal to limits on ALL containers | Guaranteed |
REMEMBER THIS**Remember:** QoS Class is read-only — you cannot set it directly in your YAML. It is always derived by Kubernetes from your resource configuration. The only way to control your QoS class is by carefully configuring resource requests and limits on every container in the pod.
PLACEMENT PRO TIP**Tip:** Run `kubectl get pods -n production -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass` as a quick audit in every production namespace. Any BestEffort pod you see needs immediate attention — find the Deployment it belongs to and add resource configuration before a traffic spike causes a node pressure event that kills it unexpectedly.
COMMON MISTAKE / WARNING**Common Mistake:** Injected sidecars breaking Guaranteed QoS silently. If you use Istio or Linkerd, a sidecar proxy container is injected into every pod automatically. This injected container may have requests that differ from limits — silently downgrading your pod from Guaranteed to Burstable without any warning. Always check `kubectl get pod -o jsonpath='{.status.qosClass}'` after deploying to confirm the actual QoS class, not what you intended.