Quality of Service (QoS) Classes in Kubernetes

Quality of Service (QoS) Classes in Kubernetes

A complete, plain-English guide to Guaranteed, Burstable and BestEffort pods — how Kubernetes assigns them, how the Linux kernel enforces them, and how they decide who survives when a node runs out of room.

Imagine a lifeboat that is supposed to hold twenty people, but the ship’s captain, trying to be generous, has quietly promised seats to thirty. On a calm day nobody notices. When the storm hits and the boat gets heavy, someone has to decide who stays aboard and who gets asked to leave. If that decision is made in a panic, it will be unfair, random and sometimes disastrous. If it was written down calmly before the voyage, everyone knows what to expect.

A Kubernetes node is that lifeboat. It has a fixed amount of CPU and memory, yet the cluster routinely promises more than it has, because most programs do not use all they ask for at the same time. Quality of Service classes are the calm, written-down plan for what happens when the promises cannot all be kept. In this guide you will learn what the three classes are, how Kubernetes picks one for each pod without being asked, how the Linux kernel turns that choice into real behaviour, and how to use the classes to keep your important workloads safe and your cluster full but stable.

1Why Quality of Service Exists

Sharing a machine is cheap and efficient, right up to the moment everyone wants the same thing at once.

The problem of sharing

A Kubernetes cluster is a pool of machines called nodes, and each node runs many pods side by side. Running many programs on one machine is the whole point, because it saves money and keeps hardware busy. But a machine has only so much CPU time and so much memory. When the programs on it collectively ask for more than exists, something must give.

Two very different things can happen. If the shortage is about CPU, programs simply run more slowly because they take turns on the processor. If the shortage is about memory, there is no polite way to share. Memory is either available or it is not, and when it runs out, the machine must stop some program to free it. Without a plan, the system would choose a victim by chance, and the unlucky one might be your payment service while a harmless test job survives.

Simple Analogy

Think of an airline that sells a few more tickets than seats, because some passengers never show up. Usually this works. On a rare full flight, the airline already has rules: people with premium tickets and confirmed seats are protected, people on standby are asked to wait for the next plane. Kubernetes QoS classes are the airline’s boarding rules for your pods.

What QoS actually is

A Quality of Service class is a label that Kubernetes attaches to every pod, summarising how firmly that pod has reserved the resources it uses. There are exactly three labels: Guaranteed, Burstable and BestEffort. The label is not decorative. The kubelet, which is the agent running on each node, and the Linux kernel underneath both consult it when deciding how to arrange control groups, which process to stop first when memory runs out, and which pods to remove when the node is under pressure.

The most surprising thing for newcomers is that you never write the class yourself. There is no field called “qosClass” that you set. Instead, Kubernetes works it out from the way you described your pod’s resource needs. Change those descriptions, and the class changes with them. This design is clever, because it ties the safety level directly to the promises you were willing to make.

3
QoS classes: Guaranteed, Burstable and BestEffort
0
fields you set directly; the class is derived from resource settings
2
resources that decide the class: CPU and memory
!
Common Misconception

“QoS classes make my pod faster.” They do not. They do not speed up your code or give it priority on the network. They decide how your pod is treated when resources are short, and how firmly capacity is reserved for it. A Guaranteed pod is protected, not turbocharged.

A story of a bad night

Scenario: the batch job that ate the memory

A data team deploys a report-generating job with no resource settings at all. One night the job reads a file ten times larger than usual and balloons in memory. On the same node sits a checkout service whose owners did set careful requests and limits. Because of QoS classes, the kernel and the kubelet go after the report job first, and the checkout service keeps running. Without the classes, the outcome would depend on luck.

“Kubernetes cannot promise that nothing will ever be stopped. QoS is how it promises that the right things are stopped first.”

2Resource Basics: Requests, Limits and Two Kinds of Resource

You cannot understand QoS without first understanding the two numbers every container can carry.

Each container in a pod may declare how much CPU and memory it needs. Kubernetes gives you two knobs for each resource. The request is the amount you are asking Kubernetes to set aside for you, and it is what the scheduler uses to decide where a pod fits. The limit is the ceiling that the container is not allowed to exceed. Between those two numbers lies a range where the container can use extra if it is available.

Term 1

Request

The minimum amount reserved for the container. The scheduler counts it when picking a node, and the kernel uses it to share CPU fairly under contention.

Term 2

Limit

The maximum amount the container may use. Going past a CPU limit means being slowed down; going past a memory limit means being stopped.

Term 3

Allocatable

The part of a node’s capacity that pods may use after Kubernetes sets aside room for the operating system, its own agents and eviction safety margins.

Term 4

Overcommit

Promising more in total limits than the node really has. It is safe only because programs rarely peak at the same moment.

Term 5

Compressible resource

A resource that can be squeezed without killing anything. CPU is the main example: less of it just means slower work.

Term 6

Incompressible resource

A resource that cannot be squeezed without taking it back by force. Memory is the main example: once given, it can only be reclaimed by stopping something.

Simple Analogy

CPU is like a busy road. When too many cars arrive, everyone slows down but everyone still gets through eventually. Memory is like seats in a theatre. When every seat is taken, the next person cannot squeeze in; someone has to leave before anyone new can sit down.

How CPU and memory behave differently

QuestionCPUMemory
Can it be shared smoothly?Yes, by time-slicingNo, it is held until released
What happens at the limit?The container is throttled and runs slowerThe container is killed by the kernel
What happens at node shortage?Pods get CPU in proportion to their requestsThe kernel or kubelet stops some pods
Typical unitCores or thousandths of a core, called millicoresBytes, usually written in mebibytes or gibibytes
Role in QoSShapes fairness and exclusive core optionsShapes who is killed first and when

The node’s real budget

A node with sixteen gigabytes of memory does not offer all sixteen to pods. The operating system needs some, the kubelet and container runtime need some, and Kubernetes keeps a safety buffer so that it can react before memory truly runs out. What remains is the allocatable amount, and that is the budget the scheduler works against. Understanding this budget explains why a node that looks half empty can still refuse a new pod.

i
Units Matter

A request of “500m” CPU means half a core, not five hundred cores. For memory, “Mi” and “Gi” mean powers of two, while “M” and “G” mean powers of ten, so the two can differ by several percent. Mixing them up is a classic source of confusing behaviour around limits.

3The Three QoS Classes

Guaranteed, Burstable and BestEffort: three promises of increasing flexibility and decreasing protection.

Class 1

Guaranteed

Every container has both CPU and memory limits and requests, and each request equals its limit. The pod has reserved exactly what it may use, so it is the most protected.

Class 2

Burstable

At least one container has some request or limit, but the pod does not meet the Guaranteed rules. It has a reserved floor and may stretch above it when room exists.

Class 3

BestEffort

No container has any CPU or memory request or limit. The pod reserved nothing, so it is the first to lose out when resources are short.

flowchart TD
    A["Look at every container in the pod"] --> B{"Does any container have any CPU or memory request or limit?"}
    B -->|"No"| C["BestEffort"]
    B -->|"Yes"| D{"Does every container have CPU and memory limits, with requests equal to limits?"}
    D -->|"Yes"| E["Guaranteed"]
    D -->|"No"| F["Burstable"]
        
FIG 1 — How Kubernetes decides a pod’s QoS class

The diagram shows the order of the questions. First Kubernetes asks whether the pod mentioned resources at all. If it said nothing anywhere, the pod is BestEffort. If it said something, the next question is whether it said everything and said it strictly: all containers, both resources, request equal to limit. A perfect answer gives Guaranteed. Every other partial answer lands in the middle as Burstable.

Simple Analogy

Guaranteed is a reserved seat with your name on it, paid in full. Burstable is a ticket with a reserved seat plus permission to move into an empty first-class seat if one is free. BestEffort is a standby ticket: you board only if there is room and you are the first to be asked to step off when the plane fills.

A side-by-side view

AspectGuaranteedBurstableBestEffort
Resource settingsRequests equal limits for CPU and memory in every containerSome settings, but not the full Guaranteed patternNone at all
Reserved capacityExactly what it can useIts requests onlyNothing
Can use more than its reservation?No, its ceiling equals its floorYes, up to its limits if presentYes, whatever is free
Kernel memory-kill preferenceLastIn the middleFirst
Node-pressure eviction tendencyLeast likely to be chosenDepends on how far above its requests it isMost likely to be chosen
Typical useDatabases, core services, latency-sensitive systemsMost web services and APIsDisposable or experimental work

