AKS node pools under 10% CPU that could run a smaller VM size
What does ZopNight detect here?
ZopNight flags an AKS node pool when node CPU averages under 10% across 30 days while memory stays below 50%, then prices the move to the next-smaller VM size in the same family from real hourly rates. Memory-bound pools and pools with no priced smaller SKU are never flagged.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1357 |
| Category | rightsizing |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | aks_underutilized.go |
Where it applies
Kubernetes hides idle nodes well
Cluster dashboards show pods, deployments, and namespaces, not the fact that every node in a pool is coasting at single-digit CPU. The bill, meanwhile, is driven entirely by the VM size and node count underneath. ZopNight reads the platform’s node-level CPU series for the agent pool; an average under 10% across the window marks the pool as provisioned well past its workload.
Memory is the veto that keeps this safe
Plenty of Kubernetes workloads are memory-hungry and CPU-light, JVM services being the classic case. A pool at 8% CPU but 70% memory is not oversized; it is memory-bound, and smaller nodes would evict pods. The rule therefore checks the full memory series and suppresses the finding whenever memory utilization reaches 50%. Crucially, this guard reads the entire harvested history, not a recent slice: a pool that ran memory-hot three weeks ago keeps its protection even if it cooled since. Disk utilization is attached as context but never gates.
Savings priced from the actual SKU ladder
The recommendation names the next-smaller VM size in the same family, so a Standard_D8s_v5 pool steps to Standard_D4s_v5. The dollar figure is the real hourly-rate delta between the two SKUs in your region, never a flat percentage. Both rates must be present in the pricing catalog, and SKUs that AKS restricts are filtered from the ladder. No nameable target or no rate means abstain.
Watch pool-level utilization directly
az monitor metrics list \ --resource "$(az aks show -n <cluster> -g <rg> --query id -o tsv)" \ --metric node_cpu_usage_percentage node_memory_working_set_percentage \ --interval PT1H --offset 30d --aggregation Average \ --filter "nodepool eq '<pool>'"Acting on the finding
Two paths capture the saving: create a new pool at the smaller size and drain workloads over with node selectors, or reduce node count and let the cluster autoscaler enforce the floor. Watch pending pods and PodDisruptionBudget violations during the move.
What the detection needs
Reader and Monitoring Reader cover it; the CPU series must span at least 7 trustworthy days before any decision. ZopNight’s AKS write role can resize pools during scheduled windows, but this finding ships as advice, not automation.