AKS node auto provisioning (NAP): what it is, how to enable

Managed Karpenter on AKS: where it stands in October 2026, what it needs, and how to turn it on and off.

Published 1 November 2026 · By Saurav (opens in a new tab), Backend engineer, Zoorik · Published by Zoorik

AKS node auto provisioning: pending pods go to the VM size that fits

Short answer. AKS node auto provisioning (NAP) is Microsoft’s managed Karpenter: it picks VM sizes for pods that can’t be scheduled, and consolidates empty or underused nodes. On AKS Standard, enable it with az aks update --node-provisioning-mode Auto on a cluster using Azure CNI Overlay or Node Subnet, not kubenet.

AKS node auto provisioning is Karpenter that AKS runs for you

Picture three pods on an AKS cluster with NAP on, each requesting 2 CPU. No node has room for them, so they stay pending. NAP reads their requests, 6 CPU in all.

It “uses pending pod resource requirements to decide the optimal VM configuration,” within the rules of the NodePool those pods match. Then it launches the VM.

When nodes empty out or run underused, the NodePool’s consolidation policy decides whether NAP removes them. Karpenter consolidation covers those policies in depth.

NAP sizes a node from pending pods' requests, then consolidates it A mechanism, left to right. A box of 3 pending pods, drawn as 3 teal blocks, 2 CPU requested each. An arrow labelled 6 CPU of requests leads to NAP, the highlighted subject, which picks a VM size. Above NAP, the default NodePool, D family, on-demand, amd64, Linux, joins down to it by an arrow labelled limits the choice. An arrow labelled writes a NodeClaim leads from NAP to a box reading New node joins. Below that node, a dashed arrow labelled if it empties or runs underused leads to a dashed box: NAP removes it, when the NodePool's consolidation policy allows. NAP sizes a node from pending pods' requests, then consolidates it default NodePoolD family, on-demand, amd64, Linuxlimits the choice3 pending pods2 CPU requested eachNAPpicks a VM sizeNew node joins6 CPU of requestswrites a NodeClaimif it empties or runs underusedNAP removes itwhen the NodePool's consolidation policy allows
Illustration: the three pods from the opening, matched by the NodePool named default.

What NAP runs, and the objects it uses

Microsoft’s overview says NAP “automatically deploys, configures, and manages Karpenter on your AKS clusters.”

It’s built on the open-source Karpenter project and the AKS Karpenter provider. AKS runs it in the managed control plane, and Microsoft says it’s always on the latest Karpenter version. To read its logs, turn on AKS control plane logs.

Three kinds of object do the work:

  • NodePool resources are yours to write. They say which VM sizes, capacity types and totals NAP may use.
  • AKSNodeClass resources, also yours, hold Azure settings such as the node image family and the subnet.
  • NodeClaims come from NAP, which writes one for each node it launches. kubectl get nodeclaims lists them.

NAP sizes nodes from requests, not use

NAP sizes new nodes from what pending pods request, not from what they use. Microsoft’s pre-migration checklist for NAP asks you to size workloads for consolidation. It also asks for proper requests, limits, replicas and PodDisruptionBudgets.

NAP still looks for room for all 6. Once they run, 6 − 1.5 = 4.5 CPU of that room is reserved and idle. That’s 4.5 ÷ 6, or three-quarters of what they request.

Three pods reserve 6 CPU to run 1.5 CPU of work Two rows for the same 3 pods on one shared scale from 0 to 6 CPU, with ticks at 0, 1.5, 3, 4.5 and 6. A highlighted bracket over the top row reads NAP looks for room for all 6 CPU. Requested, 3 × 2 CPU: a teal bar across the whole scale to 6. Used, 3 × 0.5 CPU: a violet segment to 1.5 CPU, then an amber span from 1.5 to 6 labelled 4.5 CPU reserved and idle, 3/4 of requests. Three pods reserve 6 CPU to run 1.5 CPU of work NAP looks for room for all 6 CPURequested3 × 2 CPUUsed3 × 0.5 CPU1.5 CPU4.5 CPU reserved and idle, 3/4 of requests01.534.56CPU
Illustration: the three pods from the opening, each requesting 2 CPU and using 0.5 CPU.

With NAP, requests decide which nodes you pay for, so Kubernetes cost optimization starts with requests. The EKS and AKS pricing calculator shows what a VM size costs a month.

