CPU Requests in Kubernetes: what they really are
- date
- category
- Containers
- also in
- Capacity & Performance
- reading
- 4 min / 813 words
In the previous parts we established two things.
First, a process does not "own CPU" - the Linux scheduler hands it execution time.
Second, cgroups let you group processes and give those groups relative weights when accessing the processor.
Only now does requests.cpu start to make sense.
The most common wrong model looks like this:
requests.cpu: 500m
meaning:
the container gets half a CPU
No.
A CPU request is first of all a declaration of demand. Kubernetes uses that declaration to decide where a Pod may be started. Later the same information also influences the workload's relative CPU weight.
It is still not a limit.
What 500m means
CPU in Kubernetes is expressed in CPU units:
1 CPU = 1000m
500m = 0.5 CPU
250m = 0.25 CPU
On a physical machine 1 CPU corresponds to one logical CPU as seen by the system. On a VM it will be a vCPU. Kubernetes treats that value as an absolute amount of the resource for accounting purposes.
Example:
resources:
requests:
cpu: 500m
therefore means:
I declare demand for 0.5 CPU
It does not mean:
cap me at 0.5 CPU
That distinction is crucial.
The request's first use: kube-scheduler
Assume a node:
Allocatable CPU: 8
Pods already scheduled on it:
Pod A: request 2 CPU
Pod B: request 3 CPU
Pod C: request 2 CPU
SUM = 7 CPU
Another one arrives:
resources:
requests:
cpu: "2"
For kube-scheduler the arithmetic looks like this:
7 + 2 = 9 CPU
The node only has:
8 CPU Allocatable
so the Pod cannot be scheduled there.
The most important detail: the scheduler does not have to look at current CPU usage.
The node may right now be using:
0.8 CPU
and still reject the Pod.
The scheduler compares requests, not momentary CPU usage. Kubernetes does this deliberately - a request is meant to represent capacity that must be accounted for in case the workload really needs it.
So we can say:
request = a reservation in kube-scheduler's model
But you have to be careful with the word "reservation".
The scheduler reserves capacity, the kernel does not set CPU time aside
Assume:
resources:
requests:
cpu: 500m
Once the container starts, the kernel does not create anything like this:
every second:
500 ms -> this container
500 ms -> the rest of the system
There is no statically reserved pool of 500m waiting only for this workload.
If the workload does nothing, the CPU can be used by somebody else.
And if the workload wants more CPU and the processor is free, it can use more than its request.
Example:
request = 500m
usage = 1800m
is perfectly normal.
A request is not a ceiling.
The request's second use: relative share of CPU
Here we use the mechanism from the previous article.
Assume two workloads:
A request = 250m
B request = 750m
On the resource management side, request values are used to assign the corresponding CPU weights.
Historically, in cgroup v1, Kubernetes converted a request into cpu.shares:
cpu.shares = milliCPU * 1024 / 1000
so roughly:
250m -> 256 shares
750m -> 768 shares
which gives a proportion of:
1 : 3
In the cgroup v2 world the corresponding mechanism is cpu.weight; the exact conversion also depends today on the OCI runtime layer.
What matters most, though, is not the specific number but the mechanism:
larger request
|
v
larger relative CPU weight
What that gives in practice
We have one available CPU.
Both workloads are constantly runnable:
A request = 250m
B request = 750m
Because both want the processor all the time, we have contention.
Simplified, their shares may look like this:
A ~ 25%
B ~ 75%
Now B stops doing work.
Only A is left.
Can A still use at most 25% of the CPU?
No.
It can use practically:
A ~ 100%
The 250m request does not cap it at 25%.
That is precisely the consequence of the weight mechanism.
A request influences the split when competition exists.
Why a request is not "minimum CPU"
You often meet this simplification:
request = minimum
limit = maximum
For CPU the word "minimum" is dangerous.
requests.cpu: 500m does not mean the process unconditionally receives at least:
500 ms CPU time
in every second.
A request influences resource accounting and priority during competition, but we are still talking about a shared scheduler.
It is much better to think:
request =
declared demand
rather than:
request =
a hard guaranteed slice of the processor
What if the request is too low
Assume a node:
16 CPU
We start 16 Pods.
Each declares:
request = 500m
The scheduler sees:
16 x 0.5 CPU = 8 CPU
So from its point of view the node still has plenty of free capacity.
But under production traffic every Pod wants to use:
2 CPU
The real demand is:
16 x 2 CPU = 32 CPU
on a node that has:
16 CPU
The scheduler did nothing wrong.
It received a declaration of:
500m
and made its placement decision on that basis.
It is the workload that consumes far more than we declared.
A request is therefore not just a technical setting.
It is an input to the capacity model of the whole cluster.
What if the request is too high
The opposite case is possible too.
The application really uses:
200m
but we declare:
request = 2 CPU
For the scheduler every replica then occupies:
2 CPU capacity
even if it almost never uses it.
The effect can be simple:
fewer Pods per node
more nodes
worse bin packing
So a request is a trade-off.
Too low, and the scheduler overestimates available capacity.
Too high, and we needlessly leave that capacity unused.
The most important distinction
A CPU request exists in two worlds at once.
Kubernetes
requests.cpu
|
v
resource accounting
|
v
the decision whether the Pod fits on a node
Linux
requests.cpu
|
v
CPU shares / weight
|
v
the workload's relative position during contention
That leads to a simple model:
requests.cpu: 500m
does not mean:
you have half a core
It rather means:
Kubernetes:
count me as a workload needing 0.5 CPU
Linux:
under competition, take into account the weight
derived from my declared request
And most importantly:
request != limit
A workload with a 500m request may consume far more than 500m if CPU is available.
A request describes declared demand and a relative share under competition.
It does not define the maximum amount of CPU a process may use.