Why not make everything Guaranteed?

It is tempting to ask for the strongest class everywhere. The catch is cost. A Guaranteed pod reserves its full ceiling at all times, even when it is idle, and the scheduler cannot hand that capacity to anyone else. Run enough Guaranteed pods and your nodes look full on paper while the machines sit mostly idle. Burstable and BestEffort exist because sharing unused capacity is where most of the savings in a cluster come from.

i
Think in Trade-Offs

Protection and efficiency pull in opposite directions. The art of QoS is to pay for protection only where an outage would really hurt, and to let everything else share capacity freely.

4How Kubernetes Assigns the Class, Step by Step

The rules look simple, but small details quietly change the outcome.

Every container counts, including the helpers

The class belongs to the whole pod, not to a single container, so Kubernetes inspects every container. That includes init containers, which run first to prepare the pod, and the long-running helper containers often called sidecars. For a pod to be Guaranteed, each of them must have both CPU and memory limits, with the requests matching. One forgotten helper is enough to push the whole pod down to Burstable.

1

The pod is submitted

You apply the pod, or a controller such as a deployment creates it. Your resource settings are part of the description.

2

Defaults and policies are applied

Admission steps may fill in missing values. A namespace rule called a LimitRange can add default requests and limits, and webhooks may inject extra containers.

3

If only a limit is given, the request follows

When a container sets a limit but no request, Kubernetes copies the limit into the request. This helps many pods become Guaranteed with less typing.

4

The class is calculated and stored

Kubernetes applies the decision flow and saves the result in the pod’s status. You can read it back at any time.

5

The class is locked for the pod’s life

The class cannot be edited on a running pod. Even features that resize resources in place are not allowed to change it. To get a different class you replace the pod.

Simple Analogy

Think of a school trip where the whole class gets one colour badge. The badge is decided by the least-prepared student: if just one child forgot a signed permission slip, the whole group gets the lower-status badge, no matter how well the others prepared.

Worked examples in words

Example A: perfect match, Guaranteed

A pod has one container with a CPU request and limit of one core, and a memory request and limit of two gibibytes. Every rule is met, so the pod is Guaranteed.

Example B: only memory set, Burstable

A pod has memory request and limit set but nothing for CPU. Because CPU is missing, the Guaranteed rule fails, yet something was specified, so the pod is Burstable.

Example C: requests lower than limits, Burstable

A container requests half a core and one gibibyte, but its limits are two cores and four gibibytes. It has room to grow beyond what it reserved, which is exactly what Burstable means.

Example D: nothing set, BestEffort

A quick test pod has no resource settings on any container. It is BestEffort and will be the first to be stopped if the node struggles.

Example E: an extra helper spoils Guaranteed

An application container is perfectly configured, but a logging helper container added by a platform tool has no limits. The pod drops from Guaranteed to Burstable without anyone changing the application.

!
The Injected Sidecar Surprise

Service meshes, security agents and logging tools often add containers to your pods automatically. If those injected containers lack matching requests and limits, your carefully built Guaranteed pod quietly becomes Burstable. Always check the final class on a running pod, not just the manifest you wrote.

A newer option: pod-level resources

Recent Kubernetes versions have been adding the ability to describe resources for the pod as a whole, not only per container. This can simplify pods with many small helpers, because a single pod-level budget can be set. The feature is newer and still maturing across versions, so check your cluster’s release notes to see whether it is available and how it influences the class on your version before relying on it.

5Under the Hood: Control Groups and the Linux Kernel

Kubernetes does not enforce anything by itself. It writes your intentions into Linux control groups, and the kernel does the rest.

What is a control group?

A control group, usually shortened to cgroup, is a Linux feature for putting processes into a bucket and attaching rules to the bucket: how much CPU time it gets, how much memory it may hold, how many processes it may create. Buckets can sit inside other buckets, forming a tree, and rules at the top apply to everything below. Containers are, at the bottom, just processes placed in the right cgroup buckets.

Simple Analogy

Imagine a company budget. The company has a total, each department gets a share, and each team inside a department gets a smaller share. The finance system checks spending against the nearest budget bucket. Cgroups are those budget buckets for CPU and memory, and Kubernetes is the manager who creates the buckets and fills in the numbers.

The QoS-shaped tree

The kubelet builds a family of cgroups that mirror the QoS classes. All pods live under one top bucket for Kubernetes workloads. Inside it, Burstable pods and BestEffort pods each have their own sub-bucket, while Guaranteed pods sit directly under the top bucket, side by side with those sub-buckets. Each pod then has its own bucket, and each container its own below that.

flowchart TD
    A["All Kubernetes pods bucket"] --> B["Guaranteed pod buckets"]
    A --> C["Burstable parent bucket"]
    A --> D["BestEffort parent bucket"]
    C --> E["Burstable pod buckets"]
    D --> F["BestEffort pod buckets"]
    B --> G["Container buckets"]
    E --> G
    F --> G
        
FIG 2 — The cgroup tree that mirrors QoS classes on a node

This shape has a purpose. Putting BestEffort pods in a shared sub-bucket with a very small CPU weight means that, when the node is busy, all of them together can only take a tiny slice, while Guaranteed and Burstable pods keep their reserved shares. The tree makes the class a real property of the machine, not just a label.

How requests and limits become kernel settings

You writeThe kernel receivesWhat it means in practice
CPU requestA CPU weight, called shares in older systemsUnder contention, the container gets CPU time in proportion to its weight; when the node is idle, the weight does nothing
CPU limitA bandwidth quota per scheduling periodThe container may use only so much CPU time every short period, and is paused until the next period once it runs out
Memory limitA hard maximum on the memory bucketGoing over triggers the kernel’s out-of-memory killer inside that bucket
Memory requestMostly used by the scheduler and the OOM scoreOn newer setups, an optional feature can also protect this amount from being reclaimed
i
Two Generations of Control Groups

Linux has an older and a newer version of cgroups, usually called version one and version two. The ideas are the same, but file names and some capabilities differ. Newer Kubernetes releases favour version two, which enables features such as better memory protection. Check which version your nodes use when reading low-level documentation.

The surprising behaviour of CPU limits

A CPU limit does not mean “you may use at most this fraction of the machine at any moment.” It means a budget of CPU time per tiny period, commonly a tenth of a second. A container with a limit of one core that runs on four threads at once can burn through its whole period budget in a quarter of the period and then be paused for the rest. The result can be sudden pauses, called throttling, even when the node as a whole is nearly idle. This is why CPU limits are a hot topic in latency-sensitive systems, and we will return to it later.

6Memory Pressure and the Out-of-Memory Killer

When memory runs out, the Linux kernel plays judge. QoS classes quietly influence its verdict.

Two ways a container dies from memory

People often use “OOMKilled” and “evicted” as if they meant the same thing. They do not, and the difference is essential for debugging. A container is OOMKilled when the kernel’s out-of-memory killer ends its process, either because the container hit its own memory limit or because the whole node ran out of memory. A pod is evicted when the kubelet, acting earlier and more politely, decides to remove it to relieve pressure on the node.

AspectOOM killEviction
Who acts?The Linux kernelThe kubelet
When?Immediately when memory cannot be foundWhen a configured threshold is crossed, often before real exhaustion
What is stopped?One or more processes, chosen by an OOM scoreWhole pods, chosen by a kubelet ranking
What do you see?Container status reason “OOMKilled” and exit code 137Pod status “Evicted” with a message about the resource under pressure
Does the pod restart?The container restarts according to the pod’s restart policyThe pod is gone; a controller must create a replacement, usually elsewhere

How the kernel picks its victim

Every Linux process has an out-of-memory score. When memory is exhausted, the kernel tends to stop processes with the highest score first. Kubernetes adjusts these scores using a setting called the OOM score adjustment, and it chooses the number according to the pod’s QoS class. This is how the class becomes a direct instruction to the kernel.

Guaranteed

Strongly protected

Receives a very low adjustment, around minus 997, which makes the kernel reluctant to choose it. Only essential system processes are protected more.

Burstable

Scaled by its request

Receives a number between about 2 and 999 that gets lower as the pod’s memory request grows as a share of the machine’s memory. Bigger reservations mean better protection.

BestEffort

First in line

Receives the highest adjustment, 1000, making its processes the most attractive targets for the kernel.

System

Node agents

