A Kubernetes cluster is sized to fit what pods request, not what they use. That one fact explains most oversized clusters, and it means the bill can be far above the workload without anything being broken.
There are two different kinds of waste in a cluster. They have different causes and different fixes, and treating them as one is why cost efforts stall. The Kubernetes Cost and Waste Calculator splits a cluster's bill into the two.
Waste one: requests nobody uses
A pod requests 2 CPUs and uses 0.3. The scheduler reserves the other 1.7 for it, and no other pod can have them.
Nobody was careless. Requests are set once, at launch, with a safety margin, by someone who did not yet know how the service behaves. They are almost never revisited, because a request that is too high causes no alerts.
The fix is right-sizing requests from observed usage. Run the Vertical Pod Autoscaler in recommendation mode, or read usage from your metrics, and set requests to typical peak usage plus a margin.
Waste two: capacity nothing requested
This is node capacity that no pod has asked for: nodes left over after a scale-down, a node pool sized for a monthly peak, or instance types too large to pack well.
The fix is on the node side: the cluster autoscaler or Karpenter to remove empty nodes, and instance sizes that match the shape of your pods.
Right-sizing requests without an autoscaler saves nothing, because the freed capacity stays on the bill as empty nodes. You need both.
A worked example
Take 12 nodes with 4 vCPUs and 16 GiB each, at $0.192 an hour. That is about $1,682 a month.
Across the cluster, pods request 28 of 48 vCPUs and use 11. They request 110 of 192 GiB of memory and use 62.
| Where the money goes | Per month | Share |
|---|---|---|
| Used by workloads | $464 | 28% |
| Requested but not used | $508 | 30% |
| Not requested by anything | $710 | 42% |
The split assumes the node price is divided evenly between CPU and memory. On that basis, 72% of the bill pays for capacity that does no work.
If requests tracked usage with 30% headroom, this workload would fit on 6 nodes, a saving of about $841 a month. Treat that as an upper bound. Real clusters lose some capacity to system reservations and to pods that do not pack perfectly.
Find the resource that sets your node count
In the example, memory is the constraint: 62 GiB of usage needs more nodes than 11 vCPUs does. Lowering CPU requests there would free nothing.
Work out which resource is binding before you start, and right-size that one first. On memory-bound clusters, also check whether a memory-optimised instance type fits the workload better than a general-purpose one.
How much headroom to keep
Headroom has to cover a traffic spike, a rolling deploy that briefly runs old and new pods together, and the loss of a node. Start with a margin you are comfortable with above typical peak usage, and reduce it as you come to trust autoscaling.
Do not right-size to average usage. Requests that are too low cause throttling and evictions, and the saving is not worth an incident.
Where to start
- Get the two numbers. Requested resources from
kubectl describe nodes, usage fromkubectl top nodesor your metrics. - Split the bill. See whether your waste is mostly padding or mostly empty capacity.
- Fix the larger one first. Padding means right-sizing requests. Empty capacity means autoscaling and node sizing.
- Then look at price. Spot capacity and commitments reduce the cost of what remains, and they are worth more once the cluster is the right size.
Split your cluster's bill. It runs in your browser and gives a link you can share.