AKS NAP status in October 2026: generally available, with limits

Guides written before NAP became generally available in July 2025 can be out of date. This is where NAP stands on Microsoft’s own pages:

QuestionAnswer, October 2026
Status
Generally available since July 2025
AKS Automatic
Preconfigured by default
AKS Standard
Off until you set the node provisioning mode to Auto
Azure CLI
2.76.0 or later
Supported networking
Azure CNI Overlay or Azure CNI Node Subnet. Microsoft recommends Overlay with the Azure CNI Powered by Cilium data plane
Not supported
kubenet, Calico network policy, Azure CNI Pod Subnet with dynamic IP allocation
Other limits
No Windows node pools, no IPv6 clusters, no service principals (use a managed identity), and you can’t stop a NAP cluster
Custom virtual network
Needs a Standard Load Balancer
Cluster autoscaler on the same cluster
Only with the AllowClusterAutoscalerAndNAP feature flag, as a migration step

How to enable NAP on AKS depends on where your cluster starts

Check three things before you run anything:

  • the Azure CLI version, with az --version
  • that the cluster uses a managed identity
  • its network plugin

The plugin and whatever scales your nodes today then set the path:

  • A kubenet cluster needs a network move first.
  • A cluster autoscaler hands over to NAP.
  • A self-hosted Karpenter install makes way for it.

Otherwise, enabling NAP takes one update.

Your network plugin and current autoscaler set the path to NAP A checklist for an existing cluster, read top to bottom. Three questions, each with a yes arrow to an action on its right and a no arrow down to the next. On kubenet? Yes: Move to Azure CNI Overlay first, one-way, reimages every node pool, then on to question 2. Cluster autoscaler on? Yes: Turn it off, enable NAP, then empty user pools in waves. Self-hosted Karpenter installed? Yes: Run the latest Karpenter, keep its CRDs, uninstall the chart, enable NAP without default NodePools. No to all three leads to the highlighted end: Enable NAP with one update. Your network plugin and current autoscaler set the path to NAP On kubenet?Move to Azure CNI Overlay firstone-way, reimages every node poolyesCluster autoscaler on?Turn it off, enable NAP,then empty user pools in wavesyesSelf-hosted Karpenter installed?Run the latest Karpenter, keep its CRDs,uninstall the chart, enable NAPwithout default NodePoolsyesnononothenEnable NAP with one update
The path for an existing AKS Standard cluster. A new cluster turns NAP on at creation.

New and existing clusters

For a new cluster, Microsoft’s example turns NAP on at creation, with the networking it recommends:

az aks create \
  --name <cluster-name> \
  --resource-group <resource-group> \
  --node-provisioning-mode Auto \
  --network-plugin azure \
  --network-plugin-mode overlay \
  --network-dataplane cilium \
  --generate-ssh-keys

For an existing cluster on a supported Azure CNI mode with no cluster autoscaler, it’s one update:

az aks update \
  --name <cluster-name> \
  --resource-group <resource-group> \
  --node-provisioning-mode Auto

To confirm that NAP is active, Microsoft’s check is kubectl get crd | grep karpenter. Once NAP is on, it lists the NodePool and AKSNodeClass CRDs. In an ARM template the same switch is properties.nodeProvisioningProfile.mode, which defaults to Manual.

Clusters on kubenet

NAP won’t run on kubenet, so the cluster has to move to Azure CNI Overlay first. Microsoft calls that move “a one-way, irreversible operation.” It needs:

  • Kubernetes 1.27 or later
  • no network policy enabled
  • a user-assigned managed identity, so that AKS can detach kubenet’s route table

The move reimages every node pool at the same time, so plan it like a node image upgrade. Then run:

az aks update \
  --name <cluster-name> \
  --resource-group <resource-group> \
  --network-plugin azure \
  --network-plugin-mode overlay

If you also want the Cilium data plane, add it as a second update (--network-dataplane cilium) before you enable NAP. Microsoft notes that a cluster with NAP on can’t be updated to Cilium without turning NAP off first.

Clusters running the cluster autoscaler

Microsoft gives two paths. The simple one takes three steps:

  1. Switch the autoscaler off. Nodes aren’t removed when it goes off, so capacity holds while you switch.