The kubelet and container runtime get even lower values, so the machine’s own management tools survive when workloads misbehave.

Simple Analogy

Picture a lifeguard who must clear a crowded pool by whistle. Each swimmer wears a coloured wristband showing how much they paid for their spot. When the whistle blows, the lifeguard asks the swimmers with the cheapest wristbands to leave first. QoS-based scores are those wristbands, and the kernel is the lifeguard who reads them in a hurry.

Reading the Burstable formula without fear

The Burstable calculation sounds technical but behaves intuitively. Start from the maximum score of one thousand and subtract a portion proportional to how much memory the pod requested compared with the whole machine. A tiny request subtracts almost nothing and leaves the pod near the top, close to BestEffort. A large request subtracts a lot and moves the pod toward the protected end. The result is clamped so that Burstable pods always stay above the Guaranteed range and below the BestEffort range.

The practical message is simple: for Burstable pods, setting an honest memory request is not only a scheduling hint, it is also a protection setting. A pod that requests too little memory looks cheap to the kernel and is stopped sooner, even if it is a very important service.

!
Even Guaranteed Pods Can Be OOMKilled

Guaranteed pods are protected from being chosen during node-wide shortages, but they still cannot exceed their own memory limit. A Guaranteed pod that leaks memory and reaches its limit will be OOMKilled like any other. Protection from neighbours is not protection from your own bugs.

Example: two pods, one node, one memory spike

A node is nearly full. A Burstable web pod and a BestEffort scratch pod both start using more memory at the same moment. The node runs out. The kernel looks at scores, sees the BestEffort pod at the top of the list, and stops it. The web pod survives, reads from its slightly higher protection, and keeps serving users. Nobody was paged, because the plan written in advance worked.

7Node-Pressure Eviction: The Kubelet’s Polite Alternative

The kubelet would rather remove a pod gently than wait for the kernel to start swinging.

Why the kubelet acts first

The kernel’s out-of-memory killer is a last resort. It acts only when memory is already gone, and it picks processes, not whole pods, which can leave half-broken applications behind. To avoid this, the kubelet watches the node’s health continuously and starts removing pods when certain signals cross configured thresholds, ideally before the kernel has to step in. This process is called node-pressure eviction.

Signal

Memory available

How much memory is left once the working memory of the whole node is subtracted from its capacity. The most commonly triggered signal.

Signal

Node file system available

Free space, and free index entries, on the disk holding logs, writable container layers and temporary volumes.

Signal

Image file system available

Free space on the disk where container images live, if it is separate from the node file system.

Signal

Process IDs available

How many more processes the node can create before it runs out of identifiers. Fork bombs show up here.

Soft and hard thresholds

Each signal can have two kinds of threshold. A soft threshold gives the node a grace period: if the signal stays bad for the whole period, the kubelet begins evicting pods and gives them time to shut down cleanly. A hard threshold has no grace at all: the moment the signal is crossed, the kubelet evicts pods immediately, stopping them without waiting. Kubernetes ships with sensible hard defaults, for example evicting when available memory drops below roughly one hundred mebibytes, though the exact defaults vary by version and operating system.

Simple Analogy

A soft threshold is a restaurant manager saying, “We are getting full. If it stays crowded for the next ten minutes, we will ask some tables to wrap up.” A hard threshold is a fire marshal saying, “Everyone out, right now.” Both protect the building; one is polite, the other urgent.

Who gets chosen: the real ranking

Here is where many articles oversimplify. It is often said that BestEffort pods are evicted first, then Burstable, then Guaranteed. The reality is slightly different and more interesting. The kubelet ranks candidate pods using three questions, in this order.

1

Is the pod using more than it requested?

Pods whose usage of the starved resource exceeds their request are considered before those that stay within their request. A BestEffort pod has a request of zero, so any usage at all counts as exceeding.

2

How low is the pod’s priority?

Among those pods, the ones with lower priority values are chosen before those with higher priority values.

3

How far above its request is it?

Finally, pods are ordered by how much their usage exceeds their request. The biggest offender goes first.

Notice that the class itself is not a step in the ranking. It matters indirectly, because the class determines how likely a pod is to exceed its request. A Guaranteed pod, whose limit equals its request, can never use more than it requested, so it is almost never at the front of the queue. A Burstable pod that is quietly staying under its request is also safe, while a Burstable pod that has stretched far beyond its request is a prime target. BestEffort pods almost always exceed their zero request, so they land at the front.

flowchart TD
    A["Node signal crosses a threshold"] --> B["Kubelet reclaims node-level resources such as unused images"]
    B --> C{"Still under pressure?"}
    C -->|"No"| D["Done, no pods removed"]
    C -->|"Yes"| E["Rank pods: usage above request, then priority, then size of overuse"]
    E --> F["Evict the top-ranked pod"]
    F --> G{"Pressure relieved?"}
    G -->|"No"| E
    G -->|"Yes"| H["Stop evicting"]
        
FIG 3 — The kubelet’s eviction loop under node pressure
i
The One Exception

If the node’s own agents use more than the room reserved for them, and only Guaranteed pods or pods within their requests are left, the kubelet must still remove something to stay healthy. In that case it chooses the lowest-priority pods first. This is why setting system and kubelet reservations honestly matters for stability.

What happens around an eviction

Evicted pods are marked as failed with a reason of Evicted, and a message that explains which resource was short. The node reports a pressure condition, such as memory pressure, for a period of time. While that condition is active, the kubelet refuses to start new BestEffort pods on that node, and the scheduler is discouraged from sending more. This protects the node from a pile-on while it recovers.

Two details surprise teams. First, node-pressure evictions do not consult PodDisruptionBudgets, the rules that limit voluntary disruptions, because the kubelet is acting to save the node, not to move workloads politely. Second, evicted pods are not restarted on the same node; a controller such as a deployment creates a replacement that the scheduler places elsewhere, so spare capacity in the cluster matters.

!
The Kernel May Still Win the Race

The kubelet checks memory at intervals. If a program grabs memory faster than the kubelet notices, the kernel’s OOM killer may act first, and the result is an OOM kill instead of a clean eviction. Careful thresholds and, where available, faster notification options reduce the gap, but never close it entirely.

8QoS, Priority and Preemption: Three Different Ideas

These three words are constantly confused. Pulling them apart will save you hours of debugging.

Quality of Service describes how firmly a pod has reserved CPU and memory. Priority is a separate number you attach to a pod through a PriorityClass to say how important it is compared with others. Preemption is an action the scheduler takes: when a high-priority pod cannot find room, it may remove lower-priority pods to make space. Each idea answers a different question, and each is controlled by different settings.

IdeaQuestion it answersHow it is setWhen it matters
QoS classHow firmly are resources reserved?Derived from requests and limitsKernel memory kills, cgroup layout, eviction likelihood
PriorityHow important is this pod?A PriorityClass chosen by youScheduling order, preemption, eviction ranking
PreemptionWho must make way for a pending important pod?Done by the scheduler automaticallyWhen the cluster is full and a high-priority pod arrives
Simple Analogy

At a hospital, the type of ticket you paid for is like QoS: it shows what has been reserved for you. Priority is the triage nurse’s urgency score. Preemption is the nurse asking a less urgent patient to move so an emergency can use the bed. A patient can hold a very expensive ticket yet have a low urgency score, and the other way around.

They really are independent

You can have a BestEffort pod with the highest priority, a Guaranteed pod with the lowest priority, and every combination between. The scheduler’s preemption logic looks at priority and at the pod’s requests, not at its QoS class. The kernel’s memory logic looks at QoS-derived scores, not at priority. The kubelet’s eviction ranking combines usage versus requests with priority. Only by understanding all three can you predict what happens in a crisis.

Combination

High priority and Guaranteed

The strongest position. Scheduled first, can preempt others, and protected from kernel and eviction. Reserve it for truly critical services.

Combination

High priority and BestEffort

A trap. The scheduler treats it as important, but with no reservation the kernel treats it as the cheapest victim. Avoid this pairing.

Combination

Low priority and Guaranteed

A good fit for steady background work that needs reliable resources but can wait its turn for scheduling.

Combination

Low priority and BestEffort

The natural home for disposable experiments and opportunistic batch jobs that can be stopped without harm.

Built-in critical priorities

Kubernetes defines two special priority classes for essential components: one for cluster-level services and one for node-level services. Pods with these priorities are very hard to preempt and the kubelet tries hard to keep them running. They are meant for the system’s own parts, such as networking and DNS, and ordinary applications should not use them, since overuse defeats their purpose.

