Resource Requests and Limits in Kubernetes
A plain-English guide to telling Kubernetes how much CPU and memory your containers need, how much they may use, and what happens when they ask for too much.
Imagine ten families moving into the same apartment building on the same day. Nobody has told them how much water, electricity, or parking each family may use. Within a week, one family runs a huge workshop in the basement, drains the shared water tank, and leaves everyone else with dry taps. Nobody was cruel. There were simply no rules.
A Kubernetes cluster is exactly such a building. Many applications, written by many different teams, share the same machines. Without rules, one greedy or buggy application can starve its neighbors, slow the whole cluster, or crash a machine. Kubernetes solves this with two simple ideas: requests and limits. A request says how much an application needs to run well. A limit says how much it is allowed to use at most.
In this guide you will learn what these two settings mean, how Kubernetes uses each one, and what happens when an application goes over its allowance. You will also learn how to choose sensible numbers, and how the same numbers quietly control scheduling, autoscaling, and even your cloud bill. No earlier knowledge is needed. Every new word is explained the first time it appears.
1Why Shared Machines Need Rules
Before we learn the settings, let us understand the problem they were invented to solve.
A node is one machine in a Kubernetes cluster. It has a fixed amount of processing power, called CPU, and a fixed amount of working memory, called memory or RAM. Kubernetes places many small programs, packed in containers, onto each node so that the machine is used well. This is efficient, but it creates a sharing problem.
Suppose a node has four CPUs and sixteen gigabytes of memory. If ten containers each try to use as much as they can, they will fight. Some programs will run slowly. Others may be killed by the operating system when memory runs out. The worst part is that the fight is unpredictable: a program that worked yesterday might fail today because of a noisy neighbor.
Picture a shared kitchen in a student hostel. If nobody books time on the stove, the loudest and fastest student cooks all evening, and everyone else eats cold food. A booking sheet fixes this. Each student writes down how long they need the stove, and the warden also sets a maximum time per person. The booking is the request. The maximum is the limit.
The noisy neighbor problem
The technical name for this problem is the noisy neighbor problem. One container on a node consumes so much of a shared resource that other containers suffer. It is common in every shared system, from apartment buildings to cloud data centers. Kubernetes cannot remove the sharing, but it can make sharing fair and predictable.
Unpredictable speed
An application is fast when neighbors are quiet and slow when they are busy, and nobody can explain why.
Surprise crashes
A neighbor uses up memory and the machine starts killing programs, sometimes the wrong ones.
Blind placement
Kubernetes cannot know how big your program is, so it may pack too many onto one node or waste whole machines.
Unfair cost
No team can be charged fairly, because nobody knows who used what.
You can run a cluster without setting requests or limits at all, and many beginners do. It works while the cluster is quiet. The trouble starts later, when it is busy and hard to fix.
2What Are Requests and Limits?
A request is a promise Kubernetes makes to your container. A limit is a ceiling your container must respect.
Every container in a pod can be given a request and a limit for each resource. The two settings sound similar, but they answer two very different questions.
Request: the minimum
- How much does this container need to run well?
- Used by the scheduler to choose a node.
- Space that is reserved for the container on that node.
- Also decides who is treated first when resources are scarce.
Limit: the maximum
- How much may this container use at most?
- Enforced on the node while the container runs.
- Going over has consequences: slowdown for CPU, being killed for memory.
- Protects neighbors from a greedy container.
Think of a restaurant. When you book a table for four, that is your request: the restaurant holds four seats for you, whether or not your friends all show up. The restaurant also has a rule that no group can bring more than eight people. That is the limit. You may use anything from four to eight seats when the restaurant has room, but you are never guaranteed more than four, and you are never allowed more than eight.
What it is, why it exists, where it is used
What it is: two numbers per resource, per container, that describe the minimum needed and the maximum allowed.
Why it exists: so the scheduler can place pods wisely, the node can enforce fairness, and the cluster can survive greedy programs.
Where it is used: in every production cluster. It influences scheduling, autoscaling, eviction, cost reports, and quota systems.
Requests and limits are set per container, not per pod. A pod’s total is the sum of its containers. Forgetting this leads to pods that are much bigger than anyone intended.
3A Quick Kubernetes Refresher
A few basic words are enough to follow the rest of this guide.
Kubernetes is a system that runs your containers across many machines and keeps them healthy. You say what you want, and Kubernetes makes it true and keeps it true. Here are the parts that matter for resources.
Cluster
The whole group of machines managed together. Think of it as the entire apartment building.
Node
One machine in the cluster. Each has a fixed amount of CPU and memory. These are the individual flats.
Pod
The smallest unit Kubernetes runs. A pod holds one or more containers that live and die together.
Scheduler
The part of Kubernetes that decides which node should run each new pod. It reads the requests.
kubelet
The manager program on every node. It starts containers and makes sure they obey their limits.
cgroups
A feature of the Linux operating system that measures and limits what a group of programs may use. Kubernetes relies on it to enforce limits.
Two kinds of resources
Kubernetes treats resources in two families, and the difference is the key to almost every surprise later in this guide.
A compressible resource can be squeezed without breaking anything. CPU is compressible. If a program wants more CPU than it can get, it simply runs slower. It waits its turn.
An incompressible resource cannot be squeezed. Memory is incompressible. If a program needs memory that is not available, nothing can be slowed down to make up for it. Something must be stopped. Remember this pair of words, because they explain why CPU and memory behave so differently when a limit is crossed.
4Architecture and Components
Several parts of Kubernetes cooperate to turn two numbers in a file into real behavior on a real machine.
The diagram below follows a pod from the moment you submit it until it runs inside its boundaries. Notice that different parts read different numbers. The scheduler cares about requests. The node cares about limits. Autoscalers care about actual usage compared with requests.
flowchart LR
Dev["Developer submits a pod with requests and limits"] --> API["API Server"]
API --> Adm["Admission checks with LimitRange and ResourceQuota"]
Adm --> Sched["Scheduler"]
Sched -->|picks a node with enough room| Kubelet
subgraph Node["Worker Node"]
Kubelet["kubelet"] --> Runtime["Container runtime"]
Runtime --> CG["Linux cgroups"]
CG --> Cont["Container"]
end
Metrics["Metrics server"] -.->|reads usage| Kubelet
Auto["Autoscalers"] -.->|reads usage| Metrics
Meet the components
API server
Receives your pod description, stores it, and hands it to the other parts. It is where defaults are filled in and rules are checked.
Admission rules
Two special namespace rules, LimitRange and ResourceQuota, can fill in missing numbers or reject pods that ask for too much.
Scheduler
Adds up the requests of pods already on each node and only places a new pod where its requests still fit.
kubelet
Translates requests and limits into settings for the operating system and watches over the node’s health.
Container runtime and cgroups
Actually create the container and apply the fences that keep it within its limits.
Metrics server
Collects live CPU and memory usage from the nodes. It powers the top command and autoscalers.
The scheduler works from the numbers you wrote down, not from what programs really use. This one fact explains many “why is my pod not scheduled?” mysteries.
5CPU: Measuring and Limiting Processing Power
CPU is measured in a simple unit, and limiting it works very differently from limiting memory.
How CPU is counted
Kubernetes measures CPU in cores. One CPU means one full core, whether it is a physical core or a virtual one in the cloud. You can ask for fractions. Because decimals are awkward, Kubernetes also lets you write in millicores, where a thousand millicores make one core.
| You write | It means | Plain meaning |
|---|---|---|
| 1 | One core | One whole core, always available |
| 500m | 500 millicores | Half a core |
| 250m | 250 millicores | A quarter of a core |
| 0.1 | 100 millicores | One tenth of a core |
| 2 | Two cores | Two whole cores |
The smallest amount you can ask for is one millicore. Half a core does not mean the program only runs half the time on a slow chip. It means the program is entitled to the equivalent of half a core’s worth of time.
What a CPU request does
The CPU request is turned into a weight for sharing. When the node has spare CPU, a container may use much more than it requested, and nobody minds. When the node is busy and many containers want CPU at once, the operating system divides the processor in proportion to their requests. A container that requested twice as much gets twice the share. This is how a request becomes a real, guaranteed minimum under pressure.
What a CPU limit does
A CPU limit is a hard ceiling, enforced by time slicing. The operating system works in short windows of one hundred milliseconds. A limit of half a core means the container may run for fifty milliseconds of CPU time in each window, counted across all its threads together. When the fifty milliseconds are used, the container is paused until the next window begins. This pausing is called throttling.
Imagine a road with a toll gate that lets each company send only fifty trucks through every hundred seconds. If a company has ten trucks that all arrive at once, they may spend the whole allowance in the first ten seconds, and then sit idle for ninety seconds, even though the road is empty. That is why a program with many threads can feel slow under a CPU limit even when the average usage looks low.
Throttling does not kill the program and does not write an error. It just makes the program slower, in bursts. Requests may look slow, timeouts may appear, and no message tells you why. You have to look at throttling numbers to see it.
6Memory: The Resource That Cannot Bend
Memory is counted in bytes, and going over the limit has a much sharper consequence than going over a CPU limit.
How memory is counted
Memory is written in bytes, with helpful suffixes. Two families exist, and confusing them is a classic beginner mistake. The binary family uses suffixes like Ki, Mi, and Gi, which are powers of 1,024. The decimal family uses K, M, and G, which are powers of 1,000. They differ by a few percent. Most people prefer Mi and Gi, because they match how computers actually count.
| You write | It means | Note |
|---|---|---|
| 128Mi | About 134 million bytes | A common size for a small helper program |
| 512Mi | Half a gibibyte | Typical for a small web service |
| 1Gi | About 1.07 billion bytes | Typical for a medium service |
| 500M | Exactly 500 million bytes | Slightly less than 500Mi |
| 400m | Four tenths of one byte | Almost always a typing mistake |
A small letter “m” means “milli” in Kubernetes, not “mega”. Writing 400m for memory asks for less than one byte. The container then gets killed almost at once. If you mean four hundred megabytes, write 400Mi.
What a memory request does
The memory request is mainly used for placement. The scheduler makes sure that the node has that much memory left unreserved. The request is not a fence at runtime. A container may use more than it requested if memory is free. But the request has another job: when the node runs short of memory, containers that use more than they requested are the first candidates to be removed.
What a memory limit does
A memory limit is a hard wall. If a container tries to use more memory than its limit, the operating system’s memory guard steps in and kills the offending process. Kubernetes reports this as OOMKilled, where OOM stands for out of memory. The container is then restarted according to the pod’s restart rules. Its exit code is usually 137, a number worth remembering because it appears in many incident reports.
CPU is like a queue at a counter: if too many people arrive, everyone waits a little longer. Memory is like a suitcase: if you keep stuffing things in beyond its size, it does not get slower. It bursts. The airline staff then remove the bag from the flight. The container is that bag.
flowchart TD
U["Container uses more than its request"] --> Q1{"Which resource"}
Q1 -->|CPU| C1{"Above its limit"}
C1 -->|Yes| T["Throttled and slowed but keeps running"]
C1 -->|No| OK1["Fine if CPU is free and shares fairly when busy"]
Q1 -->|Memory| M1{"Above its limit"}
M1 -->|Yes| K["OOMKilled so the container is killed and restarted"]
M1 -->|No| M2{"Node under memory pressure"}
M2 -->|Yes| E["Pod may be evicted and over-request pods go first"]
M2 -->|No| OK2["Keeps running"]
7Beyond CPU and Memory: Storage and Special Devices
CPU and memory get all the attention, but a container can also fill a disk or need special hardware.
Ephemeral storage
Every container writes some files while it runs: temporary files, logs, caches, and scratch space. This space is called ephemeral storage because it disappears when the pod goes away. It lives on the node’s disk, which is shared by everyone. A single container that fills the disk with logs can make the whole node unhealthy.
You can give ephemeral storage a request and a limit, just like CPU and memory. The request helps the scheduler avoid nodes with little free disk. The limit protects the node: if a pod writes more than its limit, Kubernetes removes the pod. Unlike CPU, there is no gentle slowdown. It is closer to the memory case.
Extended resources such as GPUs
Some nodes have special hardware, most famously graphics processors used for machine learning. Kubernetes handles these as extended resources. A device plug-in announces the hardware to the cluster, and pods ask for it by name and count. There are some special rules: devices are counted in whole numbers, they cannot be shared in fractions by default, and the amount is normally written as a limit, which then also serves as the request.
| Resource | Squeezable? | When the limit is crossed | Typical unit |
|---|---|---|---|
| CPU | Yes, compressible | Container is throttled | Cores or millicores |
| Memory | No, incompressible | Container is killed and restarted | Mi or Gi |
| Ephemeral storage | No | Pod is evicted | Mi or Gi |
| GPU and other devices | No | Not possible to exceed. Pod stays pending until available | Whole devices |
If your application writes logs to files inside the container, give it an ephemeral storage limit. A runaway log file is a very common way for one pod to hurt a whole node.
8How the Scheduler Uses Requests
The scheduler is a bookkeeper. It counts reservations, not real usage.
When a new pod arrives, the scheduler looks at each node and asks a simple question: after adding this pod’s requests to the requests of the pods already there, do we still stay within the node’s allocatable capacity? If yes, the node is a candidate. If no, the node is skipped. Among the candidates, the scheduler picks the best one using other rules like spreading and preferences.
flowchart TD
N1["Node A has 4 CPU allocatable and 3.5 CPU already requested"] --> R1{"New pod requests 1 CPU. Does it fit"}
R1 -->|No because only 0.5 CPU is unreserved| X1["Node A is skipped"]
N2["Node B has 4 CPU allocatable and 2 CPU already requested"] --> R2{"New pod requests 1 CPU. Does it fit"}
R2 -->|Yes because 2 CPU is unreserved| OK["Pod can be placed on Node B"]
A hotel booking desk counts reservations, not who is actually in the room. If a hotel has a hundred rooms and a hundred are reserved, the desk turns new guests away, even if half the guests are out sightseeing and the rooms are empty right now. Reservations are promises, and the hotel must be able to honor them all at once.
Reserved is not the same as used
This creates two opposite problems. If you request far more than you use, the node looks full while its processors sit idle. You pay for machines that do nothing. If you request far less than you use, the scheduler crams too many pods on one node, and when they all become busy, they compete and suffer. Right-sizing your requests is therefore the heart of good resource management.
When nothing fits
If no node can satisfy a pod’s requests, the pod stays in the Pending state, and its event log says something like “insufficient cpu” or “insufficient memory”. This is not an error in the usual sense. It is Kubernetes telling you honestly that there is no room. The cure is one of three things: ask for less, free up space by removing other pods, or add a bigger or additional node.
A request larger than the biggest node in your cluster can never be satisfied. The pod will stay pending forever. Check that your largest single request fits on your largest node type.
9Node Capacity and Allocatable Space
A node never offers all its resources to your pods. Some are kept for the machine itself.
A machine with sixteen gigabytes of memory does not have sixteen gigabytes to give your applications. The operating system needs some. Kubernetes’ own helper programs need some. And the node keeps a safety buffer so that it can react before memory runs out completely. What remains for pods is called allocatable.
| Part | What it is | Who uses it |
|---|---|---|
| Capacity | Everything the machine physically has | The starting point |
| System reserved | A slice kept for the operating system | Login services, kernel work, monitoring agents on the host |
| Kube reserved | A slice kept for Kubernetes programs on the node | The kubelet and the container runtime |
| Eviction buffer | A safety margin below which the node starts removing pods | The kubelet’s protection system |
| Allocatable | Capacity minus the three parts above | The pods’ requests must fit inside this |
On small machines this hidden reservation is a noticeable share of the total. A tiny node might offer only around three quarters of its memory to pods. On very large machines the share is smaller. When you plan how many pods fit, always start from allocatable, not from the number on the machine’s price sheet.
Ask Kubernetes to describe a node, and you will see both capacity and allocatable side by side, plus how much of the allocatable space is already requested. This one screen answers most “why does it not fit” questions.
10How It Works Inside the Node
The numbers you write become settings in the Linux kernel. Following that path removes all the mystery.
sequenceDiagram
participant D as Developer
participant A as API Server
participant S as Scheduler
participant K as kubelet
participant R as Runtime and cgroups
D->>A: Create pod with requests and limits
A->>A: Fill in defaults and check quota
S->>A: Pick a node with enough unreserved room
K->>A: See the pod assigned to this node
K->>R: Create the container with CPU weight, CPU time limit, memory limit
R-->>K: Container running inside its boundaries
K->>A: Report status and usage
Defaults and rules are applied
When the pod is submitted, the API server fills in any missing numbers from the namespace’s default rules and rejects the pod if it breaks a quota or a minimum or maximum.
The scheduler reserves room
It adds the pod’s requests to the node’s booked total. From this moment, that space is promised.
The kubelet creates a group for the pod
On the node, the kubelet makes a control group for the whole pod and a smaller one for each container. Control groups are the Linux way of measuring and limiting resource use.
CPU request becomes a weight
The container’s CPU request is written as a sharing weight. It only matters when several containers compete.
CPU limit becomes a time budget
The limit is written as a quota of CPU time per hundred milliseconds. Once used, the container waits.
Memory limit becomes a hard wall
The memory limit is written into the group. If the container’s processes reach it and cannot free anything, the kernel’s memory guard kills a process inside the container.
Priority for the guard is set
Each container gets a score that tells the kernel who to sacrifice first if the whole machine runs out of memory. The score is derived from the pod’s quality class, which is the topic of the next chapter.
Kubernetes itself does not police your containers moment by moment. It configures the operating system once, and the kernel does the enforcing at full speed.
11Quality of Service Classes
Kubernetes sorts every pod into one of three classes, based purely on how you set requests and limits.
You never choose the class directly. Kubernetes works it out from your numbers. The class matters most when the node is in trouble, because it decides who is protected and who is removed first.
flowchart TD
S["Look at every container in the pod"] --> Q1{"Is any request or limit set anywhere"}
Q1 -->|None at all| BE["BestEffort"]
Q1 -->|Yes| Q2{"Does every container have CPU and memory limits equal to requests"}
Q2 -->|Yes| G["Guaranteed"]
Q2 -->|No| B["Burstable"]
| Class | How to get it | Protection | Best for |
|---|---|---|---|
| Guaranteed | Every container sets CPU and memory, and limit equals request | Highest. Removed last | Databases, critical services |
| Burstable | Some requests or limits set, but not the strict Guaranteed pattern | Medium. Depends on how far above request it is | Most ordinary services |
| BestEffort | No requests and no limits at all | Lowest. Removed first | Throwaway or low-value jobs |
Think of an airline. Guaranteed passengers hold a confirmed seat and are the last to be bumped. Burstable passengers hold a ticket but may be moved if the plane is overbooked, and those who took more than their share of luggage allowance go first. BestEffort passengers are on standby: they fly only if there is room, and leave the plane first if trouble appears.
What happens during memory trouble
When a node begins to run out of memory, the kubelet steps in before the kernel has to. It starts evicting pods to free space. It first picks pods whose usage exceeds their requests, then considers pod priority, and among those it takes the ones with the largest overuse first. A BestEffort pod has no request, so any usage counts as overuse, which is why these pods vanish first. A Guaranteed pod never uses more than its request, so it sits at the back of the queue.
If memory runs out so fast that the kubelet cannot react, the kernel’s memory guard takes over and picks victims by that score we saw earlier. The same ordering applies: BestEffort first, Burstable in between, Guaranteed last.
Setting limits equal to requests gives the strongest protection, but it also gives up flexibility. A Guaranteed pod cannot borrow spare CPU beyond its number. Choose this deliberately for the pods that matter most.
12Guardrails: LimitRange and ResourceQuota
Asking every developer to remember to set sensible numbers does not scale. Namespace-level rules do the remembering for you.
A namespace is a named section of a cluster, often given to one team or one project. Kubernetes lets an administrator attach two kinds of rules to a namespace. They sound alike but do very different jobs.
LimitRange
Sets defaults and boundaries for each individual container or pod. It can fill in missing requests and limits, set a minimum, set a maximum, and limit how far a limit may exceed its request.
ResourceQuota
Sets a budget for the whole namespace. It caps the total of all requests, all limits, and even counts of objects such as pods or claims.
Imagine an office building with several tenant companies. The LimitRange is the house rule for each desk: every desk gets a standard chair and a monitor unless the tenant asks otherwise, and no desk may have more than two monitors. The ResourceQuota is the tenant’s total budget: this company may use at most forty desks in the building, no matter how it divides them.
| Question | LimitRange | ResourceQuota |
|---|---|---|
| Scope | Each container or pod | The whole namespace |
| Fills in missing values? | Yes, defaults for requests and limits | No |
| Rejects too-large requests? | Yes, above the maximum | Yes, when the total would pass the budget |
| Stops too many pods? | No | Yes, it can cap object counts |
| Typical owner | Platform team | Platform team, per team budget |
How they work together
If a namespace has a quota on CPU or memory, then every new pod there must state its requests and limits for those resources, or the API server refuses it. A LimitRange with defaults solves this neatly: developers who forget still get pods with sensible numbers, and the quota can count them. The two rules form a pair.
These rules check pods when they are created. Changing a LimitRange does not alter pods that already exist. Old pods keep their old numbers until they are recreated.
13Pods With Many Containers
A pod is more than one container, and its total size is not always a simple sum.
Many pods hold a main application and one or more helpers, called sidecars. A sidecar might collect logs, handle encryption, or route network traffic. Each container has its own requests and limits, and the pod’s total is built from them.
The basic rule: add them up
For containers that run side by side, the pod’s request is the sum of the containers’ requests. A pod with a main container asking for half a core and a helper asking for a quarter of a core needs three quarters of a core. The scheduler uses this total. Small helpers therefore matter: ten pods each carrying an oversized sidecar can quietly consume a whole node.
Init containers: a special case
An init container is a short-lived helper that runs to completion before the main containers start. It might prepare data or wait for a database to be ready. Because it runs first and then exits, it does not add to the total in the same way. The pod’s effective request is the larger of two numbers: the sum of all main containers, or the biggest single init container. This is like a moving crew that arrives, unpacks, and leaves before the residents move in. You only need room for whichever is bigger, the crew or the residents.
Extra costs hiding around a pod
Runtime overhead
Some sandboxed runtimes, used for stronger isolation, need extra CPU and memory just to run a pod. Kubernetes can add this fixed overhead to the pod’s total automatically.
Long-running helpers
Modern Kubernetes lets a helper be marked as a sidecar that starts before the main container and stops after it. Its resources count toward the pod for as long as it runs.
Resizing running pods
Recent Kubernetes versions can change a running container’s CPU and memory numbers in place, without restarting the pod. Availability depends on your version, so check your cluster’s documentation.
Review the sidecars in your cluster. A logging or proxy helper copied into every pod with generous defaults can add up to more resources than the applications it supports.
14Requests and Autoscaling
Your request numbers quietly steer every autoscaler in the cluster.
Kubernetes can grow and shrink automatically. There are three main kinds of automatic scaling, and all three depend on the numbers you set.
| Autoscaler | What it changes | How requests matter |
|---|---|---|
| Horizontal Pod Autoscaler | Number of pod copies | Utilization is measured as a percentage of the request. No request, no percentage |
| Vertical Pod Autoscaler | Size of each pod’s requests | It watches real usage and suggests better request numbers |
| Cluster autoscaler or node provisioner | Number of nodes | It adds machines when pods cannot be placed because requests do not fit |
The percentage trap
When you tell the horizontal autoscaler, “keep average CPU at seventy percent”, seventy percent of what? The answer is: seventy percent of each container’s CPU request. If you request a tiny amount, the pod looks overloaded at once and the autoscaler creates far too many copies. If you request a huge amount, the pod never looks busy, and the autoscaler never adds a copy even when users complain. The request number is therefore also a dial on your scaling behavior.
Imagine a supermarket that opens a new checkout lane whenever the queue exceeds seventy percent of what a cashier “should” handle. If the store defines a cashier’s normal load as five customers an hour, the lanes open constantly. If it defines it as five hundred, no new lane ever opens. The definition of “normal load” is the request.
How nodes are added
When pods stay pending because no node has room for their requests, a cluster autoscaler notices and asks the cloud for another machine. Once the machine joins, the pending pods are placed. So oversized requests do not just waste room on existing nodes. They also make the cluster buy more machines than needed.
Do not let two automatic systems fight. If the vertical autoscaler changes CPU requests while the horizontal autoscaler is using CPU percentage, their signals can chase each other. Use them together only with care, for example on different metrics.
15What Happens When a Container Goes Over
Every crossing of a boundary has a different symptom. Learning them is like learning to read a car’s dashboard lights.
CPU above the limit: throttling
The container is paused for the rest of the current time window. Symptoms are slow responses, timeouts, and uneven speed. No restart and no error message. The pod looks healthy.
Memory above the limit: OOMKilled
The container is killed at once. The pod’s status shows the reason OOMKilled and exit code 137. Kubernetes restarts the container, and if this repeats, it waits longer between tries.
Repeated kills: CrashLoopBackOff
After several quick failures, the pod enters a state named CrashLoopBackOff. This is the visible sign of a restart loop. The cause may be a memory limit that is simply too low for startup.
Node memory pressure: eviction
Even with no limit crossed, if the node itself runs short, the kubelet evicts whole pods. Over-request pods and low-priority pods go first. Evicted pods are recreated elsewhere if a controller owns them.
Disk above the limit: eviction
A pod that writes more ephemeral storage than allowed is removed, and its event log says so.
No room to start: Pending
If no node can hold the requests, the pod never starts. It waits, and its events explain which resource is short.
Why memory kills but CPU only slows
Remember the two families from the refresher. CPU time is renewed every instant, so a shortage can be handled by making programs wait. Memory is a stock that is filled and held. If a program holds memory and the limit is reached, there is nothing to wait for. The only way forward is to take something away, so the guard kills a process.
An OOMKilled container did not do anything “wrong” by itself. It simply asked for more than its allowance. The fix is either to raise the limit or to make the program use less, and finding out which requires measuring.
16Choosing the Right Numbers
There is no magic formula. There is a method: measure, choose, watch, adjust.
Beginners often copy numbers from a tutorial, and those numbers fit the tutorial’s toy program, not your real one. A sound approach follows four steps.
Measure real usage
Run the application under realistic load, in a test setting, and record how much CPU and memory it uses at quiet times, at typical times, and at peak times.
Set the request near typical use
Choose a request that covers everyday usage, for example a high percentile of normal load. This is the amount you want reserved for the container.
Set the limit with headroom
For memory, choose a limit comfortably above the peak, because going over means being killed. For CPU, decide your policy, discussed below.
Watch and adjust
Real traffic changes. Compare actual usage with your numbers every few weeks, and correct them. Automatic recommendation tools can help.
Different programs, different shapes
| Kind of workload | CPU behavior | Memory behavior | Sensible approach |
|---|---|---|---|
| Simple web service | Spiky with traffic | Steady with slow growth | Moderate request, memory limit above peak |
| Database | Steady with bursts | Large and grows with cache | Guaranteed class, limits equal to requests |
| Batch job | High for a short time | Depends on data size | Request what it needs while running, allow it to end quickly |
| Background worker | Low most of the time | Small and steady | Small request, small limit |
| Machine learning training | Very high, sustained | Very large | Dedicated nodes, requests near the true need |
The great debate about CPU limits
Experienced teams disagree about whether to set CPU limits at all. Both sides have good reasons, and a beginner should understand each.
Reasons to set CPU limits
- They protect neighbors from a runaway program.
- They make performance more predictable, because the container always runs at a similar ceiling.
- They are required in some shared clusters and by some quota setups.
- They make tests behave like production.
Reasons to skip them
- Throttling can slow programs even when the node has lots of idle CPU.
- Requests already guarantee a fair share under pressure.
- Unused CPU is wasted if a container cannot borrow it.
- Throttling is hard to notice and easy to misdiagnose.
A common middle path is this: always set CPU requests, set a generous CPU limit for untrusted or shared environments, and monitor throttling either way. For memory the advice is simpler: always set a limit, and for important programs, make it equal to the request so that the pod is well protected and behaves predictably.
17Performance and Scalability
Resource settings are also performance settings. A wrong number can make a fast program slow.
Threads and throttling
A program with many threads can use up a small CPU limit very quickly. Imagine a limit of one core and a program with eight busy threads. All eight run at once and consume the hundred-millisecond budget in about twelve milliseconds. Then the program stalls for the remaining eighty-eight. Average CPU usage looks low, yet latency is terrible. Increasing the limit, or reducing the number of threads, often fixes a mystery slowdown.
Language runtimes need to know the limit
Some programming platforms decide how many threads to run, or how much memory to use, by looking at the machine they think they are on. If they see the whole node instead of their container’s slice, they overreach. Modern versions of popular runtimes read container limits correctly, but older versions may not. A program that sizes its memory pool as a fraction of the machine and ignores its container limit will be OOMKilled regularly. Always check how your runtime reads its limits, and leave headroom between the program’s own memory ceiling and the container’s limit for non-heap memory.
Pinning CPUs for special workloads
For latency-sensitive programs, Kubernetes has an optional feature called the static CPU manager policy. When a Guaranteed pod asks for whole cores, for example exactly two, it is given exclusive use of those cores. No other pod runs on them. This avoids the small delays caused by sharing and switching, at the price of less flexible packing.
Packing and overcommit
Requests decide how tightly pods are packed. Limits may add up to more than the node has. This deliberate gap is called overcommit. It works because pods rarely all peak at once. Overcommit on CPU is fairly safe since the worst outcome is slowness. Overcommit on memory is riskier since the worst outcome is kills and evictions. Many teams therefore overcommit CPU freely and memory cautiously.
Measure the fraction of time each container is throttled. A container that is throttled more than a few percent of the time is telling you its limit is tight.
18Reliability, Priority, and Headroom
Good resource settings are a quiet form of insurance against outages.
Priority and preemption
Not all pods are equally important. Kubernetes lets you assign a priority class to a pod. When the cluster is full, and an important pod is pending, the scheduler may remove lower-priority pods to make room. This is called preemption. It is separate from quality of service classes, but they work together: priority decides who gets a seat, and the service class decides who is removed first under memory pressure.
Headroom for failure
If you pack nodes to the very brim, then losing one node means its pods have nowhere to go. They sit pending until a new node is added. A sturdy cluster keeps spare room, often enough to absorb the loss of a node or a whole zone. This spare room is not waste. It is the cost of surviving failures without a wait.
Reserve failure capacity
Plan node counts so that losing one still leaves enough unreserved space to reschedule its pods.
Protect critical pods
Give essential pods the Guaranteed class and a high priority so they are evicted last and never wait long.
Spread replicas
Combine sensible requests with spreading rules so that copies of an application land on different nodes.
Limit disruption
Use disruption budgets so that maintenance never removes too many copies at once.
A cluster that looks fine on a quiet Tuesday can collapse on a busy Friday if too many pods depend on borrowed, unreserved capacity. Requests are what keep promises true on the worst day, not the average day.
19Security and Fair Sharing
Resource limits are also a defense. A program with no ceiling can be turned into a weapon against its neighbors.
In a cluster shared by many teams, or running code from customers, resource control is part of security. An attacker who takes over a container can try to use every bit of CPU and memory to starve other workloads. This is a form of denial of service. Limits contain the blast.
Always set memory limits
A container without a memory limit can push a whole node into trouble. Make limits mandatory in shared clusters.
Use quotas per namespace
A team’s budget stops one compromised or careless project from using the entire cluster.
Set maximums with LimitRange
Even inside a budget, cap the size of any single container, so nobody asks for an entire node.
Cap process counts
Runaway programs can also exhaust the number of processes allowed on a node. Cluster settings can limit this per pod.
Enforce with policy
Admission policies can reject pods that lack requests and limits, so the rule does not depend on memory or goodwill.
Separate sensitive workloads
For strong isolation, run critical or untrusted workloads on dedicated nodes rather than sharing with everyone.
Resource controls do not stop a break-in. They limit what an intruder or a bug can destroy. They are a seat belt, not a lock.
20Monitoring, Logging, and Metrics
You can only right-size what you can see. A few key graphs answer most questions.
Usage against request
Plot each container’s real CPU and memory beside its request. A wide gap shows waste. Usage above the request shows risk.
Usage against limit
Memory approaching its limit warns of coming OOM kills. Track the working set of memory, which is the number the memory guard cares about.
Throttling ratio
Container monitoring reports how many time windows were throttled out of the total. A high ratio means the CPU limit is hurting.
Node reserved versus used
Compare the sum of requests on each node with real usage. Big differences mean packing could improve.
Termination reasons
Pod status records why a container last ended. Count OOMKilled reasons across the cluster to find weak spots.
Node pressure conditions
Nodes report when they are under memory, disk, or process pressure. These are early warnings before evictions begin.
Alerts worth setting up
- Any container restarted because of OOMKilled.
- A container throttled for more than a small share of its time over an hour.
- A container whose memory stays above ninety percent of its limit.
- Pods pending for longer than a few minutes because of insufficient resources.
- A namespace using more than eighty percent of its quota.
- Nodes reporting pressure conditions, or pods being evicted.
Build one dashboard per application showing requests, limits, and real usage on the same chart. Most right-sizing decisions can be made by looking at it once a month.
21Advantages, Disadvantages, and Trade-offs
Resource settings bring order. They also bring decisions you cannot avoid.
Advantages
- Fair sharing. One program cannot starve the others.
- Smart placement. The scheduler can pack pods without guessing.
- Predictable behavior. Programs behave the same on quiet and busy days.
- Automation works. Autoscalers and quota systems rely on these numbers.
- Cost visibility. You can attribute cost to teams by what they reserve.
- Safer failures. Eviction is orderly instead of random.
Disadvantages
- Guessing is hard. Right numbers need measurement and time.
- Waste is easy. Padding requests to be safe wastes money.
- Failure can be sharp. Too tight a memory limit causes kills.
- Hidden slowness. Throttling is silent.
- Drift over time. Numbers that were right last year become wrong as traffic changes.
- Extra process. Teams must maintain and review the numbers.
The main trade-offs
Safety versus cost. Large requests keep applications safe and cost more. Small requests save money and raise the risk of noisy neighbors. There is no free choice; you pick a point on the line.
Predictability versus efficiency. Setting limits equal to requests makes behavior predictable but stops pods from borrowing spare capacity. Letting pods burst uses machines better but makes performance vary with the neighbors.
Strictness versus developer speed. Requiring numbers on every pod protects the cluster, and it also adds a step for developers who just want to try something. Namespace defaults soften this.
22Design Patterns and Anti-patterns
Some habits keep clusters healthy. Others create silent trouble.
Patterns that work
Namespace defaults
Give every namespace a default request and limit, so forgetful developers still produce well-behaved pods.
Tiered classes
Run critical services as Guaranteed, ordinary services as Burstable, and disposable jobs as low priority.
Recommendation loop
Run a recommendation tool in observe-only mode, review its suggestions, and apply them in scheduled changes.
Team budgets
Give each team a quota and show them their usage. People manage what they can see.
Dedicated node pools
Put heavy or sensitive workloads on their own node groups, so neighbors are predictable.
Load test before launch
Measure real usage under expected traffic, then set numbers, instead of guessing.
Anti-patterns to avoid
What people do
They deploy pods with nothing set, because it works during testing and nobody complains.
Why it hurts
Every such pod is BestEffort. The scheduler thinks it needs nothing and stacks them on one node. When load arrives, they fight and are the first to be evicted.
What to do instead
Set at least memory and CPU requests, and a memory limit. Use namespace defaults so it happens automatically.
What people do
They request very little to fit easily, and set enormous limits to be safe, for example a request of a hundred megabytes and a limit of eight gigabytes.
Why it hurts
The scheduler believes the pod is tiny and packs dozens onto one node. When they all grow toward their limits, the node runs out of memory and pods are evicted or killed unpredictably.
What to do instead
Keep the gap between request and limit modest, especially for memory. Set requests near real typical usage.
What people do
They copy resource values from an example, another team, or an old project, and never revisit them.
Why it hurts
The numbers fit some other program at some other time. Either money is wasted on idle reservations, or the program is constantly throttled or killed.
What to do instead
Measure your own application, set values from data, and review them on a schedule.
23Best Practices and Common Mistakes
A short list of habits prevents most resource problems.
Best practices
Always set memory requests and limits
Memory is the resource that kills. Give every container a request and a limit.
Always set CPU requests
Without them, scheduling is blind and fairness under pressure is lost.
Use binary units for memory
Write Mi and Gi. Avoid the lowercase m for memory.
Add defaults and quotas
Namespace rules protect the cluster from forgetfulness.
Review numbers regularly
Compare real usage to requests monthly and adjust.
Leave headroom for failures
Keep spare capacity so a lost node does not cause a long wait.
Common mistakes
Confusing request with limit
Thinking the request is a ceiling. It is a reservation. A container can use more than its request when space is free.
Typing the wrong unit
Writing 400m for memory, which means less than a byte, or writing 1000 for CPU instead of 1000m.
Ignoring startup spikes
Many programs use much more memory while starting than while running. A limit that suits steady state may kill the program at boot.
Sizing from averages
Setting numbers from average usage. Averages hide peaks, and peaks are what kill containers.
Forgetting sidecars
Adding up only the main container and being surprised that pods are bigger than expected.
Leaving limits unchanged after growth
Traffic grows, but the limits stay as they were, and one day the program gets throttled or killed at the busiest moment.
24Real-World and Industry Examples
The same ideas appear at every scale, from a small startup to a very large platform.
Cost control in the cloud
Cloud bills are driven by the number and size of machines. Because machines are added when requests do not fit, inflated requests are one of the biggest hidden costs in Kubernetes. Many organizations run regular right-sizing reviews, comparing real usage to requests, and often find that a large fraction of reserved capacity is never used.
Multi-team platforms
Internal platforms that host many product teams typically give each team a namespace with a quota and default limits. Teams see their own usage and cost, which encourages good habits without central policing.
Databases on Kubernetes
Teams running databases usually choose the Guaranteed class, with memory and CPU limits equal to requests. A database that is evicted or throttled at a bad moment is costly, so predictability wins over flexibility.
Batch and analytics jobs
Data processing jobs are often run as low-priority pods that can fill spare capacity. When the cluster is needed for user-facing services, the scheduler can preempt them. This raises overall usage without hurting important traffic.
Machine learning platforms
Training jobs request GPUs as whole devices and large amounts of memory. Dedicated node pools keep these expensive machines from being wasted on small pods, and quotas stop any one team from taking all the accelerators.
Managed cluster defaults
Managed Kubernetes services and node provisioning tools decide how many machines to start by reading pod requests. In these systems your numbers directly become your machine count, and therefore your invoice.
In every setting the story is the same: honest requests make placement and scaling work, and sensible limits make failures small.
25When It Does Not Work: A Troubleshooting Path
Match the symptom to its cause, then apply the fix.
| Symptom | Likely cause | What to check | Typical fix |
|---|---|---|---|
| Pod stays Pending | No node has enough unreserved room | The pod’s events, and each node’s requested versus allocatable | Lower the request, add nodes, or free space |
| Container restarts with OOMKilled | Memory limit too low or a leak | Last termination reason, memory graph before the kill | Raise the limit or fix the leak |
| Slow but healthy pod | CPU throttling | Throttled share of time | Raise or remove the CPU limit, reduce threads |
| Pod evicted | Node under pressure, or disk limit crossed | Eviction message and node conditions | Set proper requests, reduce usage, add capacity |
| Pod rejected on creation | Quota or LimitRange violation | The error text from the API server | Adjust the numbers or ask for a larger budget |
| Autoscaler scales wildly or never | Wrong request as the base for percentage | Request versus real usage | Correct the request |
Read the pod’s events first
Most resource problems announce themselves in the events: insufficient cpu, OOMKilled, evicted. Start there, not with guesses.
Compare usage with numbers
Look at live and recent usage beside the request and limit. The gap tells you which side to change.
Check the node
Is the node full of requests, or under pressure? A healthy pod on a struggling node still suffers.
Check namespace rules
A default from a LimitRange may have given your pod numbers you never wrote. Look at the final values the pod actually received.
Change one thing at a time
Adjust a single number, watch for a while, and only then adjust another. Changing many at once hides the cause.
26Frequently Asked Questions
Quick answers to the questions beginners ask most often.
27Summary and Key Takeaways
What to remember about requests and limits
- A request is a reservation; a limit is a ceiling. The scheduler uses requests to place pods. The node enforces limits while pods run.
- CPU bends, memory breaks. Going over a CPU limit slows the container through throttling. Going over a memory limit kills it with an OOMKilled result.
- Units matter. Use millicores for CPU and Mi or Gi for memory. A lowercase m on memory means less than a byte.
- Your numbers decide the service class. Guaranteed, Burstable, and BestEffort determine who is protected and who is evicted first under pressure.
- Requests steer autoscaling and cost. Percentage-based scaling and node additions both depend on them, so wrong requests waste money or hurt users.
- Guardrails scale the practice. LimitRange fills in defaults and boundaries, and ResourceQuota sets each namespace’s budget.
- Measure, set, watch, adjust. Set values from real data, monitor throttling and memory, and revisit the numbers as traffic changes.