Symptoms
kubectl get podsshows pods inPendingfor minutes.kubectl describe podshowsFailedScheduling ... Insufficient cpu(or memory, ornvidia.com/gpu).- The node pool node count doesn't change, or it changes and the new node never becomes Ready.
Step 1: Ask the autoscaler what it thinks
The cluster autoscaler writes its status to a ConfigMap:
kubectl -n kube-system get configmap cluster-autoscaler-status -o yaml
Look at each node group:
Health: HealthyandScaleUp: NoActivitywith pending pods → it decided the pods won't fit on a new node (see Step 4).ScaleUp: InProgressthat never finishes → Azure failed to create the VM (Step 3).cloudProviderTargethigher thanregistered→ nodes were requested but didn't join.
Pod events are the other half:
kubectl describe pod <pod> | sed -n '/Events/,$p'
| Event text | Meaning |
|---|---|
pod didn't trigger scale-up: ... max node group size reached | Pool is at maxCount |
pod didn't trigger scale-up: ... node(s) didn't match Pod's node affinity/selector | No pool can host this pod |
pod didn't trigger scale-up: ... node(s) had untolerated taint | Pod lacks a toleration for the only pool that could scale |
TriggeredScaleUp then nothing | VM creation failed in Azure |
You can also read autoscaler logs if you enabled control plane diagnostics (category cluster-autoscaler) to Log Analytics:
AKSControlPlane
| where Category == "cluster-autoscaler"
| where TimeGenerated > ago(1h)
| project TimeGenerated, Message
| order by TimeGenerated desc
(Table name depends on whether you use resource-specific mode; older setups use AzureDiagnostics.)
Step 2: Check the pool limits
az aks nodepool list -g rg-aks -n aks-prod \
--query "[].{pool:name, auto:enableAutoScaling, min:minCount, max:maxCount, count:count, vm:vmSize, mode:mode}" -o table
If count == maxCount, raise it:
az aks nodepool update -g rg-aks --cluster-name aks-prod -n userpool \
--update-cluster-autoscaler --min-count 3 --max-count 20
Step 3: Azure couldn't create the VM
Check Activity Log for the node resource group (MC_*):
NODE_RG=$(az aks show -g rg-aks -n aks-prod --query nodeResourceGroup -o tsv)
az monitor activity-log list -g $NODE_RG --offset 2h --status Failed \
--query "[].{op:operationName.localizedValue, msg:properties.statusMessage}" -o table
Common failures:
| Message contains | Cause | Fix |
|---|---|---|
QuotaExceeded / OperationNotAllowed ... cores | vCPU quota for the VM family or region | Request quota increase for that family |
SkuNotAvailable / AllocationFailed / ZonalAllocationFailed | No capacity for that SKU in the region/zone | Add a second pool with another SKU, or allow more zones |
SubnetIsFull / InsufficientSubnetSize | Node subnet out of IPs (Azure CNI without overlay reserves IPs for pods per node) | Bigger subnet, move to CNI Overlay, or lower maxPods on new pools |
Check quota:
az vm list-usage --location centralindia \
--query "[?contains(name.value,'standardDSv5Family')]" -o table
Check subnet free IPs:
SUBNET_ID=$(az aks nodepool show -g rg-aks --cluster-name aks-prod -n userpool --query vnetSubnetId -o tsv)
az network vnet subnet show --ids $SUBNET_ID --query "{prefix:addressPrefix, used:length(ipConfigurations)}"
Step 4: The pod would not fit a new node anyway
The autoscaler simulates scheduling on a template node. If the pod still wouldn't fit, it doesn't scale.
- Requests larger than the node's allocatable: a pod asking for 16 GiB on a pool of 16 GiB VMs won't fit, because allocatable is lower after system reservations. Check with
kubectl describe node <node> | grep -A6 Allocatable. - nodeSelector / affinity pointing at a label no autoscaling pool has.
- Taints: GPU or spot pools are tainted; pods need matching tolerations.
- PVC zone: a pod with an Azure Disk PV in zone 1 can only run on a node in zone 1. If the pool's zone 1 has no capacity, it won't scale elsewhere. Use ZRS disks or one pool per zone.
- Pod topology spread with
whenUnsatisfiable: DoNotSchedulethat can't be met.
kubectl get pod <pod> -o jsonpath='{.spec.nodeSelector}{"\n"}{.spec.tolerations}{"\n"}{.spec.containers[*].resources.requests}{"\n"}'
Step 5: The new node joined but stays NotReady
Then it's not an autoscaler problem. Check the node:
kubectl describe node <new-node> | sed -n '/Conditions/,/Addresses/p'
Typical causes: egress blocked by a firewall/UDR (nodes can't pull images or reach the API server), custom DNS that can't resolve *.azmk8s.io, or CNI not ready because the subnet ran out of IPs.
Tuning the autoscaler profile
If it scales, but too slowly for bursty load:
az aks update -g rg-aks -n aks-prod \
--cluster-autoscaler-profile scan-interval=10s scale-down-delay-after-add=10m \
max-node-provision-time=15m expander=least-waste
Warning
Very aggressive scale-down settings cause node churn and pod restarts. Change one parameter at a time and watch for a week.
Prevent it next time
- Alert when any pool's node count reaches
maxCount(Container Insights metric or a scheduled KQL query). - Keep 20-30% vCPU quota headroom per VM family; review monthly.
- Size node subnets for
maxCount × (maxPods + 1)IPs, or use Azure CNI Overlay. - Run at least two pools with different VM SKUs for critical workloads so capacity shortages in one SKU don't block scaling.
- For latency-sensitive bursts, keep spare capacity with low-priority placeholder pods that get evicted first.