!
Priority Inflation

If every team marks its service “most important,” the number loses meaning and preemption becomes unpredictable. Keep a small, documented set of priority levels, and restrict who may use the highest ones through quotas on priority classes.

9The Scheduler’s View: Requests, Capacity and Overcommit

The scheduler is a bookkeeper. It adds up requests, ignores limits and never looks at the QoS class.

What the scheduler counts

When a new pod needs a home, the scheduler examines each node and asks whether the sum of requests of the pods already there, plus the new pod’s request, still fits within the node’s allocatable amount. If yes, the node is a candidate. Limits play no part in this arithmetic, and neither does the class. This single fact explains most “why is my node full when it looks idle?” mysteries: the node is full of promises, not of usage.

Simple Analogy

A conference organiser assigns rooms based on the number of seats each group reserved, not on how many people will really turn up. A room can look half empty while being completely booked on paper, and a new group is turned away even though the chairs are visibly free.

The gap between requests and limits is your overcommit

Because the scheduler counts only requests, the total of limits on a node can far exceed its capacity. This is overcommit, and it is deliberate. Most applications spend most of their time well below their limits, so allowing the gaps to overlap lets you pack more workloads on fewer machines. The risk is that if too many pods rise toward their limits together, the node runs short, and the QoS machinery we have studied decides who pays the price.

Sum of requests
must stay within allocatable; the scheduler enforces this strictly
Sum of limits
may exceed capacity; this is overcommit, and it is allowed
Real usage
fluctuates between the two and decides when pressure appears
flowchart LR
    A["Pod asks for a home"] --> B["Scheduler adds up requests on each node"]
    B --> C{"Does the total fit within allocatable?"}
    C -->|"Yes"| D["Node is a candidate"]
    C -->|"No"| E["Node is skipped"]
    D --> F["Scoring picks the best candidate"]
    E --> F
    F --> G["Pod is bound to the chosen node"]
        
FIG 4 — The scheduler uses requests, not limits or classes, to decide fit

How classes shape your overcommit appetite

ClassContribution to the scheduler’s totalContribution to overcommit risk
GuaranteedRequest equals limit, so full reservationNone; it can never rise above what was counted
BurstableOnly the request is countedThe gap between request and limit is uncounted risk
BestEffortCounted as nothing (the scheduler assumes a small default only to spread pods out)Unbounded; can use anything free on the node

A cluster made only of Guaranteed pods has no overcommit and no surprises, but also no sharing, so it tends to be expensive. A cluster full of BestEffort pods packs tightly but is fragile. Most healthy clusters blend them: Guaranteed for the heart of the business, Burstable for the bulk, and BestEffort for work that can be lost without regret.

i
Scoring Strategies

The scheduler can score nodes to spread pods out or to pack them tightly, depending on configuration. Packing saves money but increases the chance of pressure on busy nodes. Choose the strategy that matches your tolerance for risk, and revisit it as your classes mix changes.

10CPU in Depth: Weights, Throttling and Exclusive Cores

CPU is forgiving, but the way it is forgiving can create some of the most puzzling performance problems in Kubernetes.

Requests are weights, limits are ceilings

When the CPU is idle, every container may use as much as it wants, unless a limit says otherwise. When the CPU is contended, the kernel divides time among containers according to their weights, which Kubernetes derives from CPU requests. A container requesting twice the CPU of its neighbour gets roughly twice the time under pressure. This is why even a Burstable pod with a tiny CPU request can become sluggish on a crowded node: its small weight gives it a small slice.

Throttling and the limits debate

CPU limits are enforced with a quota per short period, and a container that uses up its quota is paused until the next period. For programs with many threads or with sudden bursts of work, this can add small but noticeable delays to individual requests, even when the node has plenty of idle CPU. Teams running latency-sensitive services have argued for years about whether to set CPU limits at all.

Reasons to set CPU limits

  • They protect neighbours from a runaway process that spins on the CPU
  • They make capacity planning more predictable for shared platforms
  • They are required for the Guaranteed class, with the request equal to the limit
  • They help tenants in a shared cluster stay within their fair share

Reasons to skip CPU limits

  • Throttling can hurt tail latency even when the node is idle
  • Idle CPU goes unused instead of helping a busy pod finish faster
  • CPU is compressible, so contention only slows work, it does not kill it
  • Requests alone already protect each pod’s fair share under contention

There is no single right answer. A common middle path is to set CPU requests carefully, leave CPU limits off for latency-sensitive Burstable services, and keep memory limits in place because memory, unlike CPU, is not compressible. The cost of that choice is that those pods cannot be Guaranteed, since the Guaranteed class demands limits for both resources. Whatever you choose, measure throttling directly and decide with data.

Simple Analogy

A CPU limit is like a taxi meter that stops the car every few seconds once you have used your minutes for that short window, even if the road ahead is empty. A request is like a guaranteed lane during rush hour: it protects you when traffic is heavy but does not stop you from driving fast on an empty road.

Exclusive cores for the most demanding workloads

Some workloads, such as high-frequency trading, telecom packet processing and machine learning, suffer when the operating system moves their threads between processor cores. Kubernetes offers a CPU manager with a static policy that can dedicate whole cores to a container. This only applies to Guaranteed pods whose CPU request is a whole number. Such containers receive exclusive cores that no other container on the node uses, which removes interference from neighbours and cache churn.

A companion component, the topology manager, can also try to keep a container’s CPU and memory on the same hardware region, called a NUMA node, which avoids slower cross-region memory access. These features show that the Guaranteed class is not only about protection. It is also the doorway to special hardware-aware placement.

i
Whole Numbers Matter

A Guaranteed pod requesting one and a half cores does not receive exclusive cores under the static policy; it shares a common pool. Only integer requests earn dedicated cores. If exclusive cores are the goal, size the request in whole cores.

Example: a trading gateway

A trading firm runs a market gateway as a Guaranteed pod requesting four whole cores. With the static CPU manager, those four cores are reserved for the container alone. Delays caused by other pods sharing the cores disappear, and response times become steady enough to meet strict internal targets.

11Memory in Depth: Working Sets, Cache and Swap

Memory numbers can be confusing, because the machine uses memory for more than your program’s own data.

What counts toward the limit

A container’s memory use includes the data your program holds, but also file cache the kernel keeps to speed up disk reads, and memory used by in-memory temporary volumes. Cache is generally reclaimable, so the kernel can drop it when pressure rises, and it does not by itself push a container over its limit. The measurement the kubelet cares about most is the working set: the memory that is truly in active use and cannot easily be taken back. Eviction decisions are based on this number, not on the raw total.

Simple Analogy

Think of your desk. The papers you are actively working on are the working set. The reference books you left lying around are like cache: they take space but can be shelved when someone needs the desk. Only the papers you cannot put away count as a real problem when the desk runs out of room.

Memory-backed temporary volumes

A common trap is a temporary volume stored in memory. It behaves like a small fast disk but its contents consume memory that counts against the container’s limit. A pod that writes large files into such a volume can be OOMKilled even though its program data is small. If your workload needs large scratch space, use disk-backed temporary storage instead.

Programs that know their limits

Language runtimes decide how much memory to claim based on what they think is available. Older runtimes read the whole machine’s memory instead of the container’s limit, and then happily grow past it. Modern versions of Java and Go can read container limits, and settings exist to cap the heap at a safe fraction of the limit. A good rule is to leave headroom between the runtime’s heap ceiling and the container’s limit, because runtimes also use memory for threads, buffers and code.

Tip

Leave headroom

Set the runtime’s heap ceiling noticeably below the container limit so that non-heap memory has room.

Tip

Match request to typical use

Set the request near the memory the program normally needs, so that both scheduling and OOM scores reflect reality.

Tip

Watch for leaks

A steadily rising working set that ends at the limit is a leak, not a sizing problem. Raising the limit only delays the failure.

Tip

Avoid memory-backed scratch

Prefer disk-backed temporary volumes for large files, to keep them out of the memory limit.

Newer memory features and swap

On nodes using the newer version of control groups, an optional feature called memory quality of service can use the memory request to protect that amount from being reclaimed and can apply soft throttling before the hard limit. It is an advanced option that has matured gradually across versions, so verify its status for your release.

