Skip to content
Vladimir Chavkov
Go back

Kubernetes FinOps: Cost Optimization Guide for Clusters

Edit page

Kubernetes FinOps: Cost Optimization Guide for Clusters

Kubernetes makes it easy for teams to ship services, but it can also make cloud spend hard to understand. Nodes are shared, pods move constantly, and the bill arrives at the infrastructure level even when the cost drivers live in namespaces, workloads, and deployment choices.

Kubernetes FinOps connects engineering decisions to cost outcomes. The goal is not simply to spend less. The goal is to make capacity efficient, accountable, and reliable enough that teams can choose the right trade-offs intentionally.

Start with cost allocation

You cannot optimize what nobody owns. In Kubernetes, the first FinOps problem is mapping shared cluster spend back to the teams and applications that create demand.

Use namespaces as allocation boundaries

Namespaces are a practical default for cost allocation because they already group workloads, RBAC, quotas, and deployment automation.

Recommended namespace labels:

apiVersion: v1
kind: Namespace
metadata:
name: payments-prod
labels:
cost-center: finance-platform
team: payments
environment: production
application: payments-api

Keep labels consistent across namespaces, pods, cloud resources, and observability tools. A small controlled vocabulary is more useful than dozens of free-form tags nobody trusts.

Allocate shared cluster costs deliberately

Not every cost maps cleanly to a pod. Control plane fees, ingress controllers, logging agents, monitoring agents, NAT gateways, and shared platform services need allocation rules.

Common approaches include:

The important part is consistency. Teams will trust FinOps data only if the rules are visible and stable.

Right-size requests and limits

Resource requests are one of the biggest Kubernetes cost levers. The scheduler reserves capacity based on requests, so over-requested pods create idle nodes even when actual CPU and memory usage is low.

Tune CPU and memory requests

Start by comparing requested resources with real usage over a meaningful window:

Terminal window
kubectl top pods --all-namespaces
kubectl describe node <node-name>

For production workloads, use metrics from Prometheus, Datadog, CloudWatch, Azure Monitor, Google Cloud Managed Service for Prometheus, or another time-series system. Look at p50, p95, and p99 usage instead of a single average.

Practical guidance:

Be careful with limits

CPU limits can throttle applications and create latency even when nodes have spare CPU. Memory limits are more predictable: exceeding the limit causes an OOM kill.

A common production pattern is:

Example:

resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
memory: "768Mi"

Use autoscaling as a cost control loop

Autoscaling reduces waste when demand changes, but it only works well when the signals are accurate.

Horizontal Pod Autoscaler

Use HPA for stateless workloads where adding replicas improves throughput.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

Keep minReplicas honest. Many clusters waste money because every service keeps production-sized minimums in non-production environments.

Vertical Pod Autoscaler

VPA is useful for recommendations and for workloads where replica scaling does not help. In production, many teams start with VPA in recommendation mode and review changes before applying them automatically.

Cluster autoscaling and Karpenter

Cluster autoscaling translates pod demand into node capacity. Whether you use Cluster Autoscaler, Karpenter, or a cloud-specific node provisioning system, verify that it can:

Improve bin packing

Bin packing is the art of fitting pods onto nodes efficiently. Poor bin packing leaves stranded CPU, memory, or GPU capacity that cannot be used by incoming pods.

Match node pools to workload shapes

A single generic node size is rarely optimal. Use a small number of well-defined node pools:

Avoid creating too many node pools. Each pool creates fragmentation and operational overhead.

Watch for scheduling constraints

Cost problems often hide in scheduling rules:

Review pending pods and autoscaler events regularly. They often explain why the cluster needs more nodes than expected.

Use spot and preemptible capacity safely

Spot and preemptible instances can significantly reduce compute cost, especially for interruptible workloads.

Good candidates:

Poor candidates:

Use taints and tolerations to place only interruption-tolerant workloads on discounted capacity:

tolerations:
- key: "capacity-type"
operator: "Equal"
value: "spot"
effect: "NoSchedule"

Pair spot usage with PodDisruptionBudgets, graceful shutdown handling, queue retries, and multi-instance-type node pools.

Do not ignore storage and network costs

Compute usually gets the attention, but storage and network charges can quietly dominate Kubernetes spend.

Storage optimization

Review:

Use different storage classes for different requirements. Not every workload needs the fastest disk type.

Network optimization

Network cost drivers include:

Reduce unnecessary cross-zone calls by understanding traffic flows. For high-volume internal traffic, topology-aware routing and zone-local design can matter as much as CPU rightsizing.

Build observability for cost and efficiency

Kubernetes FinOps needs both financial data and engineering telemetry.

Track these metrics by namespace, workload, team, and environment:

Dashboards should show trends, not just snapshots. The most useful FinOps question is often: “What changed this week?”

Tools commonly used for Kubernetes cost visibility include OpenCost, Kubecost, Prometheus, Grafana, cloud billing exports, and provider-native cost explorers. The exact tool matters less than whether teams can see cost in terms they understand: namespace, service, owner, and environment.

Add governance without blocking delivery

Governance should guide teams toward efficient defaults, not create a ticket queue for every deployment.

Use policy as code

Admission policies can prevent the most expensive mistakes:

Kyverno, OPA Gatekeeper, and native admission policies can enforce these checks close to deployment time.

Set quotas and budgets

ResourceQuota and LimitRange objects help keep namespaces from consuming unbounded cluster capacity.

apiVersion: v1
kind: ResourceQuota
metadata:
name: namespace-budget
spec:
hard:
requests.cpu: "20"
requests.memory: 80Gi
limits.memory: 120Gi
persistentvolumeclaims: "20"

Quotas should be paired with a request process for legitimate growth. If teams cannot get more capacity when needed, they will work around the platform.

Create a recurring optimization rhythm

Kubernetes cost optimization is not a one-time cleanup. It should become part of normal operations.

A practical monthly review includes:

  1. Top namespaces by cost
  2. Largest changes since last review
  3. Workloads with high request-to-usage gaps
  4. Idle or underutilized node pools
  5. Unused persistent volumes and snapshots
  6. Expensive network paths
  7. Spot/preemptible adoption opportunities
  8. Policy violations and missing labels
  9. Savings implemented and reliability impact

Keep a backlog of cost improvements the same way you keep a backlog of reliability work. The best FinOps programs make cost visible enough that engineers can fix waste during normal delivery.

Kubernetes FinOps checklist

Final thoughts

Kubernetes cost optimization works best when it is treated as engineering feedback, not finance policing. Give teams accurate allocation, useful observability, efficient defaults, and clear governance. Then cost becomes another operational signal teams can improve alongside latency, reliability, and deployment frequency.


Edit page
Share this post on:

Previous Post
Cloud FinOps Operating Model: Practical Guide for Engineering Teams
Next Post
Multi-Cloud FinOps Guide for AWS, Azure, and Google Cloud