The pattern is almost universal: a team adopts Kubernetes, things work, engineers are productive, and then someone looks at the cloud bill six months later and asks why it's three times what they expected. The answer is almost always the same set of problems — over-provisioned requests, idle workloads burning compute, and no visibility into where money is actually going.
Cloud providers have no incentive to help you spend less. Kubernetes defaults are set for correctness and availability, not cost efficiency. Left unmanaged, the gap between what you're paying and what you need to pay grows with every new service you deploy. Most teams we audit are wasting between 30% and 60% of their compute spend.
Here's where that waste lives and what to do about it.
Get Visibility First
You cannot optimize what you cannot see. Before touching a single resource limit or scaling policy, get cost allocation working at the namespace and workload level.
Kubecost is the standard tool here. It integrates with your cluster and breaks down compute costs by namespace, deployment, pod, and label — giving you the actual dollar cost of running each workload. The open-source version covers most use cases. For multi-cluster environments, the paid tier is worth evaluating.
Once Kubecost is running, you'll usually find that 20% of your workloads account for 80% of the spend. That's where to start. Spreading optimization effort evenly across all services is inefficient — you want to fix the expensive things first.
Tag everything. Every namespace, every workload, every persistent volume should carry labels that map to a team, product, and environment. Cost allocation only works if the data is there to allocate against.
Right-Size Your Resource Requests and Limits
This is typically the single biggest lever. In most Kubernetes clusters, resource requests — the values that determine how much CPU and memory the scheduler reserves for a pod — are set by whoever wrote the deployment manifest, often with a healthy (read: excessive) buffer "just in case."
Kubernetes schedules pods based on requests, not actual usage. A pod requesting 2 CPU cores that consistently uses 0.3 will still consume 2 cores of schedulable capacity on whatever node it lands on. That node fills up with requested capacity that's never used, and you're paying for it.
The fix is to measure actual utilization over time and set requests to match it — with a reasonable headroom buffer, not an order-of-magnitude one. Prometheus and Grafana can surface the data you need. A simple query to find pods with request-to-usage ratios above 3:1 will immediately surface your most egregious over-provisioners.
Vertical Pod Autoscaler (VPA) can automate this. In recommendation mode, VPA watches actual resource usage and generates right-sizing recommendations without automatically applying them — a sensible starting point. In auto mode, it can resize pods dynamically, though this comes with restart implications that need to be factored in per workload.
A resource request is a reservation, not a ceiling. Every CPU core you request but don't use is a core you're paying for on a node that's partially idle.
Use Spot and Preemptible Nodes for Appropriate Workloads
Spot instances (AWS) and preemptible VMs (GCP) offer the same compute at 60–90% discount in exchange for the possibility that the provider reclaims them with short notice — typically 2 minutes on GCP, variable on AWS.
Many Kubernetes workloads are excellent candidates for spot nodes:
- Batch processing jobs
- CI/CD build runners
- Development and staging environments
- Stateless workers that can be rescheduled quickly
- Any workload designed with graceful shutdown in mind
The key is workload segregation. Run your spot node pool alongside your on-demand node pool, use Kubernetes taints and tolerations to steer appropriate workloads onto spot nodes, and keep stateful or latency-critical workloads on on-demand instances.
Karpenter (AWS) and equivalent tools handle the node provisioning logic — automatically selecting the most cost-effective instance type for pending pods, packing nodes efficiently, and replacing spot interruptions quickly. Teams that migrate from cluster autoscaler to Karpenter typically see 20–40% additional savings from better bin-packing alone.
Scale to Zero Where You Can
The Horizontal Pod Autoscaler (HPA) scales pods based on CPU or memory utilization. It's useful, but it has a floor — by default it won't scale below 1 replica, meaning you always have at least one pod running even when traffic is zero.
For development environments, batch processing services, and workloads with clear off-hours patterns, scaling to zero is a significant cost opportunity. KEDA (Kubernetes Event-Driven Autoscaler) enables this — it can scale any deployment down to zero based on queue depth, schedule, or any external metric, and back up when demand appears.
A development environment that runs 8 hours a day instead of 24 uses one-third of the compute. At scale, that's a material budget line.
Namespace-level scheduled scaling is another angle. Tools like Kube-Downscaler or simple CronJobs that patch deployment replica counts can implement "office hours" scheduling for dev and staging environments with minimal configuration.
Fix Your Storage Costs
Persistent volumes are easy to forget about because they don't show up on the nodes panel. But orphaned PVCs — volumes that were provisioned for a pod that no longer exists — accumulate quietly and can add up to hundreds of dollars per month in clusters with active development.
Run a monthly audit: list all PVCs, join against their bound pods, and flag anything that's been unbound for more than a few days. Cloud providers charge for provisioned storage capacity, not used capacity, so a 100GB volume attached to nothing is a 100GB cost regardless.
Also audit your storage class usage. Many teams default to SSD storage for everything, including workloads that have no low-latency IO requirements. Standard HDD storage costs 40–60% less and is entirely appropriate for log aggregation, backups, non-latency-sensitive databases, and build artifact storage.
Right-Size Your Cluster Node Pools
Most cluster autoscalers add nodes when pending pods can't be scheduled and remove them when utilization drops. But the default configuration usually adds nodes at the first sign of pressure — which is fine for availability but not ideal for cost.
Tuning the scale-down behavior is usually worth the effort. Increase the scale-down-delay-after-add period (default: 10 minutes) to avoid thrashing. Review your scale-down-utilization-threshold — the default of 50% means a node won't be removed until utilization drops below half, but many workloads with varying request/usage ratios sit in that 50–70% range on requested capacity while actually using much less.
Also look at your node sizes. Smaller nodes (e.g. 4 vCPU) often lead to better bin-packing than larger ones (16 vCPU) because pods have less headroom to waste. The right node size depends on your workload mix, but it's worth running the numbers before defaulting to whatever instance type was in the initial Terraform config.
Multi-Tenant Cluster Consolidation
Teams that operate separate clusters per environment and per team can often consolidate significantly. Running 6 small clusters instead of 2 medium ones typically costs more because each cluster has fixed overhead — control plane costs, system daemon pods, monitoring agents — that doesn't scale to zero.
Namespace isolation, network policies, and resource quotas can provide strong isolation guarantees within a shared cluster. For many use cases, this is sufficient and meaningfully cheaper than cluster-per-team models. The tradeoff is blast radius — a misconfigured resource quota in a shared cluster can affect multiple teams. Size that risk against the cost savings for your specific situation.
Building a Cost Culture
Tooling gets you halfway there. The other half is making cost a visible metric that engineering teams care about.
Include monthly cloud costs by team in your engineering all-hands. Set cost budgets per namespace and alert when they're exceeded. Add cost visibility to your platform's developer portal so engineers can see what their services actually cost to run. These aren't punishment mechanisms — they're feedback loops. Engineers make better decisions when they can see the downstream cost of the architecture choices they're making.
One team we worked with added a "cost per request" metric to their service dashboards alongside p99 latency and error rate. Within two quarters, they'd redesigned their caching layer in a way that cut their data transfer costs by 40% — not because anyone told them to, but because the metric made the opportunity visible.
The Sequencing That Works
Start with visibility (Kubecost). Find the top 20% of spenders. Right-size their resource requests. Move eligible workloads to spot nodes. Enable scheduled scale-down for dev environments. Clean up orphaned volumes. Then instrument and iterate.
Teams that do this systematically over a quarter typically find themselves at 40–55% of their original compute spend, with better reliability and fewer noisy-neighbor incidents to boot. The optimization work pays for itself within weeks, and the savings compound as you continue to grow.
Kubernetes is expensive by default. It doesn't have to stay that way.