Skip to main content
emnode
Cost

Run interruptible AKS workloads on Spot node pools

One capability across every AKS cluster: move the workloads that tolerate interruption, batch jobs, CI runners, dev and test, off pay-as-you-go nodes and onto Azure Spot node pools that buy spare capacity at a deep discount, without putting anything that cannot survive an eviction at risk.

14 min·10 sections·AZURE

Last reviewed

AKS Spot node pools: the basics

What does running interruptible work on full-price nodes actually cost you?

An AKS cluster runs its pods on node pools, and by default every pool is backed by regular pay-as-you-go virtual machines. That is the right choice for anything that must stay up: the system pool that runs cluster services, an API that serves live traffic, a stateful database. But a large share of what most clusters actually run does not need that guarantee. Batch jobs, nightly data processing, CI and build runners, machine-learning training, development and test environments: all of these can be restarted if a node disappears, and paying the full on-demand rate to run them is leaving money on the table.

A Spot node pool is a user node pool backed by an Azure Spot Virtual Machine Scale Set. It buys Azure's spare, unused capacity at a steep discount instead of the on-demand price. The trade is that the capacity is not guaranteed: when Azure needs it back, it evicts the nodes with thirty seconds of notice, and your pods are rescheduled elsewhere. For interruptible work that is a fine bargain. For a live API it is not. The whole capability is about drawing that line correctly, then moving everything on the right side of it onto Spot.

Azure Advisor surfaces this directly as a cost recommendation, 'Consider Spot nodes for workloads that can handle interruptions' (resource type microsoft.containerservice/managedclusters, recommendation ID 17f6ab99-c9b6-4800-bebd-7466699cec31). It fires when Advisor sees an AKS cluster whose pattern of work looks like it could tolerate eviction. The recommendation is the prompt; the work is deciding which of your workloads genuinely can be interrupted, adding a Spot pool with the right taints and tolerations, and scheduling that work onto it.

In this lesson you will learn how AKS expresses guaranteed versus interruptible capacity, how Spot node pools work and what an eviction actually does, how to find the workloads that can safely run on Spot, and how to add a Spot pool and schedule that work onto it without touching anything that must stay up. The Azure Advisor recommendation that prompts this is the starting point; the judgement about which workloads qualify is the real content.

Fun fact

The capacity nobody else booked

Spot pricing exists because cloud datacentres are never full. At any given moment a region has servers powered on and idle, capacity that has been bought and built but not yet rented. Rather than let it sit earning nothing, Azure offers it at a deep, variable discount on the understanding that it can be reclaimed the instant a paying, on-demand customer wants it. That is the whole bargain in one line: you run on the spare seats, and you give them up when the booked passenger arrives. For work that can simply catch the next train, batch jobs, build runners, test clusters, it is close to free money, which is exactly why Azure Advisor goes looking for AKS clusters that could be using it and are not.

Finding the interruptible work in a cluster

Priya runs the platform team at a company whose AKS bill has been climbing as more teams onboard. Azure Advisor has raised the Spot recommendation on two of their busiest clusters, and the monthly compute line is now large enough that leadership has asked what is driving it.

Rather than reach for Spot blindly, Priya starts by looking at what the cluster's node pools actually are today and what kind of work sits on them, so the interruptible workloads can be separated from anything that must stay up before a single node changes.

Start by listing the node pools on the cluster and their current priority. Everything on a Regular pool is paying the full on-demand rate.

$ az aks nodepool list --resource-group rg-prod --cluster-name aks-prod --query "[].{name:name, mode:mode, priority:scaleSetPriority, vmSize:vmSize, count:count}" -o table
Name Mode Priority VmSize Count
-------- ------ ---------- --------------- -------
system System Regular Standard_D4s_v5 3
apps User Regular Standard_D8s_v5 6
# 'apps' is one big Regular pool running live APIs AND nightly batch at full price.
# The batch jobs are interruptible. They are the candidate to move onto Spot.

When a Priority of Regular is shown next to a user pool that mixes live traffic with batch work, the interruptible share is paying full price for no reason. That is the spend a Spot pool reclaims.

How an AKS Spot node pool actually behavesdeep dive

A Spot node pool is a user node pool whose underlying Virtual Machine Scale Set has 'scaleSetPriority' set to 'Spot' rather than 'Regular'. It cannot be the default system pool, because the cluster's own services must run on guaranteed capacity. When you create it, AKS automatically applies two markers so that work does not land on it by accident: the label 'kubernetes.azure.com/scalesetpriority=spot', and the taint 'kubernetes.azure.com/scalesetpriority=spot:NoSchedule'. The taint repels every pod that does not explicitly tolerate it, which is why a Spot pool stays empty until you deliberately schedule work onto it. To run on Spot, a pod needs a matching toleration, and usually a node affinity so the scheduler prefers the Spot nodes.

Pricing is governed by 'spotMaxPrice'. A value of -1 means 'accept the current Spot price up to, but never above, the on-demand price', which is the common choice: you always pay the Spot rate and are only ever evicted for capacity, never for price. You can instead set a specific maximum in US dollars, in which case a node is also evicted if the Spot price rises above your cap. The eviction policy decides what happens to an evicted node: 'Delete' (the default) removes it, and the autoscaler can create a fresh one when capacity returns; 'Deallocate' stops it in place, but it still counts against your scale set quota.

