BestEffort
No requests or limits anywhere, so this pod is evicted first under pressure
apiVersion: v1
kind: Pod
metadata:
name: debug
spec:
containers:
- name: shell
image: busybox:1.36Paste your workloads and find out which QoS class the kubelet will give each pod, which container decided it, and where that puts the pod in the eviction queue when a node runs short of memory. One container missing one limit demotes the whole pod, and nothing in kubectl tells you.
Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.
The answer appears here
Paste on the left and press Classify. Nothing leaves this tab.
Nothing else to flag.
No formatting problems, and nothing the rules object to. Worth remembering what that covers: this reads the file you pasted, not the account or cluster it will be applied to.
No finding matches that filter.
Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.
No requests or limits anywhere, so this pod is evicted first under pressure
apiVersion: v1
kind: Pod
metadata:
name: debug
spec:
containers:
- name: shell
image: busybox:1.36A limit above the request, and what that means for eviction order
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: app
image: api:1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256MiThese are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
That is BestEffort, which is the first thing evicted under node pressure and gets the lowest CPU share under contention.
Instead:Set requests at minimum. Requests alone give Burstable, which is a large improvement.
It requires limits equal to requests for every container including init containers, which wastes capacity for workloads that burst.
Instead:Reserve Guaranteed for latency-sensitive workloads. Burstable is correct for most things.
There is no qosClass field to set. It is derived from every container's requests and limits, so one sidecar without them changes the whole pod's class.
Instead:Set resources on every container, sidecars included.
All of it comes from the manifest, which is the point: the class is only visible on a running pod, and by then the workload is already in the eviction queue you did not intend.
QoS is a property of the pod, not of a container. One container short of one limit demotes every container in the pod to Burstable, and the usual culprit is a logging agent or a mesh proxy that arrived from a webhook with no resources on it. The report names the container that did it and says what the rest of the pod would have been without it.
Both are included in the computation, and a native sidecar is an init container with restartPolicy: Always, so it counts twice over: for the class and for the pod's memory request while it runs. An unresourced init container demotes a pod whose app containers are all correct.
The class is not the sort key. Under node memory pressure the kubelet asks first whether a pod is using more memory than it requested, so a Burstable pod sitting under its request is safer than one over it, and a Burstable pod with no memory request at all is in the same group as every BestEffort pod. Each workload is placed in that order and the diagram draws the queue.
Only cpu and memory take part, and only above zero. A container whose sole resource entry is nvidia.com/gpu or ephemeral-storage is BestEffort, and a literal cpu: 0 counts for nothing. Requests you did not write are also filled in: the API server copies each limit into the matching request, which is why a limits-only container is Guaranteed rather than Burstable.
The kubelet compares values, not strings. cpu: 1 and cpu: 1000m are equal and that pod is Guaranteed. 1Gi and 1G differ by 7.4% and that pod is Burstable. Both are parsed exactly, through the same code as the quantity converter on this site, so nothing here turns on a rounded double.
Eviction is the kubelet reclaiming memory in advance. A node-level OOM is the kernel choosing under pressure it did not see coming, and it chooses by oom_score_adj: -997 for Guaranteed, 1000 for BestEffort, and a value scaled by the container's own memory request in between. A container hitting its own memory limit is a third thing again, and the class has no say in it.
Nobody writes a QoS class. The kubelet derives it from the requests and limits on every container in the pod, writes the answer to status.qosClass, and never changes it. It controls two things: which pods the kubelet evicts first when a node runs short of memory, and what oom_score_adj the container processes are given so the kernel knows who to kill. It controls nothing else, and in particular it has no effect on scheduling.
Guaranteed needs every container in the pod, init containers included, to have both a CPU and a memory limit, with the request equal to the limit for both. BestEffort is the opposite end: no container sets any CPU or memory request or limit above zero. Burstable is everything in the middle, which is where most pods actually live. There is no partial credit and no per-container class.
Guaranteed every container: cpu and memory limits set,
request == limit for both
BestEffort no container sets any cpu or memory
request or limit above zero
Burstable anything else This is the case worth checking a manifest for. The app container is sized carefully, a sidecar is added later with no resources, and the pod that was meant to be Guaranteed is Burstable. Nothing warns about it: kubectl get pod does not print the class, the app container's own block still reads as correct, and the difference only appears as the wrong pod dying under memory pressure.
containers:
- name: app cpu 500m/500m, memory 512Mi/512Mi
- name: istio-proxy no resources at all
pod QoS: Burstable
the app container is unchanged and the whole pod
moved class, because the class belongs to the pod When a request key is absent and the matching limit is set, the API server copies the limit into the request. It happens per key, so a container with a CPU request and both limits gets its memory request filled in from its memory limit. The consequence surprises people twice over: a limits-only container is Guaranteed, and it reserves its full limit on the node even though no request appears anywhere in the file.
written: stored:
limits: requests:
cpu: 1 cpu: 1
memory: 1Gi memory: 1Gi
limits:
cpu: 1
memory: 1Gi
class: Guaranteed When a node crosses a memory eviction threshold the kubelet sorts the pods on it. The first key is whether the pod is using more memory than it requested, then pod priority, then how far over the request it is. BestEffort pods come first because they have no request to be under, which also means a Burstable pod that never set a memory request is exactly as exposed. A Burstable pod comfortably under its request is not in that group at all, and sits with the Guaranteed pods in the second one.
evicted first
| pods over their memory request,
| lowest priority first,
| then furthest over first
|
| everything else, only if the node is
V still over the threshold
a Burstable pod whose memory limit equals its
request cannot exceed it, so it never enters
the first group at all They are constantly confused and only one of them involves the QoS class. A container that hits its own memory limit is killed by the kernel against its cgroup, immediately, and no other pod is affected: the class is irrelevant. A node under memory pressure is handled by the kubelet, which evicts whole pods in the order above. A node that runs out faster than the kubelet reacts is handled by the kernel OOM killer, which picks a process by oom_score_adj, and that is where the class is the input.
container over its own memory limit
-> that container, OOMKilled, always
node under the eviction threshold
-> kubelet evicts pods, by request-versus-usage
node out of memory, no warning
-> kernel picks by oom_score_adj:
Guaranteed -997
Burstable 1000 - 1000*memRequest/capacity
clamped to at least 1000 + (-997)
BestEffort 1000 CPU is a compressible resource. A container over its CPU limit is throttled by the CFS scheduler for the rest of the 100ms period and continues, slower. Memory is not compressible: there is nothing to take back, so the only enforcement available is killing. That asymmetry is why a memory limit equal to the request is the safe setting and a tight CPU limit is a latency problem rather than a safety one.
cpu over limit throttled, per 100ms period
memory over limit OOMKilled, immediately The one real performance reason to want it: on a node started with --cpu-manager-policy=static, a Guaranteed pod whose CPU limits are whole numbers of cores gets exclusive cores, and nothing else is scheduled onto them. A fractional CPU limit anywhere in the pod disqualifies it. What Guaranteed does not do is help you get scheduled. The scheduler only ever looks at requests, so two pods with identical requests are placed identically whatever their classes are.
Guaranteed + integer cpu limits + static policy
-> exclusive cores, no other container on them
Guaranteed + cpu: 500m
-> shared pool, same as everything else
scheduling: requests only. QoS is not an input. The class is computed at admission and written to status.qosClass. There is no field to patch, no annotation, and in-place resource resize refuses any change that would move the pod between classes. Changing the class means a new pod, which means editing the template and rolling the workload.
kubectl get pod api-7d4f -o jsonpath='{.status.qosClass}'
Burstable
kubectl patch ... status.qosClass no such thing