The Kubernetes Cost Problem
Kubernetes was supposed to improve resource utilisation. In practice, most organisations see the opposite: CNCF's FinOps for Kubernetes report found that the average Kubernetes cluster runs at 30-40% CPU utilisation and 45-55% memory utilisation. That means 50-60% of your compute spend is paying for idle resources.
The root causes are predictable: developers over-request resources because it is safer than under-requesting, autoscaling is either misconfigured or absent, and nobody owns cluster-level cost accountability. We have applied these 10 strategies across dozens of client clusters and consistently achieve 35-50% cost reductions within 60 days.
Strategy 1: Right-Size Resource Requests and Limits
This single strategy typically accounts for 15-25% of total savings. Most developers set resource requests based on guesswork, and those guesses are always too high.
Use Goldilocks or the Kubernetes Vertical Pod Autoscaler (VPA) in recommendation mode to analyse actual resource consumption over 7-14 days, then adjust requests accordingly.
# Install Goldilocks to get right-sizing recommendations
helm install goldilocks fairwinds-stable/goldilocks \
--namespace goldilocks --create-namespace
# Enable VPA recommendations for a namespace
kubectl label ns production goldilocks.fairwinds.com/enabled=true
# After 7 days, check recommendations via the Goldilocks dashboard
kubectl port-forward svc/goldilocks-dashboard -n goldilocks 8080:80
Key rule: Set requests to the P95 of actual usage and limits to 2x requests. Never set CPU limits on latency-sensitive workloads — CPU throttling causes more outages than CPU exhaustion.
Strategy 2: Implement Cluster Autoscaler Correctly
Cluster Autoscaler scales nodes up and down based on pending pods. Most teams install it and walk away. Here is how to configure it properly:
# Cluster Autoscaler Helm values for cost optimization
autoscaling:
enabled: true
extraArgs:
scan-interval: "10s"
scale-down-delay-after-add: "5m"
scale-down-delay-after-delete: "0s"
scale-down-unneeded-time: "5m"
scale-down-utilization-threshold: "0.5"
skip-nodes-with-local-storage: "false"
max-graceful-termination-sec: "600"
balance-similar-node-groups: "true"
expander: "least-waste"
The critical parameter is scale-down-utilization-threshold. The default is 0.5, meaning nodes below 50% utilisation are candidates for removal. For non-production clusters, set this to 0.3 to be more aggressive.
Strategy 3: Use Spot/Preemptible Nodes for Stateless Workloads
Spot instances cost 60-90% less than on-demand. For stateless workloads that can tolerate interruptions (web servers behind load balancers, batch jobs, CI/CD runners), this is the single highest-impact cost lever.
Use Karpenter (AWS) or the equivalent GKE/AKS spot node pool configurations to automatically provision spot nodes:
# Karpenter NodePool for spot instances
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-workloads
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m6i.xlarge", "m6a.xlarge", "m5.xlarge", "m5a.xlarge"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: "100"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
Best practice: Diversify across at least 4 instance types and 3 availability zones to reduce spot interruption frequency below 5%.
Strategy 4: Implement Namespace-Level Resource Quotas
Without quotas, a single team can consume disproportionate cluster resources. Set quotas per namespace to enforce accountability:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-api-quota
namespace: team-api
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
persistentvolumeclaims: "10"
services.loadbalancers: "2"
Combine with Kubecost for per-namespace cost visibility. When teams can see their spend, they optimise it — this is FinOps 101 applied to Kubernetes.
Strategy 5: Right-Size and Consolidate Persistent Volumes
Persistent volumes are often provisioned at creation and never resized. We regularly find 500 GB volumes with 30 GB of actual data. Audit with:
# Find oversized PVCs across all namespaces
kubectl get pvc --all-namespaces -o json | \
jq -r '.items[] | [.metadata.namespace, .metadata.name,
.spec.resources.requests.storage, .status.capacity.storage] | @tsv'
Switch from gp3 to st1 (AWS) for infrequently accessed data. Use lifecycle policies on object storage for logs and backups. Typical savings: 10-20% of storage costs.
Strategy 6: Schedule Non-Production Cluster Shutdowns
Development, staging, and QA clusters rarely need to run 24/7. Shutting them down outside business hours (18:00-08:00 weekdays, all weekend) reduces costs by 65%.
# CronJob to scale down dev namespaces at 18:00 CET
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-down-dev
spec:
schedule: "0 18 * * 1-5"
jobTemplate:
spec:
template:
spec:
serviceAccountName: cluster-scaler
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
for deploy in $(kubectl get deploy -n dev -o name); do
kubectl scale $deploy -n dev --replicas=0
done
Strategy 7: Use Pod Disruption Budgets with Bin Packing
Enable bin packing in your scheduler to pack pods more tightly onto fewer nodes. Combined with PDBs, this ensures reliability during consolidation:
# Use the MostAllocated scoring strategy
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
enabled:
- name: NodeResourcesFit
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
type: MostAllocated
Strategy 8: Optimise Container Images
Smaller images mean faster pulls, less registry storage, and reduced bandwidth costs. We routinely see 500 MB+ images that could be 50 MB.
- Use multi-stage builds to separate build dependencies from runtime
- Switch from
node:18(1 GB) tonode:18-alpine(180 MB) orgcr.io/distroless/nodejs(120 MB) - Scan images with
diveto find wasted space in layers - Set up image lifecycle policies in ECR/ACR/GCR to delete untagged images older than 30 days
Strategy 9: Implement Horizontal Pod Autoscaler with Custom Metrics
Default HPA based on CPU percentage is too coarse for most workloads. Use custom metrics from Prometheus for precise scaling:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 2
maxReplicas: 20
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 50
periodSeconds: 60
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
The scaleDown.stabilizationWindowSeconds of 300 prevents flapping. The asymmetric scale-up/scale-down speeds ensure you respond quickly to traffic spikes but shrink gradually.
Strategy 10: Monitor, Attribute, and Iterate
Cost optimisation is not a one-time project — it is a continuous practice. Set up:
- Kubecost or OpenCost for real-time per-namespace, per-deployment cost attribution
- Weekly cost review meeting (15 minutes) where the top 3 cost anomalies are investigated
- Slack/Teams alerts when namespace costs exceed budget thresholds by 20%+
- Quarterly right-sizing sprints where each team reviews and adjusts their resource requests
Expected Impact
When applied together, these 10 strategies compound. Here is the typical breakdown we see:
| Strategy | Typical Savings | Effort to Implement |
|---|---|---|
| Right-size requests | 15-25% | Low |
| Cluster autoscaler tuning | 5-10% | Low |
| Spot instances | 20-40% | Medium |
| Resource quotas | 5-10% | Low |
| PV right-sizing | 10-20% | Low |
| Non-prod shutdown | 40-65% | Low |
| Bin packing | 5-15% | Medium |
| Image optimisation | 2-5% | Low |
| Custom metrics HPA | 10-20% | Medium |
| Continuous monitoring | 5-10% | Ongoing |
Net effect across production and non-production: 35-50% total Kubernetes cost reduction.
Get Started
If you want a hands-on assessment of your Kubernetes cost posture, our DevOps consulting team runs a 2-week K8s cost audit that delivers a prioritised savings roadmap with exact dollar amounts. Book a free consultation to discuss your cluster setup.