Should you set CPU limits in Kubernetes?
For most latency-sensitive services, no: set a CPU request, set a memory limit, and leave the CPU limit out. A CPU limit can throttle a container even when its average usage is far below the limit, which adds latency, while the CPU request alone already guarantees the container its share under contention. CPU limits are worth setting for multi-tenant fairness, for Guaranteed QoS with exclusive cores, and for benchmarks that need repeatable results.
How CPU limits are enforced
Kubernetes turns a CPU limit into a quota for the Linux CFS bandwidth controller. The kernel hands out that quota in periods of 100 milliseconds by default. A limit of 500m allows 50 ms of CPU time per 100 ms period, counted across all threads of the container.
That accounting is what causes surprise throttling. A container with four busy threads and a 500m limit uses its 50 ms of quota in about 12.5 ms of wall-clock time, then waits roughly 87.5 ms for the next period. Its average usage over a minute may look like 20%, but every request that arrives during the wait pays the delay. Multi-threaded runtimes, garbage collectors and request bursts make this common.
Throttling shows up in two cAdvisor counters: container_cpu_cfs_throttled_periods_total and container_cpu_cfs_periods_total. A throttled-period ratio above a few percent on a latency-sensitive service is worth investigating.
Why the request is enough for most services
The Kubernetes documentation describes the CPU request as a weighting: when containers compete for CPU, those with larger requests get proportionally more CPU time. A container without a limit can use idle CPU on its node, and when the node is busy it still receives at least the share its request entitles it to. The scheduler also never places more CPU requests on a node than it has capacity.
Memory is different. It cannot be throttled, only reclaimed or killed, so a container without a memory limit can push the whole node into memory pressure. Set a memory limit on every container, usually equal to its memory request. Kubernetes resource requests and limits covers both in more detail.
When CPU limits help
| Situation | Why a CPU limit helps |
|---|---|
| Shared clusters with untrusted or unreviewed tenants | Caps how much spare CPU one tenant can take, so behavior does not depend on what else runs on the node |
| Exclusive cores with the static CPU manager | The static CPU manager policy gives exclusive CPUs only to containers in Guaranteed Pods with integer CPU requests, which requires limits equal to requests |
| Benchmarks and capacity tests | Results stay repeatable instead of depending on idle CPU that production may not have |
Namespaces with a limits.cpu quota | A ResourceQuota on CPU limits can reject Pods that declare none, unless a LimitRange supplies a default |
| Batch jobs next to latency-sensitive services | Keeps a CPU-hungry job from using every spare cycle, if requests alone do not isolate them well enough |
For multi-tenant clusters, namespace quotas and separate node pools usually isolate tenants more reliably than per-container CPU limits.
Runtimes that read the CPU limit
Some language runtimes size their thread pools from the CPU limit, so removing it changes their behavior:
- Go. Since Go 1.25, the runtime sets
GOMAXPROCSfrom the container’s CPU limit on Linux and ignores the CPU request. Without a limit,GOMAXPROCSdefaults to the node’s logical CPU count, which on a 64-core node can mean far more parallel threads than the request covers. - Java. The JVM derives its active processor count from the container’s CPU quota. Without a limit, it sees every CPU on the node and sizes garbage collector and fork-join thread pools to match.
When you drop CPU limits, set the runtime explicitly: GOMAXPROCS as an environment variable for Go, or -XX:ActiveProcessorCount for Java, close to the CPU request rounded up.
CPU limits do not lower the bill
Nodes are sized and counted by requests, not limits. Removing CPU limits does not add nodes, and adding them does not remove any. What changes the bill is the request: an inflated request blocks capacity whether or not a limit sits above it. Without limits, an under-requested service can borrow idle CPU and look healthy until its node gets busy, so pair the change with rightsizing that sets requests from measured usage.
A practical default
- Set CPU and memory requests on every container from measured usage.
- Set a memory limit on every container, usually equal to the memory request.
- Leave CPU limits off for latency-sensitive services and set the runtime’s thread count explicitly.
- Add CPU limits for untrusted tenants, exclusive-core workloads, benchmarks and namespaces whose quotas require them.
- Watch throttling metrics wherever CPU limits remain.