az aks update \
  --resource-group <resource-group> \
  --name <cluster-name> \
  --disable-cluster-autoscaler
  1. Turn NAP on with the az aks update command above.
  2. Keep the system pool, and empty your user node pools gradually, in waves. Scaling a pool to zero evicts its pods, and NAP launches nodes for the pods that move. Watch replica counts and PodDisruptionBudgets as you go.
az aks nodepool scale \
  --resource-group <resource-group> \
  --cluster-name <cluster-name> \
  --name <pool-name> \
  --node-count 0

The second path runs both side by side under the AllowClusterAutoscalerAndNAP feature flag. Taints keep the two sets of nodes apart. Microsoft warns that both react to pending pods and “can lead to double provisioning of nodes,” so treat it only as a migration step.

Karpenter vs Cluster Autoscaler explains how the two remove nodes differently.

Clusters running self-hosted Karpenter

If you run Karpenter on AKS yourself, from the AKS provider’s Helm chart, Microsoft documents how to switch to NAP:

  1. Run the latest Karpenter first.
  2. Keep the Karpenter CRDs. Deleting them deletes the NodeClaims, which can disrupt workloads.
  3. Uninstall the chart.
  4. Enable NAP with --node-provisioning-default-pools None, so your existing NodePools stay in charge.

NAP NodePools are separate from AKS node pools

On a NAP cluster, “node pool” means two things:

  • An AKS node pool is a group of identical VMs that you manage through the AKS API and az aks nodepool commands. Your system node pool belongs to this kind, and AKS still requires one for system components such as CoreDNS.
  • A NAP NodePool is a Karpenter custom resource you apply with kubectl. Microsoft is plain about which of the two NAP reads: it “doesn’t use Azure CLI commands to manage node behavior.”

With --node-provisioning-default-pools Auto, the default, NAP creates two NodePools. The one named default:

  • allows on-demand Linux VMs on amd64 from the D family
  • consolidates nodes that are empty or underused
  • never expires them

Microsoft documents it like this:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
  template:
    spec:
      nodeClassRef:
        group: karpenter.azure.com
        kind: AKSNodeClass
        name: default
      expireAfter: Never
      requirements:
      - key: kubernetes.io/arch
        operator: In
        values:
        - amd64
      - key: kubernetes.io/os
        operator: In
        values:
        - linux
      - key: karpenter.sh/capacity-type
        operator: In
        values:
        - on-demand
      - key: karpenter.azure.com/sku-family
        operator: In
        values:
        - D

The second, system-surge, gives critical add-ons their own on-demand capacity on amd64 nodes. It’s for add-ons that tolerate the CriticalAddonsOnly=true:NoSchedule taint.

With None, NAP creates no NodePools, so you write your own: NAP needs at least one. Switching an existing cluster from Auto to None leaves the defaults in place until you delete them.

A few rules shape your own NodePools:

  • Keep their requirements from overlapping. When more than one matches a pod, NAP uses the one with the highest weight, from 1 to 100.
  • spec.limits caps the memory and CPU a NodePool can hold. Without limits, NAP schedules within your Azure quota.
  • If a NodePool allows both spot and on-demand, NAP prefers spot.
  • A NodePool with a replicas field is a static pool. It keeps that many nodes whatever the demand, takes only a nodes limit, and isn’t consolidated.
  • Selectors such as karpenter.azure.com/sku-family, karpenter.azure.com/sku-name and topology.kubernetes.io/zone narrow the VM sizes and zones NAP may pick.

Node auto provisioning on AKS with Terraform is one cluster property

Microsoft’s Terraform reference for AKS covers the AzAPI provider. There, the cluster resource takes the same properties as an ARM template. On Microsoft.ContainerService/managedClusters, NAP is the nodeProvisioningProfile property.

Its mode is Auto or Manual, and defaults to Manual. Its defaultNodePools is Auto or None, and defaults to Auto. Pair it with Azure CNI in networkProfile.

This example uses the API version from Microsoft’s ARM template for NAP:

resource "azapi_resource" "aks" {
  type      = "Microsoft.ContainerService/managedClusters@2025-05-01"
  name      = "<cluster-name>"
  parent_id = "<resource-group-id>"
  location  = "<region>"

  identity {
    type = "SystemAssigned"
  }

  body = {
    properties = {
      dnsPrefix = "<dns-prefix>"
      agentPoolProfiles = [
        {
          name   = "system"
          mode   = "System"
          count  = 3
          vmSize = "<vm-size>"
        }
      ]
      networkProfile = {
        networkPlugin     = "azure"
        networkPluginMode = "overlay"
        networkDataplane  = "cilium"
      }
      nodeProvisioningProfile = {
        mode = "Auto"
      }
    }
  }
}

