FinOps & Cloud Engineering · Jan 2026 · 13 min read
FinOps for Kubernetes: Cost Optimization & Sizing on Azure AKS
Practical strategies to slash compute spend by 40% on AKS clusters using spot node pools, KEDA auto-scalers, and Azure Data Lake Gen2 tiering.
Introduction to Kubernetes FinOps
Azure Kubernetes Service (AKS) makes it easy to deploy microservices. It makes it just as easy to over-provision and over-spend — idle worker pods, oversized node pools, and logs sitting in the most expensive storage tier all accumulate quietly.
A production-ready AKS cost model should incorporate:
- Spot capacity for interruption-tolerant workloads
- Event-driven autoscaling instead of always-on replicas
- Storage lifecycle tiering for logs and reports
- Right-sized requests/limits instead of guessed defaults
- Reserved capacity for steady-state baseline load
- Namespace-level cost visibility
- Governance that survives someone forgetting to clean up
The goal is not to make the cluster as cheap as possible — it is to ensure spend tracks actual demand, not peak-provisioned capacity.
1. Spot Node Pools with Fallback Routing
Azure Spot Virtual Machines offer unused Azure capacity at up to a 90% discount. In exchange, they can be evicted at any time with little notice.
Use a multi-pool architecture so eviction risk is isolated to workloads that can tolerate it:
AKS Cluster
|
+---------------+---------------+
| | |
v v v
System Pool Primary User Fallback User
(Standard) Pool (Spot) Pool (Standard)
| | |
CoreDNS, Stateless Scales up on
Metrics batch/API Spot eviction
Server workloads
Use node affinity and tolerations so pods prefer Spot capacity but can still schedule on the Standard fallback pool:
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
template:
spec:
tolerations:
- key: "kubernetes.azure.com/scalesetpriority"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.azure.com/scalesetpriority
operator: In
values:
- spot
Do not schedule stateful or latency-critical workloads onto Spot pools without an explicit eviction-handling strategy — a preference weight alone does not guarantee availability.
2. Event-Driven Autoscaling with KEDA
The Horizontal Pod Autoscaler (HPA) scales on CPU/memory, which is a poor signal for queue-processing workers — a worker can sit at 5% CPU while a queue backs up, or run 5 replicas 24/7 waiting for infrequent messages.
KEDA (Kubernetes Event-driven Autoscaling) scales on the actual event source instead:
Azure Service Bus Queue
|
queue depth
|
v
KEDA ScaledObject
|
0 messages -> scale to 0 pods
N messages -> scale toward maxReplicaCount
A queue-depth-based ScaledObject lets a worker deployment sit at zero replicas when there is nothing to process, instead of holding idle pods (and idle node capacity) around the clock.
Do not assume every workload benefits from scale-to-zero — a worker with a multi-second cold start may need minReplicaCount above 0 to meet latency requirements.
3. Azure Data Lake Gen2 Lifecycle Tiering
Kubernetes workloads generate logs, reports, and analytical output continuously. Leaving all of it in the Hot access tier is one of the most common and easiest-to-fix sources of AKS-adjacent waste.
Blob Age
|
+---- Day 0-30 -> Hot tier
|
+---- Day 31-90 -> Cool tier
|
+---- Day 91+ -> Archive tier
Configure a storage account lifecycle management policy to transition blobs automatically rather than relying on a manual or forgotten process.
For example, moving 10TB of logs from Hot to Archive:
Hot tier: ~$180/month
Archive tier: ~$10/month
That is roughly a 94% reduction on data that is rarely, if ever, read again after 90 days. Archive tier has a retrieval latency of hours, not milliseconds — do not archive data your application or on-call process may need to read back quickly.
4. Right-Sizing Requests and Limits
Spot pools and lifecycle tiering address obviously wasteful patterns. A quieter, often larger source of waste is pods requesting far more CPU/memory than they use.
kubectl top nodes
kubectl top pods -A
Compare requested capacity against actual utilization over at least several days of production traffic, not a single snapshot. Consistently over-provisioned requests inflate the Cluster Autoscaler's view of required node capacity even when actual usage is low.
Do not right-size from CPU alone — check memory, and check whether a workload has periodic spikes (batch jobs, deploy-time warmup) that a short observation window would miss.
5. Reserved Instances and Savings Plans for Baseline Load
Spot capacity and autoscaling handle variable load well. They do nothing for the steady-state baseline a cluster always runs — the System node pool and any node pool that never scales below a known floor.
For that predictable baseline, Azure Reservations or Savings Plans can reduce compute cost significantly compared to pay-as-you-go pricing, in exchange for a 1- or 3-year commitment.
Do not commit reserved capacity to your peak observed node count. Commit to the floor — the capacity you are confident you will run every hour of every day — and let Spot and autoscaling continue to handle the variable portion above it.
6. Namespace-Level Cost Visibility
A cluster-wide bill tells you AKS is expensive. It does not tell you which team, application, or environment is responsible.
Enable AKS Cost Analysis (built on Kubernetes cost allocation) to break spend down by:
- Namespace
- Deployment / workload
- Node pool
- Label / cost-center tag
+------------------------------------------------+
| AKS COST BREAKDOWN |
+------------------------------------------------+
| Namespace | Compute | Storage |
|------------------|--------------|--------------|
| checkout-prod | $4,120 | $310 |
| batch-jobs | $1,860 | $95 |
| ml-inference | $2,940 | $640 |
+------------------------------------------------+
Without this, cost accountability defaults to "the platform team," which removes any incentive for individual teams to right-size their own workloads.
Operational Checklist
Before treating an AKS cost-optimization pass as complete, verify:
- Interruption-tolerant workloads are scheduled on Spot node pools with a Standard fallback pool.
- Tolerations and node affinity are configured, not just affinity alone.
- Queue-driven workers use KEDA instead of static replica counts.
- Data Lake Gen2 lifecycle policies transition Hot -> Cool -> Archive automatically.
- Archived data's retrieval latency has been validated against actual recovery requirements.
- Pod requests/limits have been reviewed against real utilization, not defaults.
- Reserved capacity or Savings Plans cover the cluster's steady-state floor only.
- AKS Cost Analysis is enabled and broken down by namespace.
- Resource groups and workloads are tagged with a cost center.
- Savings are tracked over time, not measured once and forgotten.
Final Takeaway
AKS cost optimization is not a single node-pool setting — it is a layered set of decisions across compute, storage, and visibility.
Spot capacity → event-driven autoscaling → storage tiering → right-sizing → reserved baseline → cost attribution.
The strongest AKS cost posture assumes utilization will drift as workloads change, and builds in the visibility to catch that drift before it becomes next month's surprise invoice.
AKS FinOps = Spot + KEDA + Storage Tiering + Right-Sizing + Reservations + Cost Visibility
FinOps · Kubernetes · AKS · KEDA · Data Lake Gen2 · Azure