Kubernetes also gained support for swap space on nodes. In the common limited mode, only Burstable pods are allowed to use swap, in proportion to their memory request. Guaranteed pods and BestEffort pods are kept out, and so are pods with high priority. The reasoning is that Guaranteed workloads want predictable performance, while BestEffort workloads have no reservation to justify a share. If you enable swap, QoS class suddenly decides who may use it.

!
Swap Changes the Rules

With swap, a memory shortage may slow pods instead of killing them, which changes how you diagnose problems. Latency-sensitive systems may see slow phases that look like CPU trouble. If you enable swap, update your monitoring and runbooks to include swap activity.

12Ephemeral Storage, Process Limits and What QoS Ignores

QoS classes look only at CPU and memory. Other resources have their own rules, and mixing them up leads to nasty surprises.

The class does not care about disk

A pod can declare requests and limits for ephemeral storage, which is the temporary disk space used for the container’s writable layer, its logs and temporary volumes. These numbers matter for scheduling and for eviction, but they are not part of the QoS class calculation. A pod with perfect CPU and memory settings is Guaranteed even if it says nothing about storage, and a pod with careful storage settings but no CPU or memory settings is still BestEffort.

ResourceCounted in the QoS class?What happens when it runs short?
CPUYesThrottling at limits; fair sharing by weight
MemoryYesOOM kill at limits; kernel and kubelet remove pods under node shortage
Ephemeral storageNoThe kubelet evicts pods that exceed their limit or fill the node disk
Process IDsNoNew processes fail at the pod limit; node-wide shortage triggers eviction
Extended resources such as GPUsNoPod stays pending until the device is free; not shared by default
Simple Analogy

QoS is like a boarding class on a flight, based on your seat ticket. It tells you nothing about how much luggage you may bring, and the airline has separate rules for baggage weight. Luggage rules are the ephemeral storage and process limits: separate systems that happen to apply to the same passengers.

Disk pressure and evictions

When a node’s disk fills up, the kubelet first tries to reclaim space by removing unused images and dead containers. If that is not enough, it evicts pods, ranking them by how much they exceed their ephemeral storage requests and by priority. A noisy pod that writes endless logs or fills its temporary volume can bring down its neighbours’ ability to run, which is why setting limits on ephemeral storage is a good habit for any workload that writes data.

Process limits

Each node has a finite supply of process identifiers, and a buggy program that creates processes in a loop can exhaust them, breaking every pod on the node. Kubernetes lets operators set a limit on the number of processes per pod and monitors the node-wide supply as an eviction signal. This is a protection against a different kind of noisy neighbour, one that does not show up in CPU or memory charts until it is too late.

i
Think Beyond CPU and Memory

A full QoS strategy sets ephemeral storage limits for writers, process limits for risky workloads and quotas for special devices. The class only covers part of the picture, so do not let a green “Guaranteed” label lull you into skipping the others.

Example: the chatty logger

A Guaranteed pod with perfect CPU and memory settings writes debug logs to its container layer without limit. Over a weekend it fills the node’s disk, and the kubelet starts evicting pods on that node, including innocent neighbours. The pod’s class gave it no protection against being the cause. An ephemeral storage limit would have stopped the problem at the source.

13Configuring the Node: Reservations, Thresholds and Special Pods

QoS only works as well as the node underneath it is set up. A few kubelet settings decide how much room workloads really get.

The allocatable formula

The amount of CPU and memory that pods may use on a node is not the machine’s full size. Kubernetes starts from the node’s capacity and subtracts three things: a reservation for Kubernetes’ own components such as the kubelet and container runtime, a reservation for the operating system’s essential services, and the hard eviction threshold, which is the safety buffer the kubelet wants to keep free. What is left is the allocatable amount, and it is the budget that the scheduler spends.

Setting

Kube-reserved

Room set aside for the kubelet, the container runtime and other Kubernetes node components, so that workloads cannot starve them.

Setting

System-reserved

Room set aside for the operating system’s own services, such as the login service, the logging daemon and network tools.

Setting

Eviction thresholds

The levels of free resource at which the kubelet starts evicting pods. The hard threshold also shrinks the allocatable amount.

Setting

Enforcement of allocatable

An option that places all pods under a single control group capped at the allocatable amount, so their combined use cannot invade the reserved room.

Simple Analogy

Think of a restaurant that has one hundred seats. The owner keeps a few seats for staff meals, a few for the kitchen’s prep tables and a few empty near the door for emergencies. Only the remaining seats are sold to customers. If the owner sells all one hundred, the kitchen cannot work and the restaurant fails, even though every customer has a seat.

Why reservations are a stability feature

Without reservations, a burst of workload memory can push out the kubelet itself. When the kubelet is unhealthy, it cannot report on pods, cannot enforce eviction and may be marked unreachable by the control plane, causing a cascade of rescheduling. Setting honest reservations means the node’s own tools survive the storms that the QoS system is trying to manage. A common starting point is to reserve a small fixed amount plus a percentage that grows with node size, then adjust using measurements from your own machines.

!
Reservations Too Small Are Silent Risks

A node with tiny reservations looks generous, because it offers a lot of allocatable room. The danger appears only under stress, when node agents are crowded out and eviction decisions go wrong. Review reservations whenever you change instance sizes or add heavy node-level agents.

Special kinds of pods and how they are counted

Init containers

Init containers run one after another before the main containers start. For scheduling, Kubernetes uses an effective request that is the larger of the biggest init container request and the combined requests of the regular containers, since they do not run at the same time. For the QoS class, however, every init container must also meet the Guaranteed rules, or the pod drops to Burstable.

Sidecar containers

Long-running helper containers, now supported as a first-class idea in recent releases, run alongside the main container for the whole life of the pod. Their requests add to the pod’s total and they count for the class. A single sidecar without limits can change the pod’s class.

DaemonSet pods

These run one copy per node and often provide essential services such as networking, monitoring and storage drivers. They deserve honest requests, memory limits and an appropriate priority, because they live on every node and are the first line of visibility during pressure.

Static pods

Static pods are managed directly by the kubelet from files on the node, such as control plane components on self-managed clusters. They have a class like any other pod and should usually be Guaranteed or have high priority, since their loss can disable the cluster.

Jobs and scheduled jobs

Batch pods are often Burstable or BestEffort. Because they end on their own, an eviction usually means a retry, which is cheap if the job is idempotent. Make sure retries are configured, so an eviction does not silently lose work.

i
Document Your Node Profile

Keep a short page that states, for each node type, its capacity, reservations, eviction thresholds and resulting allocatable amounts. When a team asks why a pod does not fit, the answer is then one lookup away.

14LimitRange and ResourceQuota: Guardrails for Namespaces

Relying on every developer to set resources correctly is hope, not a plan. Namespace rules turn good habits into defaults.

LimitRange: automatic defaults and boundaries

A LimitRange is a namespace-level rule that does two helpful things. It can fill in default requests and limits for containers that forgot to set them, and it can enforce minimum and maximum values, or a maximum ratio between a container’s limit and its request. Because it acts when the pod is created, it directly changes the QoS class the pod ends up with. A namespace with sensible defaults will almost never produce an accidental BestEffort pod.

Feature

Default request

Applied to containers that set no request, so they reserve a reasonable floor instead of nothing.

Feature

Default limit

Applied to containers that set no limit, giving every container a ceiling.

Feature

Minimum and maximum

Rejects containers that ask for too little to be useful or too much for the team’s budget.

Feature

Limit-to-request ratio

Caps how far above its request a container may stretch, which limits how much overcommit one pod can create.

Simple Analogy

A LimitRange is a school rule that every student must bring a lunch box and may not bring a lunch box bigger than a backpack. Students who forgot get a standard lunch box handed to them, and absurd requests are turned away at the door.

ResourceQuota: a budget for the whole namespace

A ResourceQuota sets a total budget for a namespace: the sum of requests and limits for CPU and memory, the number of pods, and more. When a quota covers CPU or memory, every new pod in the namespace must declare those values or be rejected, unless a LimitRange supplies defaults. The pair works well together: the quota sets the total, and the LimitRange makes sure each pod arrives with numbers attached.

Quota scopes that understand QoS

Quotas can be scoped to apply only to certain kinds of pods. Two scopes relate directly to QoS: one matches BestEffort pods and the other matches all pods that are not BestEffort. The BestEffort scope can only limit the number of such pods, since they have no resource numbers to add up. This allows a platform team to say, for example, “this namespace may run at most five BestEffort pods, but may reserve up to forty cores for everything else.”

