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 ClassConditionEviction Priority
GuaranteedEvery container has requests = limits (CPU and memory)Last to be evicted (most protected)
BurstableAt least one container has a request or limit, but not all equalMiddle — evicted after BestEffort
BestEffortNo requests or limits set on any containerFirst 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
The rules are strict for Guaranteed: ALL containers in the Pod must have BOTH CPU and memory requests AND limits, and requests must EQUAL limits. If even one container is missing a memory limit or has request ≠ limit, the Pod drops to Burstable.

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:

Eviction Order (first killed → last killed) BestEffort Killed FIRST No guarantees Burstable Killed second If exceeding requests Guaranteed Killed LAST Only in extreme pressure Within same QoS class: Pod using the most memory ABOVE its request is killed first Guaranteed Pods only evicted when system processes need memory (very rare)

Detailed Eviction Logic

  1. BestEffort Pods — evicted first (sorted by memory usage, highest first)
  2. Burstable Pods exceeding their memory request — evicted next (sorted by how much they exceed their request, as a percentage)
  3. Burstable Pods within their memory request — evicted only if pressure continues
  4. 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 Classoom_score_adjMeaning
Guaranteed-997Almost never killed by kernel OOM (lowest score)
Burstable2 to 999 (calculated)Score based on memory request/limit ratio
BestEffort1000First 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
Two levels of eviction:
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 TypeRecommended QoSWhy
Databases, stateful systemsGuaranteedMust never be evicted — data corruption risk
Core microservices (API, auth)Guaranteed or tight BurstableDowntime directly impacts users
Web frontends, general servicesBurstableCan tolerate brief evictions, benefit from burst capacity
Batch jobs, cron tasksBestEffort or BurstableCan be rescheduled without user impact
Dev/test workloadsBestEffortLowest 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
In production, most Pods are Burstable (requests < limits). Critical Pods (databases, control plane) are Guaranteed. Pure BestEffort is rare in production — even batch jobs should have requests (to prevent scheduling on overcommitted nodes). A common pattern: set memory request=limit (prevent OOMKill surprises) but CPU request < limit (allow burst).

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
BestEffort Pods enable overcommitment. Since they have no requests, the scheduler doesn't account for them. A node with 13.9Gi allocatable might actually be using 15Gi if BestEffort Pods are actively consuming memory. This overcommit is why eviction exists — the kubelet cleans up when reality exceeds capacity.

Summary

ConceptKey Point
Guaranteedrequests = limits for all containers (both CPU and memory). Last evicted.
BurstableHas some requests/limits but not all equal. Middle eviction priority.
BestEffortNo requests or limits. First evicted. Uses spare resources.
Eviction orderBestEffort → Burstable (exceeding requests) → Guaranteed
oom_score_adjGuaranteed: -997, BestEffort: 1000, Burstable: calculated
kubelet vs kernel OOMkubelet: graceful, proactive. Kernel: immediate SIGKILL, reactive.
Production ruleCritical = 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?

Burstable. For Guaranteed, ALL requests must equal ALL limits. Here memory request (256Mi) ≠ memory limit (512Mi). Even though CPU request = CPU limit, the mismatch in memory makes it Burstable.

Q2: A Pod has two containers. Container A has requests=limits for both CPU and memory. Container B has no resources set. What QoS?

Burstable. For Guaranteed, EVERY container must have requests = limits for both CPU and memory. Container B has nothing set, so the Pod drops from Guaranteed to Burstable. (If Container A also had nothing, it would be BestEffort.)

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?

Pod C (BestEffort) is evicted first — always. If pressure continues after that, Pod B (Burstable) is next because it's using 500Mi above its 1Gi request. Pod A (Guaranteed) is evicted only as an absolute last resort.

Q4: You want a database Pod to never be evicted before web server Pods. What resource configuration achieves this?

Set the database Pod to Guaranteed QoS: requests = limits for both CPU and memory. Set web server Pods to Burstable (requests < limits). Under memory pressure, Burstable web Pods are evicted first; Guaranteed database Pod survives.

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?

Pod A. Within the same QoS class, Pods are ranked by how much they exceed their request as a proportion. Pod A exceeds by 1Gi (100% over request). Pod B exceeds by 256Mi (50% over request). Pod A is evicted first because it's proportionally more over its guaranteed allocation.

Q6: A Pod only sets limits.memory: 512Mi (no requests). What QoS class and what memory request does it get?

When you set a limit without a request, Kubernetes auto-sets request = limit. So the Pod gets 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.