AKS Node Autoprovisioning: Karpenter Economics for Your Cluster

The cluster autoscaler from the original AKS post scales node pools: pending pods appear, it adds identical VMs to a pre defined pool. Node autoprovisioning (NAP), built on Karpenter, removes the pool from the equation: it looks at the pending pods’ actual requirements and provisions whatever VM shapes satisfy them best right now, from across the SKU catalog, then continuously reshuffles for efficiency. The difference is economic as much as operational.

How the Model Changes

With pools, you guess shapes in advance: the apps pool is D8ds v5 because someone sized it in a design review, and every scaling event adds another one whether the pending pods need 8 vCPUs or 2. With NAP you declare constraints instead: a NodePool custom resource says which VM families, capacity types, zones, and limits are acceptable, and Karpenter bin packs pending pods onto the cheapest satisfying combination, often provisioning in well under a minute because it talks to the VM API directly rather than through scale set choreography. The second engine is disruption: consolidation watches for underutilized nodes and actively replaces two half empty VMs with one right sized one, or moves workloads off a node whose spot price stopped making sense, subject to your disruption budgets and the pod disruption budgets applications declare, which the chaos post already made you test.

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    spec:
      requirements:
        - key: karpenter.azure.com/sku-family
          operator: In
          values: ["D", "E"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: topology.kubernetes.io/zone
          operator: In
          values: ["westeurope-1", "westeurope-2", "westeurope-3"]
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 5m
  limits:
    cpu: "400"
    memory: 1600Gi

Spot as a First Class Citizen

The capacity type requirement above is where the FinOps posts cash in: Karpenter prefers spot when allowed and handles eviction by provisioning replacement capacity as the eviction notice lands. The pattern that works: workloads declare their tolerance, batch and stateless services tolerate the spot taint and ride the discount, stateful and latency critical workloads pin on-demand via node selectors, and the NodePool’s breadth across families is the availability hedge, spot scarcity in one SKU family routes to another instead of to failure. Combined with consolidation, the cluster’s shape at 3 AM genuinely differs from its shape at noon, which is the elasticity the cloud always advertised and pool based scaling only approximated.

Operating It

NAP shifts the discipline from sizing pools to declaring truthfully. Resource requests become load bearing: Karpenter provisions against requests, so the under requested pod that coasted on pool headroom now lands on a node sized to its lie and gets throttled or evicted, meaning the requests hygiene from the observability tooling is a prerequisite, not a nicety. Set NodePool limits so a runaway deployment cannot provision the subscription’s quota, keep a small static system pool for the critical addons exactly as the original design post prescribed, and give disruption sensitive workloads the do not disrupt annotation plus honest PDBs rather than fighting consolidation globally, because consolidation is where the savings live. Watch the drift feature too: when you update the node image or a NodePool requirement, Karpenter progressively replaces non conforming nodes, which quietly becomes your node upgrade mechanism, one more pool era chore absorbed into declarative machinery. The cluster stops being a fleet you size and becomes a market you set rules for, and the rules, as everywhere in this series, live in Git.

Cheers
Osama

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.