No Kubernetes manifest explicitly declares a quality-of-service class: it’s deduced automatically from how requests and limits are set, or not set at all. That silent deduction has a concrete consequence, often discovered at the worst possible time: it determines the exact order in which the kubelet evicts pods when a node runs low on memory.

Three classes, none declared directly

Guaranteed applies when every container in the pod sets requests strictly equal to limits, for both CPU and memory: maximum protection, the last candidate for eviction. Burstable covers everything else once at least one request or limit exists without meeting Guaranteed’s conditions: the most common class in practice, where the vast majority of workloads land without anyone explicitly thinking about it. BestEffort applies when no request or limit is defined anywhere: the first candidate for eviction, regardless of the service’s actual importance.

# Guaranteed: requests equal to limits on both resources,
# the only combination that earns this class
resources:
  requests: {cpu: "500m", memory: "512Mi"}
  limits: {cpu: "500m", memory: "512Mi"}
# BestEffort: nothing defined, the most vulnerable class,
# usually the result of an oversight rather than a choice
resources: {}

What actually triggers eviction

The eviction order only activates under real node memory pressure (MemoryPressure), not in normal operation: as long as the node has headroom, all three classes coexist with no visible difference. That’s exactly what makes the problem silent until the incident: a critical BestEffort service runs perfectly fine for months, until a load spike on the same node triggers memory pressure, and the kubelet evicts that service first, ahead of any other less critical but better-classed pod.

# A pod's effective QoS class, visible after the fact,
# never explicitly declared in the manifest
kubectl get pod my-app -o jsonpath='{.status.qosClass}'

QoS and PriorityClass: two mechanisms, not one

A PriorityClass decides who gets preempted at scheduling time, when a new higher-priority pod needs a spot. The QoS class decides something different: who gets evicted afterward, under memory pressure, once pods are already running. Both mechanisms apply in cascade: at equal priority, the QoS class breaks the eviction tie, which means a poorly sized critical pod (BestEffort) stays vulnerable even with a high PriorityClass, if nobody also set its requests and limits correctly.

The unintentional BestEffort trap

BestEffort is almost never a deliberate choice: it’s the result of a manifest written quickly, with no resources block at all, often for a component considered secondary at the time, which later becomes critical without anyone going back to fix its configuration. A regular audit of the effective QoS classes of workloads that matter (kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass) catches what no normal deployment reveals before the incident.

Takeaway

A pod’s QoS class gets deduced silently from its requests and limits, never an explicit choice, and only becomes visible at the exact moment a node runs low on memory. BestEffort (no resources defined) is the first eviction candidate, regardless of the service’s actual importance; Guaranteed (requests equal to limits) offers the best protection at the cost of more rigid sizing. This mechanism complements, without replacing, PriorityClass: both matter, one of the fine-tuning details that separates a cluster that holds up under pressure from one that discovers its real priorities during the incident, a central concern of a well-run Kubernetes migration.