0%
Containers and Kubernetes: From cgroups to Operators
What a Container Actually Is
Working with Docker
Kubernetes Architecture
Workloads
Networking and Storage
Extending Kubernetes
Requests, Limits and Quality of Service Classes
Two numbers per resource per container decide scheduling, runtime enforcement and survival under pressure, and they do so through completely different mechanisms. Nearly every resource problem in a cluster comes from treating them as one setting.
The request is a scheduling claim and a share
A request does two unrelated things, which is why it is the more consequential of the two.
It is the number the scheduler uses: a node is feasible only if its allocatable amount minus the sum of requests already assigned is at least the pod's request. Nothing about actual usage enters that calculation. And for the processor, the request is translated into a relative weight on the container's control group, which decides how contended time is divided.
So a request is both a reservation for placement purposes and a share for runtime purposes, and the second meaning only matters when the node is actually contended. On an idle node, a container with a tiny request can use every core.
The limit is a ceiling enforced by the kernel
A limit does one thing and it does it differently for each resource.
For the processor it becomes a quota per accounting period, enforced by the kernel suspending the container's threads once the quota is used until the period rolls over. For memory it becomes a hard maximum on the control group, enforced by the kernel refusing allocations and killing a process inside the group when it cannot reclaim.
The asymmetry is fundamental. Processor time is compressible: taking it away slows the container. Memory is not: taking it away is impossible, so the only enforcement available is killing something. Everything else in this lesson follows from that asymmetry.
The control group fields involved
Under the second generation of control groups, the mapping is small enough to memorise.
The processor request becomes a weight, roughly proportional to the requested millicores, which the kernel uses to divide runnable time between groups. The processor limit becomes a maximum expressed as a quota and a period. The memory limit becomes a maximum, and a throttling threshold below it is also set. The memory request is used for eviction accounting and, in the newer arrangement, as a reclaim protection floor.
Reading those values directly inside a container is the fastest way to confirm what the cluster actually applied, which is worth doing whenever observed behaviour disagrees with the manifest.
Why limits are invisible to the scheduler
One consequence catches every cluster eventually.
The scheduler reads requests only, so the sum of limits on a node can exceed its capacity by any amount. That is deliberate overcommitment: it lets containers use idle capacity, which is most capacity most of the time.
The hazard is that nothing checks whether the sum of limits is survivable. A node whose pods request four cores in total and are limited to forty can have every container throttled while the node sits at full utilisation, and a node whose memory limits sum to double its capacity will eventually kill containers. The requests are the promise; the limits are the hope.
The units and their traps
Two unit conventions cause real mistakes.
Processor quantities are in cores, with a milli suffix for thousandths, so one hundred millicores is a tenth of a core and one core is a thousand millicores. Memory quantities accept both power-of-ten and power-of-two suffixes, where a suffix ending in a single letter is decimal and one ending in an i is binary, so a mebibyte is larger than a megabyte by about five percent.
The common error is writing a bare number for memory, which means bytes, so a limit of five hundred is five hundred bytes and the container is killed instantly. The other is assuming the decimal and binary suffixes are interchangeable, which silently shifts a limit by seven percent at the gigabyte scale.
Resource granularity, and what a fraction means
One question comes up constantly and the answer is specific to each resource.
A fractional processor quantity is meaningful: a quarter of a core is a quarter of the available time per period, and a container with that limit on a sixteen core node still gets a quarter of one core's worth of time. Fractional memory is not meaningful in the same way, since memory is allocated in pages and the limit is simply a byte count.
Extended resources such as accelerators are integral and non-overcommittable: the request must equal the limit and fractions are rejected, because the underlying device cannot be divided by the kernel. That is why accelerator sharing requires a device plugin that presents fractions as separate countable units.
Where the numbers come from, in practice
It is worth stating plainly that most requests and limits in most clusters are guesses.
The usual provenance is a copied manifest, a round number, or a value chosen to stop a problem once and never revisited. The consequence is a cluster whose scheduling is driven by numbers unrelated to the workload, which presents as low utilisation with pods that cannot schedule.
The alternative is cheap: measure the ninety-fifth percentile of actual usage over a representative period, set the request near it, and set limits by the policy discussed later in this lesson. The measurement is a query, not a project, and it is the single highest-value piece of resource work available in most clusters.
Init containers and the pod's totals
One computation surprises people the first time they see a pod rejected.
A pod's effective request per resource is the larger of two things: the maximum request among its init containers, since those run one at a time, and the sum of requests across its regular containers, since those run together. Sidecars implemented as restartable init containers count in the sum rather than in the maximum, because they keep running.
So a pod with a heavy init container that needs eight gigabytes to migrate a schema needs a node that can accommodate eight gigabytes even though the application needs one, and that peak decides where the pod can be placed for its whole life.
The overhead nobody accounts for
One addition to a pod's totals exists and it matters on some clusters.
A runtime class can declare a pod overhead, which is added to the pod's effective requests and limits to account for the sandboxing cost of that runtime. For an ordinary container runtime it is zero or negligible; for a virtualised runtime it can be a couple of hundred megabytes and a fraction of a core per pod.
The reason to know about it is capacity planning on clusters using such runtimes, where many small pods pay the overhead repeatedly: a hundred pods with two hundred megabytes of overhead each consume twenty gigabytes before any application memory, which does not appear in any workload's manifest.
Resizing a running pod
One capability changes an old constraint and is worth knowing about.
Requests and limits were immutable on a running pod, so changing them meant recreating it, which is why vertical autoscaling caused restarts. In-place resizing allows them to be changed on a running pod, with the node agent updating the control group values and reporting the result.
The awkward case is reducing a memory limit, since memory already allocated cannot be taken back from a process, so a decrease may be reported as pending until usage falls below the new value. A resize policy per container also declares whether a change requires a restart, which matters for runtimes that read their memory allowance once at startup and would not notice an increase.
A strong answer separates the two mechanisms: a request is a scheduling claim and, for the processor, a relative weight that only matters under contention, while a limit is a kernel-enforced ceiling, a quota per period for the processor and a hard maximum for memory. It then names the asymmetry that explains everything else, which is that processor time is compressible so enforcement means slowing a container down, while memory is not, so enforcement means killing a process.