ControlWhat it doesHow it affects QoS
LimitRange defaultsInjects requests and limits into containers that omit themTurns would-be BestEffort pods into Burstable or Guaranteed
LimitRange ratioLimits the gap between request and limitNarrows how much a Burstable pod can overcommit
ResourceQuota on CPU and memoryCaps total reservations in a namespaceForces pods to declare values, making BestEffort impossible without a default
BestEffort quota scopeCaps the count of BestEffort podsLets you permit a few disposable pods without a flood
!
Defaults Can Hide Problems

A LimitRange that adds a generous default limit can quietly turn a forgotten setting into a bloated reservation or a surprise class. Review what defaults produce in practice, and tell developers what the namespace applies, so the final result is never a mystery.

15QoS and Autoscaling

Autoscalers make decisions based on the numbers you wrote down. Wrong numbers lead to wrong scaling.

Horizontal scaling reads requests

The horizontal pod autoscaler adds or removes pod copies to keep a target level of utilization. When that target is a percentage of CPU or memory, the percentage is measured against the pod’s request. This has two consequences. A pod with no request, such as a BestEffort pod, cannot be scaled by utilization at all, because there is nothing to compare usage against. And a request that is far too large or too small distorts the scaling: a bloated request makes the pod look idle and prevents scaling up, while a tiny request makes it look overloaded and causes needless scale-outs.

Simple Analogy

Imagine a restaurant that hires extra waiters whenever tables are more than seventy percent full. If the manager wrote the table count wrongly, the restaurant hires too many waiters on a quiet night or too few on a busy one. The percentage is only as honest as the number it divides by, and for pods that number is the request.

Vertical scaling recommends better numbers

The vertical pod autoscaler studies real usage and recommends, or applies, new requests and limits. It is a valuable tool for finding honest numbers, especially for services whose needs drift over time. Applying new values usually means recreating pods, so plan for the brief disruption, and keep in mind that changing the numbers may change the pod’s QoS class. Use recommendation-only mode first, and review the results before letting it act automatically.

Resizing pods in place

Newer versions of Kubernetes allow certain resource changes on a running pod without restarting it. This is a big improvement for steady, stateful workloads, but it comes with an important rule: the QoS class must not change during a resize. A Guaranteed pod must remain Guaranteed, so you cannot resize it in a way that makes requests differ from limits. If the new shape would change the class, you need to replace the pod.

Node scaling follows requests too

The cluster autoscaler, and similar node provisioning tools, add machines when pods are stuck pending for lack of room. Their decisions are based on pod requests, exactly like the scheduler. They do not add nodes because pods are using a lot of memory, only because requests do not fit. If your pods ask for far more than they use, you will pay for nodes that sit mostly idle; if they ask for far less, nodes will feel crowded without any scale-out.

1

Measure real usage

Collect weeks of CPU and memory usage per workload, including busy peaks and quiet periods.

2

Set requests near typical need

Choose requests that cover normal behaviour with a small cushion, so scheduling and scaling reflect reality.

3

Set limits for safety

Choose memory limits that allow rare peaks, and decide deliberately about CPU limits. Equal values give Guaranteed.

4

Let autoscalers react

Add horizontal scaling for traffic swings, and use vertical recommendations to keep numbers honest over time.

16Choosing the Right Class for Each Workload

The right class is the cheapest one that still lets you sleep at night.

flowchart TD
    A["New workload"] --> B{"Would losing it for a few minutes hurt the business?"}
    B -->|"Yes, severely"| C{"Needs steady latency or exclusive cores?"}
    B -->|"Somewhat"| D["Burstable with honest requests and memory limits"]
    B -->|"No"| E{"Can it restart safely and run anywhere?"}
    C -->|"Yes"| F["Guaranteed with whole-number CPU requests"]
    C -->|"No"| G["Guaranteed or Burstable with high requests and priority"]
    E -->|"Yes"| H["BestEffort or Burstable with tiny requests and low priority"]
    E -->|"No"| D
        
FIG 5 — A practical path for picking a QoS class

The diagram is a starting point, not a law. Follow the questions: how bad is a short loss, does the workload need steady performance, and can it tolerate being restarted on another node. The answers point toward a class, and you can adjust from there based on cost and the experience of running it.

Typical workloads and their natural homes

Databases and message brokers: Guaranteed

These hold state in memory and are expensive to restart. Giving them equal requests and limits keeps neighbours from squeezing them and protects them from being chosen during node-wide shortages. Whole-number CPU requests can add stable performance.

Web servers and APIs: Burstable

Traffic rises and falls through the day. A request sized for ordinary load and a limit that allows busy spikes gives a good balance of protection and efficiency, especially when paired with horizontal autoscaling.

Machine learning training: Guaranteed

Training jobs run for hours and are sensitive to interruptions and noisy neighbours. Guaranteed pods with exclusive resources give predictable speed, and checkpointing guards against the rare failure.

Batch and report jobs: Burstable or BestEffort

Jobs that can be restarted and have flexible deadlines are ideal for using spare capacity. Low priority and small requests make them gentle neighbours that step aside when production needs room.

Developer sandboxes and experiments: BestEffort

Short-lived, disposable pods that nobody depends on can use the cheapest class. Pair it with a namespace quota on the number of such pods to avoid floods.

Node agents and daemons: Guaranteed or high-priority Burstable

Monitoring, logging and networking agents run on every node and keep the platform healthy. Give them honest requests, memory limits and a suitable priority so they survive the very pressure they help you detect.

i
Review the Class Once a Quarter

Workloads change. A quiet internal tool can become business-critical, and an old critical service may become less important. Check the classes of your top services regularly, and align them with how much their outage would really cost today.

17Observability: Seeing Classes, Pressure and Throttling

You cannot protect what you cannot see. Make QoS visible on dashboards and in daily operations.

Finding a pod’s class

Every pod shows its class in its status. The standard description command prints it in a line labelled “QoS Class,” and a query on the pod’s status field can list classes across a whole namespace. Metrics exporters such as kube-state-metrics also publish the class as a metric, so you can group and filter dashboards by it. Make it a habit to check the class on a running pod, not just on the file you wrote, because injected containers and namespace defaults may have changed the result.

The signals worth watching

SignalWhere it comes fromWhat it tells you
Pod QoS classPod status, and a kube-state-metrics seriesHow firmly a pod is protected
CPU throttling ratioContainer runtime metrics on throttled periods versus total periodsWhether CPU limits are slowing the container
Working set memory versus limitContainer memory metricsHow close the container is to being OOMKilled
Usage versus requestContainer metrics compared with the pod specWhether requests are honest and how eviction-prone a pod is
Node pressure conditionsNode statusMemory, disk or process pressure that may trigger evictions
Eviction counts and eventsKubelet metrics and cluster eventsHow often pods are being removed and why
OOM kill eventsContainer last state and node kernel logsWhich containers were killed and at what memory level
Simple Analogy

Think of a car dashboard. The speedometer, fuel gauge and engine light each warn about a different problem. Throttling is the engine feeling sluggish, working set against limit is the fuel gauge, and pressure conditions are the warning light. A driver who watches only speed will miss the fuel running out.

Useful alerts

Alert

Throttling above a threshold

Fires when a significant share of periods is throttled for a sustained time, pointing to limits that are too tight.

Alert

Memory near the limit

Fires when working set stays close to the limit, giving time to act before an OOM kill.

Alert

Node pressure condition

Fires when a node reports memory, disk or process pressure, as evictions may follow.

Alert

Unexpected BestEffort pod

Fires when a BestEffort pod appears in a production namespace, signalling a missing default or a rushed deployment.

i
Compare Usage with Requests Weekly

A short weekly report listing the pods whose usage is far above or far below their requests catches most sizing problems early. Pods far above their requests are future eviction targets; pods far below are wasted money.

18Troubleshooting Playbook

When a pod disappears or crawls, a calm sequence of questions finds the cause faster than guessing.

1

Find out how it ended

Was the pod evicted, or was a container OOMKilled, or did it simply restart for another reason? The status reason and exit code tell you which path to follow.

2

Check the class and the numbers

Read the QoS class, requests and limits on the running pod, including injected containers. Compare them with real usage over the last hours.

3

Look at the node

Check the node’s pressure conditions, recent events and kernel logs around the time of failure. Was this one pod’s problem or a node-wide shortage?

4

