KubernetesXIII · Kubernetes QoS ClassesKubernetes QoS classes
BestEffort class — no reservations, evicted first
What you'll learn
- Identify a BestEffort Pod (no resources)
- Reason about why BestEffort is evicted first
- Recognise when BestEffort is the right choice (and when it is not)
- Apply LimitRange to prevent BestEffort in production namespaces
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
BestEffort is the lowest QoS class: a Pod with no requests or limits. This lesson covers when BestEffort is acceptable, why it’s risky in production, and how to prevent it with LimitRange.
The BestEffort rule
A Pod is BestEffort when:
- Every container has no
resourcesfield. No requests, no limits.
containers:
- name: app
# no resources block
The Pod’s status will show qosClass: BestEffort.
A subtle case: a Pod where every container has only one of requests or limits (but not both) is Burstable, not BestEffort. BestEffort specifically requires no resources at all.
Why BestEffort is risky
BestEffort Pods are evicted first under node pressure:
flowchart TD
Pressure[Node memory pressure] --> Q{Pod QoS?}
Q -- BestEffort --> E1[Evicted first]
Q -- Burstable --> E2[Evicted second]
Q -- Guaranteed --> E3[Evicted last]
E1 --> Capacity[Reclaim memory for higher-QoS Pods]
The scheduler also treats BestEffort Pods as having no resource requirements. They can be scheduled on any node regardless of capacity (the scheduler assumes zero usage). This means:
- A node can become overcommitted by BestEffort Pods.
- BestEffort Pods use whatever resources are available.
- Under pressure, BestEffort Pods are evicted to make room for higher-QoS Pods.
For production workloads, BestEffort is dangerous:
- The workload can be evicted at any time, without warning.
- Resource usage is unbounded; the Pod can consume all node resources.
- The Pod is invisible to capacity planning (no requests).
When BestEffort is the right choice
BestEffort is acceptable for:
- Batch jobs that can tolerate interruption: a Job that retries on failure; the workload is best-effort by design.
- Ad-hoc debugging containers:
kubectl debugwith no resources; the container is short-lived. - Specific patterns where QoS is irrelevant: a Job that uses 0 CPU and just writes output to a file.
# Job with BestEffort QoS — the Job retries on failure
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: worker
image: batch-worker:1.0
# no resources — BestEffort
The Job retries if evicted or killed. BestEffort QoS is acceptable.
Why BestEffort bypasses ResourceQuota
ResourceQuota counts requests and limits. A Pod with no requests is not counted:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-prod-quota
spec:
hard:
requests.cpu: "32"
requests.memory: 64Gi
A Burstable Pod with requests.cpu: 1, requests.memory: 1Gi
counts 1 CPU and 1Gi against the quota. A BestEffort Pod
counts zero. A team could create 100 BestEffort Pods and
bypass the quota entirely.
The fix: pair ResourceQuota with LimitRange defaults:
# LimitRange: every container gets defaults
apiVersion: v1
kind: LimitRange
metadata:
name: default
spec:
limits:
- type: Container
default: {cpu: 500m, memory: 512Mi}
defaultRequest: {cpu: 100m, memory: 128Mi}
# ResourceQuota: namespace total
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-prod-quota
spec:
hard:
requests.cpu: "32"
requests.memory: 64Gi
With both applied:
- A Pod without resources gets the defaults (100m CPU, 128Mi memory) — Burstable, counted against quota.
- The quota enforces namespace totals.
BestEffort Pods are effectively eliminated.
Preventing BestEffort with admission control
The LimitRange admission plugin can reject Pods that violate min constraints:
apiVersion: v1
kind: LimitRange
metadata:
name: default
spec:
limits:
- type: Container
min:
cpu: 10m
memory: 16Mi
default: {cpu: 500m, memory: 512Mi}
defaultRequest: {cpu: 100m, memory: 128Mi}
A Pod without resources:
defaultRequestis applied (100m, 128Mi).- The min check passes (defaults are ≥ min).
- The Pod is Burstable.
A Pod with resources below min:
- The admission plugin rejects the Pod.
- The Pod is never created.
Production discipline: every production namespace has a
LimitRange with min and defaultRequest to prevent
BestEffort Pods.
Production patterns
Production namespace with LimitRange:
apiVersion: v1
kind: LimitRange
metadata:
name: prod-defaults
namespace: team-a-prod
spec:
limits:
- type: Container
default: {cpu: 500m, memory: 512Mi}
defaultRequest: {cpu: 100m, memory: 128Mi}
min:
cpu: 10m
memory: 16Mi
max:
cpu: 4
memory: 8Gi
maxLimitRequestRatio:
cpu: "4"
memory: "2"
This LimitRange:
- Applies sane defaults to every Pod.
- Prevents BestEffort (defaults are ≥ min).
- Caps large resources (max).
- Prevents unbounded bursting (maxLimitRequestRatio).
Batch namespace without strict limits:
apiVersion: v1
kind: LimitRange
metadata:
name: batch-defaults
namespace: batch-jobs
spec:
limits:
- type: Container
default: {cpu: 2, memory: 4Gi}
defaultRequest: {cpu: 100m, memory: 256Mi}
Batch jobs can use up to 2 CPU and 4Gi memory by default. BestEffort is not allowed (defaults applied).
Diagnosing BestEffort Pods
kubectl get pods -A -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,QOS:.status.qosClass \
| grep BestEffort
Output:
team-a-prod debug-tool BestEffort
team-a-prod one-off-curl BestEffort
Production discipline: investigate every BestEffort Pod in a production namespace. If intentional (debugging), document why. If unintentional, fix the manifest.
Cross-course references
- The Linux course part
XXXVII-Linux-Resourcescovers cgroup OOM behaviour; BestEffort is the cluster-level equivalent. - The Ansible course part
XXXV-Ansible-Scriptingcovers service priority; BestEffort is the cluster-level equivalent. - The Observability course part
LXXXVII-Kubernetes-MetricsServercoverskubectl top; BestEffort Pods may not show up.
Quiz
Knowledge check · 4 questions
Q1. A Pod has a container with `resources: {requests: {cpu: 100m}}` but no limits. What is its QoS class?
Q2. A BestEffort Pod bypasses the namespace's ResourceQuota because it has no requests to count.
Q3. An audit reveals 30 BestEffort Pods in a production namespace. Walk through the diagnosis and remediation.
Namespace `team-a-prod` has 30 BestEffort Pods. The namespace has a ResourceQuota but no LimitRange. The Pods were created by `kubectl run` commands during debugging sessions that were never cleaned up.
Q4. When is BestEffort the right choice for a workload?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Never use BestEffort in production namespaces. A
missing
resourcesblock is usually a manifest error. - Pair ResourceQuota with LimitRange. LimitRange defaults prevent BestEffort; ResourceQuota enforces totals.
- Audit QoS regularly. BestEffort Pods in production indicate a missing LimitRange or undisciplined kubectl usage.
- Use
kubectl apply -fwith manifests. Neverkubectl runfor production; manifests enforce resources. - Document exceptions. If a BestEffort Pod is intentional (e.g., a debugging container), document why and limit its lifetime.