Kubernetes assigns every Pod a QoS class based on its resource configuration. This class determines the order in which Pods are killed when a node runs out of memory. Understanding QoS is essential for protecting critical workloads from eviction.
1. The Three QoS Classes
QoS is automatically assigned — you don't set it directly. It's derived from how you configure requests and limits:
| QoS Class | Condition | Eviction Priority |
|---|---|---|
| Guaranteed | Every container has requests = limits (CPU and memory) | Last to be evicted (most protected) |
| Burstable | At least one container has a request or limit, but not all equal | Middle — evicted after BestEffort |
| BestEffort | No requests or limits set on any container | First to be evicted (least protected) |
Guaranteed
# Every container must have requests = limits for BOTH CPU and memory:
spec:
containers:
- name: db
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 1000m # ← must equal request
memory: 2Gi # ← must equal request
- name: sidecar
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 100m # ← every container must match
memory: 128Mi
# Check QoS:
kubectl get pod db-0 -o jsonpath='{.status.qosClass}'
# Guaranteed
Burstable
# At least one container has a request OR limit, but not all requests = limits:
spec:
containers:
- name: app
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m # ← limit ≠ request → Burstable
memory: 512Mi # ← limit ≠ request
BestEffort
# No resources specified on ANY container:
spec:
containers:
- name: batch
image: busybox
# No resources block at all → BestEffort
2. Eviction Order Under Memory Pressure
When a node's available memory drops below the eviction threshold, the kubelet must choose which Pods to kill. QoS class determines the order:
Detailed Eviction Logic
- BestEffort Pods — evicted first (sorted by memory usage, highest first)
- Burstable Pods exceeding their memory request — evicted next (sorted by how much they exceed their request, as a percentage)
- Burstable Pods within their memory request — evicted only if pressure continues
- Guaranteed Pods — evicted only as a last resort (only if system processes need memory)
OOM Score Adjustment
Kubernetes sets the Linux oom_score_adj based on QoS class:
| QoS Class | oom_score_adj | Meaning |
|---|---|---|
| Guaranteed | -997 | Almost never killed by kernel OOM (lowest score) |
| Burstable | 2 to 999 (calculated) | Score based on memory request/limit ratio |
| BestEffort | 1000 | First killed by kernel OOM (highest score) |
# The formula for Burstable: # oom_score_adj = 1000 - 10 * (memory_request / memory_limit * 100) # Example: request=256Mi, limit=512Mi → 1000 - 10*(50) = 500
1. kubelet eviction — proactive, graceful (SIGTERM → wait → SIGKILL). Uses QoS class order. Triggered at configurable thresholds (default: <100Mi free).
2. Kernel OOM killer — reactive, immediate (SIGKILL). Uses oom_score. Triggered when the node is completely out of memory and kubelet eviction wasn't fast enough.
3. Choosing the Right QoS for Your Workloads
| Workload Type | Recommended QoS | Why |
|---|---|---|
| Databases, stateful systems | Guaranteed | Must never be evicted — data corruption risk |
| Core microservices (API, auth) | Guaranteed or tight Burstable | Downtime directly impacts users |
| Web frontends, general services | Burstable | Can tolerate brief evictions, benefit from burst capacity |
| Batch jobs, cron tasks | BestEffort or Burstable | Can be rescheduled without user impact |
| Dev/test workloads | BestEffort | Lowest priority, uses leftover resources |
The Trade-Off
# Guaranteed: # ✅ Maximum protection from eviction # ✅ Predictable performance (no burst, no throttling if request=limit at right level) # ❌ Cannot burst above request — wastes unused capacity # ❌ Must know exact resource needs upfront # Burstable: # ✅ Can burst when spare capacity exists # ✅ Flexible — only needs approximate sizing # ❌ Can be evicted under pressure # ❌ Performance varies based on node load # BestEffort: # ✅ Uses free resources without any reservation # ❌ First to be killed — no guarantees whatsoever # ❌ Can starve when node is busy
Checking QoS Class
# Check all Pods' QoS: kubectl get pods -o custom-columns=\ NAME:.metadata.name,\ QOS:.status.qosClass,\ CPU_REQ:.spec.containers[0].resources.requests.cpu,\ MEM_REQ:.spec.containers[0].resources.requests.memory,\ CPU_LIM:.spec.containers[0].resources.limits.cpu,\ MEM_LIM:.spec.containers[0].resources.limits.memory
4. QoS and Node Allocatable
Understanding how QoS interacts with the node's resource accounting:
# Node capacity: 16Gi memory # kube-reserved: 1Gi # system-reserved: 1Gi # eviction-threshold: 100Mi # ───────────────────────────── # Allocatable: 13.9Gi ← what scheduler sees # Sum of all Pod requests must fit within Allocatable # A Guaranteed Pod with 4Gi request takes exactly 4Gi from Allocatable # A BestEffort Pod takes 0 from Allocatable (scheduler thinks node has more room) # This is why BestEffort can cause overcommit: # Node may have 13.9Gi Allocatable but 16Gi of actual usage if BestEffort Pods burst
Summary
| Concept | Key Point |
|---|---|
| Guaranteed | requests = limits for all containers (both CPU and memory). Last evicted. |
| Burstable | Has some requests/limits but not all equal. Middle eviction priority. |
| BestEffort | No requests or limits. First evicted. Uses spare resources. |
| Eviction order | BestEffort → Burstable (exceeding requests) → Guaranteed |
| oom_score_adj | Guaranteed: -997, BestEffort: 1000, Burstable: calculated |
| kubelet vs kernel OOM | kubelet: graceful, proactive. Kernel: immediate SIGKILL, reactive. |
| Production rule | Critical = Guaranteed, general = Burstable, disposable = BestEffort |
📝 Quiz: Quality of Service Classes
Q1: A Pod has requests.cpu: 500m, limits.cpu: 500m, requests.memory: 256Mi, limits.memory: 512Mi. What QoS class?
Q2: A Pod has two containers. Container A has requests=limits for both CPU and memory. Container B has no resources set. What QoS?
Q3: A node has memory pressure. It has three Pods: Pod A (Guaranteed, using 2Gi), Pod B (Burstable, using 1.5Gi with request 1Gi), Pod C (BestEffort, using 500Mi). Which is evicted first?
Q4: You want a database Pod to never be evicted before web server Pods. What resource configuration achieves this?
Q5: Two Burstable Pods are on the same node under pressure. Pod A: request=1Gi, using=2Gi. Pod B: request=512Mi, using=768Mi. Which is evicted first?
Q6: A Pod only sets limits.memory: 512Mi (no requests). What QoS class and what memory request does it get?
requests.memory: 512Mi automatically. If CPU is not set at all, the Pod is Burstable (has memory req/limit but no CPU). If you also set limits.cpu (and it auto-sets CPU request to match), it becomes Guaranteed.