Identify the culprit

If the node was short of memory, find which pods were far above their requests. The victim and the cause are often different pods.

5

Fix the numbers, not just the symptom

Adjust requests, limits, priority or placement based on evidence. Raising a limit without understanding a leak only postpones the next incident.

Symptom to cause lookup

SymptomLikely causeFirst thing to try
Container restarts with reason OOMKilled and exit code 137Memory reached its own limit, or node memory ran outCompare working set with the limit; check for leaks and memory-backed volumes
Pod shows Evicted with a message about memoryNode memory pressure and the pod ranked high for evictionCompare its usage with its request and find the heavy neighbours
Pod shows Evicted with a message about ephemeral storagePod exceeded its storage limit or the node disk filledCheck logs and temporary volume growth; add storage limits
Pod stays Pending with “insufficient cpu” or “insufficient memory”Requests do not fit any node’s allocatable roomReduce requests if inflated, or add capacity
Service is slow while node CPU looks idleCPU limit throttlingCheck throttled-period ratio; raise or remove the CPU limit
Pod expected to be Guaranteed shows BurstableA container, often injected, lacks matching requests and limitsInspect every container, including init and sidecar containers
New pods refused on a node under pressureThe kubelet is blocking BestEffort pods during memory pressureGive the pod a real request or let the node recover
Important service evicted while a batch job survivedThe service exceeded its request while the job stayed within its ownRaise the service’s request and priority
!
The Victim Is Not Always the Cause

The pod that was evicted or killed may be innocent. A leaking neighbour can push a node into pressure and cause others to be removed first. Always look at who was using the most above their requests when the node got into trouble.

19Shared Clusters, Noisy Neighbours and Security

When many teams share nodes, resource settings stop being a private matter and become a question of fairness and safety.

The noisy neighbour

A noisy neighbour is a workload that consumes so much of a shared resource that others suffer. With CPU, the harm is slowness. With memory, the harm is kills and evictions. With disk and process counts, the harm is node-wide failures. QoS classes, requests, limits and quotas are the tools that keep one tenant’s problem from becoming everyone’s problem.

Simple Analogy

In an apartment block, one tenant blasting music affects the whole floor. The building rules limit noise hours, assign each flat a fair share of electricity and let the caretaker ask loud tenants to quiet down. Resource settings and quotas are the building rules for a shared cluster.

Resource settings as a security control

Unbounded workloads are an easy way to cause an outage, whether by accident or by attack. A compromised pod that spins the CPU, allocates memory without end or floods the disk with data can take down a node, and thereby harm neighbours. Memory and storage limits, process limits and quotas turn these attacks into contained incidents. Treat missing limits as a security gap, not only as an efficiency problem.

Control

Require limits by policy

Use admission policies to reject production pods without memory limits, or to inject defaults through a namespace rule.

Control

Cap BestEffort pods

Use the BestEffort quota scope to limit how many unreserved pods any tenant can run.

Control

Restrict high priorities

Use quotas on priority classes so that only approved teams can run at the highest levels, preventing priority abuse.

Control

Protect node agents

Reserve resources for system and Kubernetes components so that runaway workloads cannot starve the node itself.

Example: a quota that saved a quarter-end

A platform team gave each business unit a namespace with a quota and a LimitRange. During quarter-end reporting, one unit’s job went into a loop and tried to create thousands of pods. The pod-count quota stopped it at the namespace boundary, and other units’ critical workloads never noticed.

!
Soft Isolation Only

Resource controls reduce interference but share a kernel and a network. For strongly untrusted tenants, combine them with network policies, strict security contexts and, where needed, separate clusters or sandboxed runtimes.

20Design Patterns and Anti-Patterns

The teams that handle QoS well repeat a few good habits. The teams that suffer repeat a few famous mistakes.

Patterns that work

Pattern

Defaults everywhere

Give every namespace a LimitRange and quota so no workload can arrive without resource numbers.

Pattern

Honest requests from real data

Base requests on measured usage, and revisit them regularly with recommendations from autoscaling tools.

Pattern

Tiered classes by business value

Guaranteed for the crown jewels, Burstable for the majority, BestEffort for the disposable, with matching priorities.

Pattern

Memory limits always, CPU limits deliberately

Treat memory limits as non-negotiable safety, and decide on CPU limits per workload using throttling data.

Anti-patterns to avoid

ANTI-PATTERN-01 Avoid
Problem

Production pods are deployed with no requests or limits, because “it works in testing.”

Why It’s Harmful

They become BestEffort. The scheduler thinks they cost nothing, packs nodes full of them, and the first memory spike removes the most important services along with the least important.

Correct Approach

Enforce resource settings through namespace defaults and admission policy, and alert whenever a BestEffort pod appears in production.

ANTI-PATTERN-02 Avoid
Problem

Every service is made Guaranteed with huge requests “to be safe.”

Why It’s Harmful

The cluster fills up on paper while real usage stays low. Costs balloon, autoscalers add needless nodes, and teams still do not know what their services truly need.

Correct Approach

Reserve Guaranteed for workloads that truly justify it. Size requests from measured data and let the rest share capacity as Burstable.

ANTI-PATTERN-03 Avoid
Problem

A critical service has a very high priority but a tiny memory request, so it is Burstable with an almost-BestEffort OOM score.

Why It’s Harmful

The scheduler treats it as important, but the kernel treats it as cheap. Under memory pressure it is killed early, and the team is baffled because “it has the highest priority.”

Correct Approach

Align priority, request and class. A truly critical service needs an honest request, a memory limit and, ideally, the Guaranteed class.

ANTI-PATTERN-04 Avoid
Problem

A memory limit is raised every time a pod is OOMKilled, with no investigation.

Why It’s Harmful

If the cause is a leak, the pod simply takes longer to die, and larger limits increase overcommit and node-level risk. The real defect is never fixed.

Correct Approach

Plot working set over time. A flat line near the limit suggests undersizing; a steady climb suggests a leak that needs a code fix.

21Best Practices and Common Mistakes

If you keep only one chapter for your next design review, keep this one.

Do This

  • Set memory requests and limits on every production container, including injected helpers
  • Base requests on measured usage and revisit them on a regular schedule
  • Use Guaranteed for stateful, latency-sensitive and business-critical workloads
  • Align priority, request and class so they tell the same story about importance
  • Give each namespace a LimitRange and a ResourceQuota
  • Reserve capacity for the operating system and Kubernetes components on every node
  • Monitor throttling, working set versus limit and node pressure conditions

Avoid This

  • Deploying production pods with no resource settings at all
  • Making everything Guaranteed without evidence that it is needed
  • Pairing high priority with a tiny memory request
  • Raising memory limits as a reflex without checking for leaks
  • Assuming the scheduler looks at limits or at the class
  • Ignoring ephemeral storage and process limits for chatty workloads
  • Trusting the manifest instead of checking the class on the running pod

A review checklist you can reuse

1

Class inventory

List the QoS class of every pod in production. Question every BestEffort pod and every important service that is not Guaranteed or well-sized Burstable.

2

Request honesty

Compare actual usage with requests. Flag pods far above their request, which are eviction risks, and pods far below, which are waste.

3

Guardrails

Confirm that every namespace has defaults and quotas, and that policy blocks pods without memory limits.

4

Node reservations

Check that system and kubelet reservations and eviction thresholds are set sensibly, so node agents are protected.

5

Observability

Verify dashboards and alerts for throttling, memory against limit, node pressure and unexpected BestEffort pods.

6

Failure rehearsal

Practise a memory-pressure drill in a test cluster so the team has seen evictions and OOM kills before they happen in production.

!
A Class Is Not a Guarantee of Uptime

Even Guaranteed pods can be stopped by node failures, upgrades, their own memory leaks or the kubelet’s last-resort choices. QoS improves the odds in resource shortages, but you still need replicas, health checks and disruption budgets for real availability.

i
Write Down the Reasons

Record why each important workload has its class and numbers, right next to the manifest. When someone later wonders why a service is Guaranteed or why its CPU has no limit, the reasoning is already there.

22Cost and Capacity Planning with QoS in Mind

Classes are not only a safety tool. They are one of the strongest levers on your cloud bill.

Where the money goes

Cloud nodes are billed by the hour whether they are busy or idle. Because the scheduler fills nodes based on requests, every unit of request you declare becomes a unit of capacity you pay for. A request that is twice what a service needs doubles that service’s cost, even though the machine sits half empty. Guaranteed pods lock in the most capacity, since their ceiling is their reservation, while Burstable and BestEffort pods let you pack more work onto the same hardware.