Eviction is the behaviour that defines the whole capability. When Azure needs the capacity back, it gives the node thirty seconds of notice and then reclaims it. Kubernetes sees the node go and reschedules its pods. For a stateless batch job or a build runner that means a restart; for a job that writes checkpoints it means resuming from the last one. This is why placement is everything: pair a Spot pool with the cluster autoscaler so evicted capacity is replaced when it returns, keep the system pool and any latency-sensitive or stateful workload on a Regular pool, and only tolerate the Spot taint from the workloads you have decided can survive being interrupted.

What is the impact of running interruptible work on full-price nodes?

The direct impact is pure overspend. Every node-hour that a batch job, build runner or test environment spends on a Regular pool is paying the on-demand rate for capacity that Azure would have sold you at a steep discount. Because this work is often bursty and runs at scale, machine-learning training, large nightly jobs, parallel CI, the full-price node-hours add up fast, and they are some of the easiest hours in the whole estate to discount because the work does not care which physical capacity it runs on.

The second-order impact is that the cluster's cost stops reflecting its actual reliability needs. When everything runs on guaranteed capacity, the bill treats a throwaway test pod and a customer-facing API as if they have the same requirement, which they plainly do not. That makes the compute line harder to reason about and hides the fact that a large slice of it is discretionary. Splitting interruptible work onto Spot makes the bill tell the truth: guaranteed capacity for what needs it, discounted spare capacity for what does not.

The reason this is worth doing rather than just nice to have is the asymmetry. The saving is large, recurring and a direct discount on a rate you already pay. The cost to capture it is a one-off piece of engineering plus modest resilience work, and the only ongoing risk, an eviction, is fully contained by only placing interruptible work on Spot. Azure Advisor flags the clusters where this gap exists precisely because it is one of the most reliable cost wins available in Kubernetes.

How do you move interruptible work onto Spot safely?

Work this as a deliberate split, not a blanket switch. The order matters: decide what can tolerate an eviction before you add any Spot capacity, so nothing that must stay up ever lands on a node that can disappear.

1. Classify every workload as guaranteed or interruptible

Go through the cluster's workloads and mark each one. The system pool, live APIs, stateful services and anything latency-sensitive stay on guaranteed Regular capacity. Batch jobs, CI and build runners, machine-learning training, and development and test environments are the interruptible candidates. When in doubt, leave it on Regular: this capability is about moving the clear wins, not forcing every workload onto Spot.

2. Add a Spot user node pool with the autoscaler

Create a user node pool with priority Spot, an eviction policy of Delete, and a max price of -1 so you pay the Spot rate and are only ever evicted for capacity. Enable the cluster autoscaler with a sensible min and max so the pool grows when there is interruptible work and shrinks, ideally toward zero, when there is not. AKS automatically taints the pool so nothing schedules onto it by accident.

3. Schedule the interruptible work onto it

Add a toleration for the Spot taint, and a node affinity for the Spot label, to each workload you classified as interruptible. The toleration lets the pod past the taint; the affinity tells the scheduler to prefer the Spot nodes. Keep replicas, retries and, for long jobs, checkpointing in place so a thirty-second eviction notice results in a clean reschedule rather than lost work.

4. Make the split the default and review it

Once it is working, make 'interruptible work runs on Spot' the team default rather than a one-off, and revisit the classification as workloads change. Track the share of cluster compute still running interruptible work on Regular pools as the signal of discount left on the table, and keep production firmly on guaranteed capacity.

# 1. Add a Spot user node pool to an existing AKS cluster.
#    --priority Spot is required; --spot-max-price -1 pays the Spot rate up to
#    the on-demand price, so you are only ever evicted for capacity, not price.
az aks nodepool add \
  --resource-group rg-prod \
  --cluster-name aks-prod \
  --name spot \
  --priority Spot \
  --eviction-policy Delete \
  --spot-max-price -1 \
  --node-vm-size Standard_D8s_v5 \
  --enable-cluster-autoscaler \
  --min-count 0 \
  --max-count 10

# AKS applies these automatically to the new pool:
#   label: kubernetes.azure.com/scalesetpriority=spot
#   taint: kubernetes.azure.com/scalesetpriority=spot:NoSchedule
# so nothing schedules onto Spot until a workload tolerates the taint.

# 2. Tolerate the taint and prefer Spot nodes on an interruptible workload.
#    Apply this to batch jobs, CI runners, ML training, dev/test, never to
#    the system pool or a live, stateful or latency-sensitive service.
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nightly-etl
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nightly-etl
  template:
    metadata:
      labels:
        app: nightly-etl
    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
      containers:
        - name: etl
          image: myregistry.azurecr.io/nightly-etl:latest
EOF

Quick quiz

Question 1 of 5

Azure Advisor raises 'Consider Spot nodes for workloads that can handle interruptions' on an AKS cluster. What is the right way to act on it?

You can now treat AKS compute as two categories rather than one: guaranteed capacity for what must stay up, and discounted Spot capacity for the interruptible work that can simply restart. Classify each workload, add a Spot user node pool with the autoscaler, tolerate its taint only from the work that can survive an eviction, and make 'interruptible runs on Spot' the default. The Azure Advisor recommendation that started this is just the prompt; the saving comes from acting on it deliberately.

Back to the library