The moves to Azure CNI Overlay and to the Cilium data plane each reimage every node pool. AKS won’t take both in one operation, so read the plan before each apply. NAP goes last because a cluster with NAP on can’t move to Cilium.

NodePools and AKSNodeClasses aren’t properties of that resource: they’re Kubernetes objects, applied to the cluster once it exists.

How to disable node auto provisioning on AKS: empty NAP’s nodes first

AKS lets you turn NAP off only when no NAP nodes are left and every NodePool has spec.limits.cpu set to 0. Microsoft’s order:

  1. Set spec.limits.cpu: 0 on every NodePool. NAP stops creating nodes, and running ones stay.
  2. Add a taint with key karpenter.azure.com/disable and effect NoSchedule to every NodePool’s template. NAP starts moving pods to non-NAP nodes where they fit, within PodDisruptionBudgets and disruption limits.
  3. Scale up your fixed-size AKS node pools, or add new ones, so those pods have somewhere to go.
  4. Run kubectl get nodes -l karpenter.sh/nodepool until it returns no nodes. If some remain, the cluster likely lacks fixed-size capacity, so add more.
  5. Switch the mode back: az aks update --name <cluster-name> --resource-group <resource-group> --node-provisioning-mode Manual.

Microsoft also documents a shorter route, and recommends against it. That route “might leave some pods pending and doesn’t honor Pod Disruption Budgets (PDBs).”

It skips steps 2 and 3 and deletes each NAP node with kubectl delete node. If you take it anyway, check the label above first, so you delete only NAP nodes.

How Coral works with NAP

Coral optimizes Kubernetes on AKS and EKS clusters running 1.30 or later. NAP picks machines for the requests it’s given, but doesn’t change those requests.

Coral rebalances each container’s CPU and memory requests to what it uses. With smaller requests, NAP can run fewer or smaller nodes. Coral never adds nodes for demand, so scale-up stays with NAP.

What connect turns on

With your approval, connecting an AKS cluster with no node autoscaler to Coral turns on NAP. It also adds Coral’s node class, zoorik-base. Connect lists every cloud change and asks once, and --dry-run prints them all without changing anything.

A few cases go differently:

  • Kubenet. NAP needs Azure CNI, so a kubenet cluster gets no autoscaler this way. Nothing adds nodes until one exists.
  • The Cluster Autoscaler. Connect leaves it in place and doesn’t turn on NAP.
  • Later. If you decline the cloud changes, zoorikctl cluster prepare sets up NAP when you’re ready.
  • Disconnect. NAP stays.

Consolidation and rebalancing with NAP

  • Pools NAP consolidates. Coral leaves them to NAP. It deletes machines itself only in fixed-size pools that nothing else manages.
  • Cheaper machines. With NAP in place, Coral can rebalance work onto smaller or cheaper machines. It creates fixed-size NodePools for the move, and NAP launches the new machines. The old nodes are then drained and removed one by one.
  • Disruption. A rebalance never goes past a PodDisruptionBudget. Where Coral starts a new copy of a pod first, that copy must be Ready and serving before the old one goes. Other pods are evicted, so an app with a single copy gets a short restart.
  • Protected pods. A pod annotated karpenter.sh/do-not-disrupt: "true" is never moved, and its node is kept. A node Coral can’t empty in time is kept too.

On our own AKS development cluster, Coral removed one of two 4-CPU workers. It then rebalanced the remaining worker onto a cheaper machine of the same size. Eight days after we connected it, the cluster cost $154.76 a month instead of $368.65.

How we reduced AKS cluster costs by 58% has the details. For a side-by-side view, see what Coral and NAP each do. For where NAP sits beside the HPA, the VPA and the Cluster Autoscaler, see HPA vs VPA vs Cluster Autoscaler.

Find your own number

One line in your cloud console. One PDF report with your estimated saving at list price.

Estimate your saving

Talk to the builders

Thirty minutes with the people who build Coral.

Book a demo (opens in a new tab)

Or write to founders@zoorik.com