Simple Analogy

Renting office space works the same way. If every team insists on reserving a private room sized for its busiest day, you rent a huge building that is mostly empty. If teams share a pool of desks and only the most critical groups hold reserved rooms, you can rent a far smaller building and still keep the critical groups safe.

Four levers for balanced capacity

Lever 1

Right-size requests

Trim inflated requests to match measured needs, and raise tiny requests that make pods look cheaper than they are. This improves both cost and fairness.

Lever 2

Mix classes deliberately

Fill part of the cluster with Guaranteed pods for protection, and the rest with Burstable and BestEffort pods that soak up spare capacity.

Lever 3

Choose packing strategy

Decide how tightly the scheduler packs nodes. Tight packing saves money and increases the chance of pressure; looser packing is safer and costlier.

Lever 4

Separate node pools

Dedicate pools to critical, steady workloads and others to flexible batch work, often on cheaper interruptible machines.

A simple planning method

1

Classify workloads by value and tolerance

Sort services into critical, standard and disposable groups, based on what an outage would cost and how well each restarts.

2

Estimate the reserved base

Add up the requests of critical Guaranteed workloads. This is the capacity you must always keep available.

3

Estimate the flexible layer

Add typical usage of Burstable services, and decide how much headroom you want for their busy peaks.

4

Fill gaps with disposable work

Let low-priority, BestEffort or small-request batch jobs use what remains, knowing they can be removed first.

5

Test the stress case

Simulate everything rising to its limit at once, and confirm that critical workloads survive while disposable ones give way.

Requests
drive the scheduler, the autoscalers and therefore your bill
Limits
drive safety, throttling and kill decisions
Class
summarises how the two relate and who is protected
i
Measure the Waste

A useful monthly number is the gap between total requested resources and total used resources across the cluster. A large gap is money spent on promises nobody keeps; a tiny gap with frequent evictions means requests are too lean.

23Real-World and Industry Examples

The same three classes appear in very different industries, always solving the same problem of sharing without chaos.

Online retail during a sale

A retailer runs its checkout and payment services as Guaranteed pods, its product browsing as Burstable pods that scale with traffic, and its recommendation refresh jobs as low-priority BestEffort work. During a big sale, browsing traffic soars, recommendation jobs give way automatically, and checkout stays steady.

Financial trading platforms

Trading firms rely on Guaranteed pods with whole-number CPU requests and the static CPU manager to give market gateways dedicated cores. Predictable latency is worth the reserved capacity, because a few extra milliseconds can mean lost trades.

Streaming and media services

Video platforms run encoding workers as Burstable or BestEffort pods on cheaper interruptible machines, while the services that authenticate viewers and deliver playlists run as Guaranteed. If encoding nodes are reclaimed, jobs restart with no viewer impact.

Healthcare data platforms

Hospitals and health-tech companies keep patient-record services Guaranteed, while analytics and reporting jobs run as lower-priority Burstable workloads. Resource quotas per department keep one team’s heavy report from starving another team’s clinical tools.

Machine learning platforms

Teams training large models use Guaranteed pods so that long runs are not disrupted by neighbours, and run notebooks and experiments as BestEffort pods with strict quotas. Data scientists get freedom to explore, and training runs stay stable.

Managed platform teams

Internal platform groups give every product team a namespace with defaults, a quota and a documented class policy. The result is that new teams get safe behaviour without learning every detail, and the platform team can predict how the cluster behaves under stress.

“Classes turn a cluster from a crowd into an orderly queue, and they do it before the crowd shows up.”

24Frequently Asked Questions

Q1Can I set a pod’s QoS class directly?

No. Kubernetes derives the class from the requests and limits of the containers. To change the class, change those settings and recreate the pod, because the class cannot be edited on an existing pod.

Q2What exactly makes a pod Guaranteed?

Every container, including init and sidecar containers, must have CPU and memory limits and requests, with each request equal to its limit. If a container sets only limits, the requests default to the same values.

Q3Is a Burstable pod risky?

It is a sensible default for most services. The risk depends on how far its usage rises above its request, because that gap decides how likely it is to be chosen during pressure. Honest requests keep that risk small.

Q4Will Guaranteed pods never be evicted?

They will not be evicted because of another pod’s consumption. They can still be removed if node agents exceed their reserved resources and nothing less important remains, and they can still be OOMKilled if they exceed their own memory limit.

Q5Does the scheduler consider QoS classes?

No. The scheduler checks that the sum of requests fits the node’s allocatable resources. It does not use limits or classes for placement. Priority, however, does influence scheduling and preemption.

Q6What is the difference between OOMKilled and Evicted?

OOMKilled means the Linux kernel stopped a container’s process for lack of memory. Evicted means the kubelet removed a whole pod to relieve node pressure. They have different causes, different messages and different recovery behaviour.

Q7Should I always set CPU limits?

Not necessarily. CPU limits protect neighbours and are required for Guaranteed, but they can cause throttling that hurts latency. Many teams set CPU requests and memory limits, and add CPU limits only where there is a clear reason. Measure throttling to decide.

Q8Why did my Guaranteed pod become Burstable?

Most often because a container added by a mesh, security or logging tool lacks matching limits and requests, or because a LimitRange changed a value. Inspect all containers on the running pod, not just your manifest.

Q9Do QoS classes apply to ephemeral storage and GPUs?

No. The class is computed only from CPU and memory. Ephemeral storage has its own requests, limits and eviction rules, and devices such as GPUs are handled through extended resources.

Q10What is the relationship between priority and QoS?

They are independent. Priority says how important a pod is for scheduling, preemption and eviction ranking. QoS describes how firmly resources are reserved and influences kernel kill preference. Align them for important workloads.

Q11Can I resize a running pod and change its class?

No. In-place resizing, where available, must keep the same QoS class. If you want a different class, replace the pod with a new one that has the new settings.

Q12Why does my node look empty but refuse new pods?

The scheduler counts requests, not usage. A node whose pods have requested all its allocatable room is full on paper, even if real usage is low. Right-sizing requests frees that room.

Q13Is BestEffort ever a good idea?

Yes, for disposable and experimental work that can be stopped without harm, ideally with low priority and a quota on how many such pods a namespace may run. It is a poor choice for anything users depend on.

Q14How do autoscalers interact with requests?

Horizontal scaling by utilization measures usage against requests, so pods without requests cannot use it. Node autoscalers add machines when requests cannot be placed. Honest requests make both work properly.

Q15How can I enforce good resource settings across many teams?

Combine a LimitRange for defaults, a ResourceQuota for budgets, and an admission policy that rejects production pods without memory limits. Add alerts for unexpected BestEffort pods and a regular review of usage against requests.

25Summary and Key Takeaways

Kubernetes nodes are shared and deliberately overcommitted, so there will be moments when the promises made cannot all be kept. Quality of Service classes are the plan that decides what happens in those moments. Guaranteed pods have reserved exactly what they use and are protected most, Burstable pods have a reserved floor and room to stretch, and BestEffort pods have reserved nothing and give way first. The class is derived automatically from requests and limits, turned into control groups and kernel scores, and combined with priority and usage to guide the kubelet’s evictions. Treat the numbers you write as promises, keep them honest, and use classes to spend protection where an outage would hurt and to share capacity everywhere else.

Key Takeaways

  • Three derived classes — Guaranteed, Burstable and BestEffort are computed from CPU and memory requests and limits across every container, and cannot be set directly or changed on a running pod.
  • Requests drive scheduling, limits drive enforcement — the scheduler counts only requests, while the kernel enforces limits through throttling for CPU and kills for memory.
  • The kernel and kubelet use classes differently — QoS shapes the control group layout and OOM scores, while eviction ranks pods by usage above requests, then priority, then size of overuse.
  • Priority is separate from QoS — align them for important workloads, and never pair high priority with a tiny memory request.
  • Memory is unforgiving, CPU is flexible — always set memory limits, and decide on CPU limits deliberately using throttling data, remembering that Guaranteed requires them.
  • Guardrails beat good intentions — LimitRange defaults, ResourceQuota budgets and admission policies prevent accidental BestEffort pods and noisy neighbours.
  • Observe and revisit — watch throttling, working set against limit and node pressure, check the class on running pods, and review requests regularly as workloads change.