Start with visibility, not cuts
Before changing anything, get a per-namespace, per-workload breakdown of spend. Guessing which service is expensive wastes more engineering time than the savings you'll find.
Tools worth setting up first:
- Kubecost or your cloud provider's cost allocation tags
- Resource requests/limits reporting via
kubectl topand the metrics-server
| Signal | Tool | What it tells you |
|---|---|---|
| Per-namespace spend | Kubecost | Where the money actually goes |
| Requested vs. used | metrics-server | How much is over-provisioned |
| Idle nodes | Cluster autoscaler logs | Whether you can scale down |
The checklist, in priority order
- Right-size requests, not just limits. Over-requested CPU/memory reserves capacity nothing uses. Compare requests against actual
kubectl top podusage over a two-week window. - Turn on the Cluster Autoscaler (or Karpenter). Static node pools sized for peak load are the single biggest source of waste.
- Use spot/preemptible nodes for stateless workloads. Anything that tolerates restarts — batch jobs, stateless APIs behind a load balancer — belongs on spot capacity.
- Set Pod Disruption Budgets before you touch autoscaling. Otherwise spot reclaims or scale-downs turn into incidents.
- Consolidate small workloads onto fewer, larger nodes. Bin-packing efficiency drops fast with lots of tiny nodes; fewer larger nodes reduce per-node overhead.
- Audit persistent volumes. Orphaned PVCs from deleted StatefulSets are a quiet, recurring cost.
- Downscale non-production environments outside working hours. A simple CronJob that scales dev/staging deployments to zero overnight pays for itself in a week.
- Revisit egress traffic patterns. Cross-AZ and cross-region traffic between services adds up; co-locate chatty services where you can.
Example: scheduled scale-down CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-down-staging
spec:
schedule: "0 20 * * 1-5"
jobTemplate:
spec:
template:
spec:
serviceAccountName: scaler
containers:
- name: scaler
image: bitnami/kubectl
command:
- /bin/sh
- -c
- kubectl scale deployment --all --replicas=0 -n staging
restartPolicy: OnFailure
The result
Applying steps 1–4 alone typically recovers 25–40% of compute spend without touching application code. Steps 5–8 are smaller wins individually, but they compound — and unlike a one-time right-sizing pass, they keep paying off as the cluster grows.