Kubernetes abstracts infrastructure beautifully — including the invoice. A monthly AWS bill shows EC2, EBS, NAT Gateway and data transfer lines, but nothing tells you which team, namespace or service caused them. That gap is where cloud waste hides. Here is the checklist I run through when reviewing an EKS estate.
1. Requests are your real bill
The scheduler packs nodes based on requests, not actual usage. A pod requesting 2 vCPU that uses 200m still reserves 2 vCPU of capacity you pay for.
- Compare requests against p95 usage per workload (Prometheus, Kubecost or OpenCost).
- Use the Vertical Pod Autoscaler in recommendation mode to get right-sizing suggestions without auto-applying them.
- Set LimitRanges and namespace ResourceQuotas so defaults are sane.
sum by (namespace) (kube_pod_container_resource_requests{resource="cpu"})
/
sum by (namespace) (rate(container_cpu_usage_seconds_total[5m]))
A request-to-usage ratio well above 2 is a right-sizing candidate.
2. Let the node layer work for you
- Karpenter with broad instance families lets the provisioner pick the cheapest fitting capacity and consolidate underused nodes.
- Spot for stateless, interruption-tolerant workloads — with PodDisruptionBudgets and diversification across instance types.
- Graviton (arm64) instances typically offer better price-performance; multi-arch images make the switch low-risk.
- Savings Plans to cover the steady baseline once right-sizing is done — commit after you optimise, not before.
3. Hunt the networking line items
Networking is the most common surprise on EKS bills:
- NAT Gateway processing — pulling images and calling AWS APIs through NAT is billed per GB. Add VPC endpoints for ECR, S3 (gateway endpoint, free), STS and CloudWatch.
- Cross-AZ traffic — chatty services spread across zones pay for every hop. Use topology-aware routing and keep replicas of tightly coupled services zone-local where availability allows.
- Load balancers — one LoadBalancer Service per app adds up. Consolidate behind an ingress controller or the AWS Load Balancer Controller with IngressGroups.
4. Storage and logs
- Migrate gp2 volumes to gp3 — cheaper per GB with baseline IOPS decoupled from size.
- Find orphaned EBS volumes and snapshots left by deleted PVCs (check reclaim policies).
- Set retention on CloudWatch log groups; “never expire” is the default and it is rarely what you want.
- Drop debug-level logs at the collector (Fluent Bit or the OpenTelemetry Collector) before they are ingested.
5. Non-production should sleep
Dev and staging clusters rarely need to run at 2 AM on Sunday. Scale deployments to zero outside working hours with KEDA cron scalers or a simple scheduled job, and let Karpenter remove the empty nodes. Ephemeral preview environments with a TTL beat long-lived shared staging stacks.
6. Make cost visible and owned
Optimisation without accountability decays within a quarter. Make cost a platform feature:
- Enforce
teamandcost-centerlabels with Kyverno or Gatekeeper. - Run OpenCost or Kubecost and publish a weekly cost-per-namespace report.
- Activate AWS cost allocation tags and enable split cost allocation data for EKS in Cost and Usage Reports.
- Add cost to service scorecards next to SLOs and DORA metrics.
Prioritise by effort
Quick wins: VPC endpoints, gp3 migration, log retention, non-prod schedules. Medium effort: right-sizing requests, Spot and Graviton adoption. Long term: cost ownership culture and commitment planning.
The takeaway
EKS cost optimisation is not a one-off cleanup; it is a feedback loop. Make usage visible, right-size continuously, let the node layer bin-pack efficiently and give every team ownership of what they run. The cheapest cluster is the one where everyone can see what they spend.


