kubernetescost-optimizationfinopsrightsizing

Kubernetes overprovisioning waste: the 3x gap between requested and used

Across most clusters, pods request roughly three times the CPU they actually consume, and the cluster buys nodes for the requests. That gap is the largest single line of avoidable Kubernetes spend. Here is where it comes from and how to close it.

The C3X Team··7 min read

Quick answer

Overprovisioning in Kubernetes means pods request far more CPU and memory than they use, and since the scheduler provisions nodes against requests, you buy capacity for the request not the usage. Measured gaps are consistently 2.5 to 3.5 times on CPU and 1.5 to 2.5 times on memory. A cluster requesting 400 vCPU while using 140 vCPU is spending roughly $10,000 per month on capacity nobody touches. The causes are copy-pasted manifests, fear of throttling, no feedback loop, and requests set during a load test that never got revisited. The fix is setting requests from p95 observed usage plus a 20 to 30 percent buffer.

There is one number that explains most Kubernetes overspend: the ratio of requested resources to used resources. The scheduler does not care what your pods use. It packs nodes based on what they request, and the cloud bills you for the nodes. If your pods collectively request 400 vCPU while using 140, you are paying for 400.

Where the gap comes from

CauseTypical inflationWhy it persists
Copy-pasted manifest defaults2 to 5 timesNobody owns the original number
Sized for peak load test3 to 6 timesPeak is rare, request is permanent
Fear of CPU throttling2 to 3 timesOne bad incident, permanent padding
Round numbers1.5 to 2 times1 vCPU looks nicer than 0.35
Helm chart defaults2 to 4 timesCharts size for the largest user

The Helm chart case is worth calling out because it scales badly. A popular chart ships with requests of 1 vCPU and 2 GB so it works out of the box for large installs. A team deploys 30 instances of that chart across namespaces and now requests 30 vCPU and 60 GB for workloads using perhaps 6 vCPU and 20 GB. That is roughly 4 nodes worth of m5.2xlarge, $1,167 per month, for one unexamined default.

Putting a number on it

The calculation is direct. Take total requested CPU across the cluster, take p95 actual usage, and multiply the difference by cost per vCPU. On m5.2xlarge at $280 per month with 7.2 allocatable vCPU, one requested vCPU costs about $38.89 per month.

ClusterRequested vCPUp95 used vCPUGapMonthly waste
Small team802852$2,022
Mid-size platform400140260$10,111
Large multi-tenant1,8006201,180$45,890

Not all of that gap is recoverable. You need headroom for traffic variance, for rolling deploys, and for the startup burst many applications need. A realistic target is capturing 60 to 75 percent of the gap, which on the mid-size platform is $6,000 to $7,500 per month.

Memory behaves differently from CPU

CPU is compressible: a pod that exceeds its request gets throttled but keeps running. Memory is not: exceed the limit and the kernel kills the container. That asymmetry should drive different policies. Set CPU requests aggressively close to p95 usage because the downside is throttling, not death. Set memory requests closer to observed peak plus 25 percent, because the downside is an OOMKill and a restart loop.

A common and costly mistake is setting CPU limits equal to CPU requests on latency-sensitive services. That caps burst capacity at exactly the steady-state allocation, so any traffic spike throttles, which causes a latency incident, which causes someone to double the request. The more durable pattern is a modest CPU request with no CPU limit, and a memory request equal to the memory limit. Our guide torequests and limits covers the tradeoffs.

Closing the gap without breaking things

Do it gradually and per workload, not cluster-wide overnight. A workable sequence looks like this.

First, collect 14 days of per-container CPU and memory usage at p50, p95, and p99. Two weeks catches weekly traffic cycles that a 24-hour sample misses. Second, rank workloads by absolute waste, requested minus used times replica count, not by ratio. A workload over-requesting by 10 times on 0.1 vCPU is irrelevant; a workload over-requesting by 2 times on 40 replicas of 2 vCPU is $1,500 per month. Third, set CPU request to p95 times 1.2 and memory request to p99 times 1.25. Fourth, watch throttling and OOMKill rates for a week before moving on.

Vertical Pod Autoscaler in recommendation mode is useful for step one because it generates these numbers automatically without changing anything. Run it with updateMode Off, read the recommendations, and apply them through your normal manifest review process. Full VPA auto-update is riskier because it evicts pods to resize them.

Keeping it closed

Overprovisioning grows back. New services arrive with copy-pasted requests, and old services get padded after incidents. The durable controls are a LimitRange per namespace to cap the default, a ResourceQuota to bound total namespace requests, and a monthly report of the top 20 workloads by absolute waste. Pair that with pod right-sizing as a standing practice rather than a project, and price node group changes from Terraform against theresource catalog so the capacity you remove shows up as money saved.

FAQ

What is overprovisioning in Kubernetes?

Overprovisioning is when pods request more CPU and memory than they actually consume. Because the scheduler places pods and the cluster autoscaler provisions nodes based on requests rather than usage, you pay for the requested amount. Measured gaps are consistently 2.5 to 3.5 times on CPU and 1.5 to 2.5 times on memory across production clusters.

How much money does Kubernetes overprovisioning waste?

On typical rates, one requested vCPU on m5.2xlarge nodes costs about $38.89 per month. A mid-size cluster requesting 400 vCPU while using 140 wastes 260 vCPU, roughly $10,111 per month. Not all of that is recoverable since you need headroom for spikes and rolling deploys, but capturing 60 to 75 percent of the gap is realistic, around $6,000 to $7,500 per month in that example.

Should I set CPU requests and limits to the same value?

Usually not for latency-sensitive services. Setting a CPU limit equal to the request caps burst capacity at the steady-state allocation, so any traffic spike throttles and causes latency incidents, which typically leads someone to double the request. A more durable pattern is a modest CPU request with no CPU limit, combined with a memory request equal to the memory limit since memory is not compressible.

How do I set correct resource requests?

Collect at least 14 days of per-container usage to capture weekly traffic cycles, then set CPU request to p95 usage times 1.2 and memory request to p99 usage times 1.25. CPU can be set aggressively because exceeding it only causes throttling, while memory needs more headroom because exceeding the limit triggers an OOMKill. Roll changes out per workload and watch throttling and OOMKill rates for a week.

Why do Helm charts cause overprovisioning?

Charts ship with requests sized for their largest expected user so they work out of the box, commonly 1 vCPU and 2 GB for components that need a fraction of that. Deploying 30 instances of such a chart requests 30 vCPU and 60 GB for workloads using perhaps 6 vCPU and 20 GB, roughly four m5.2xlarge nodes or $1,167 per month from one unexamined default value.

How does C3X help with Kubernetes overprovisioning?

C3X prices infrastructure from Terraform against a live catalog, so when right-sizing work lets you shrink a node group, the saving appears as a concrete monthly number in the pull request. It also makes the cost of adding capacity visible up front, so a node group that grows to absorb inflated requests gets questioned at review time rather than on the invoice.

What to do next

Stop paying for capacity nobody uses. C3X reads your Terraform and prices your resources against a live catalog. Start with the quickstart.

Try C3X on your own Terraform

Free and open source. No API key required. One command to install, one command to estimate.