Kubernetes abstracts away the hardware, but it does not abstract away the bill. Most production clusters bill at 3–5× their actual computational load — the gap between provisioned capacity and utilised capacity is where the waste lives.
The causes are structural: teams over-provision pods to avoid evictions, node pools sit at their maximum size during off-peak hours, and system overhead consumes 20–30% of every node before a single application workload runs. Without active management, the bill grows with cluster size, not with actual work done.
This guide covers the specific tools and techniques that close that gap: right-sizing at the pod and node level, Karpenter for intelligent node provisioning, spot instance node pools, namespace governance, and cost visibility tooling that makes the savings sustainable.
Why Kubernetes Clusters Are Expensive by Default
A fresh cluster before any optimisation typically looks like this:
| Layer | Overhead |
|---|---|
| System pods (kube-system) | 5–15% of node capacity |
| Monitoring stack (Prometheus, Grafana) | 5–10% per node |
| Logging agent (Fluentd, Fluent Bit) | 2–5% per node |
| Service mesh (Istio, Linkerd sidecars) | 10–20% of pod capacity |
| Pod request over-provisioning | 30–50% of remaining capacity |
Before optimisation, it is common for actual workload utilisation to be 20–30% of what the bill reflects.
Cost Visibility First: Kubecost and OpenCost
You cannot optimise what you cannot see at the workload level. Cloud billing shows you node cost; it does not tell you which deployment, which namespace, or which team is responsible for it.
Kubecost (open-source core donated to CNCF as OpenCost) fills this gap. Deployed inside your cluster, it correlates Kubernetes resource consumption with cloud billing data to produce cost-per-workload views:
- Cost per namespace, deployment, service, and pod
- CPU and memory efficiency scores per workload
- Right-sizing recommendations based on actual usage
- Team-level cost allocation dashboards
Deploy it and run it for 2–4 weeks before taking optimisation actions. The baseline data makes recommendations credible and lets you measure savings precisely.
Right-Sizing Pods: The Foundation
Pod right-sizing is the foundation of all other Kubernetes cost work. Node autoscalers provision capacity based on pod requests — if requests are inflated, nodes are oversized even when an autoscaler is running.
The problem with typical requests
Engineers set requests based on estimates or copied from a template:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
If the pod actually uses 80m CPU at p95 and 120Mi memory at p95, the request is 6× oversized for CPU and 4× oversized for memory. That node slot is claimed but 80% empty.
Using Goldilocks for recommendations
Goldilocks deploys VPA in recommendation mode per namespace and provides a dashboard showing actual usage vs current requests with recommended values:
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install goldilocks fairwinds-stable/goldilocks --namespace goldilocks
kubectl label namespace production goldilocks.fairwinds.com/enabled=true
Review recommendations weekly. Apply changes to staging first, monitor for a week, then apply to production.
Target resource settings
| Value | Setting |
|---|---|
| CPU request | p95 observed usage + 20% headroom |
| CPU limit | 2–4× CPU request (allow bursting) |
| Memory request | p95 observed usage + 30% headroom |
| Memory limit | 1.2–1.5× memory request (OOMKill > swap) |
Karpenter: Intelligent Node Provisioning
The Cluster Autoscaler works by scaling pre-configured node groups up and down. It provisions nodes of one type and waits for the group to scale up. Karpenter takes a different approach: given pending pods, it evaluates their requirements and provisions exactly the most cost-effective node that satisfies them — in under 60 seconds.
Key Karpenter advantages
Best-fit instance selection. Instead of scaling a fixed node group, Karpenter evaluates all available instance types and picks the cheapest one that satisfies pending pod requirements. A pod needing 2 CPUs and 4GB RAM gets a node that fits that, not a general-purpose node 3× larger.
Automatic consolidation. Karpenter continuously evaluates whether running workloads can be packed onto fewer nodes. When consolidation is safe (respecting PDBs and do-not-disrupt annotations), it terminates underutilised nodes and reschedules their pods — without manual intervention.
Mixed capacity types in one pool. A single Karpenter NodePool can provision both on-demand and spot capacity, choosing spot opportunistically and falling back to on-demand when spot capacity is unavailable.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
Teams migrating from Cluster Autoscaler to Karpenter typically see 30–60% reduction in node costs from better bin-packing and spot adoption alone.
Spot Node Pools
Spot instances offer 60–80% discounts. On Kubernetes, they are most effective for:
| Workload type | Spot suitability |
|---|---|
| Stateless applications (>= 2 replicas) | High — pods reschedule on interruption |
| CI/CD build runners | High — jobs restart on new nodes |
| ML training with checkpointing | High — resume from last checkpoint |
| Batch data processing | High — idempotent retry |
| Stateful applications (databases) | Low — interruption causes downtime |
| Single-replica critical services | Low — no redundancy on interruption |
Reliability through diversification
Spot interruptions are instance-type and zone specific. Requesting capacity across multiple instance families and availability zones dramatically reduces the probability of losing all spot capacity simultaneously:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"] # multiple families
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a", "us-east-1b", "us-east-1c"] # multiple zones
Pod Disruption Budgets
Set PDBs on any workload that runs on spot nodes to ensure enough replicas stay healthy during interruption:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: api
Namespace Governance
Without namespace-level controls, a single misbehaving deployment can consume all cluster capacity:
ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "50"
LimitRange (defaults for pods without explicit settings)
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-alpha
spec:
limits:
- default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
type: Container
Non-Production Environment Scheduling
Development and staging clusters often run 24/7 when they only need to run during business hours. A cluster running from 8am to 8pm weekdays operates for 60 hours per week instead of 168 — a 64% reduction in running time.
KEDA's cron scaler can scale deployments to zero replicas on a schedule. For the cluster itself, a Lambda/Cloud Function can call the Kubernetes API or node group scale-in API on a schedule.
Savings Summary
| Optimisation | Typical savings |
|---|---|
| Pod right-sizing | 15–25% of compute cost |
| Karpenter vs Cluster Autoscaler | 20–40% of node cost |
| Spot node pools (eligible workloads) | 50–70% on those nodes |
| Non-production scheduling (off-hours) | 50–70% of non-prod compute |
| Namespace quotas + waste elimination | 5–15% miscellaneous |
Applied together on a cluster without prior optimisation, total savings of 40–60% are consistently achievable. The key is sequencing: visibility first, pod right-sizing second, node optimisation third, governance fourth.
For help implementing Kubernetes cost optimisation as part of a broader cloud architecture engagement, see our Cloud Engineering capabilities or get in touch.
For context on the broader cloud cost picture beyond Kubernetes, our Cloud Cost Optimization guide covers the complete FinOps approach across AWS, Azure, and GCP.