EKS node groups holding live nodes at essentially zero utilization
What does ZopNight detect here?
Worker nodes bill full instance-hours whatever their pods do, so a node group whose CPU averages and peaks under 3% for 30 straight days is capacity bought for nothing. ZopNight also vetoes on memory, since a CPU-quiet group holding large caches must not be torn down, and prices the entire run-rate as recoverable.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-043b |
| Category | idle |
| Severity | high |
| Metric | none — pure configuration read |
| Source | eks_running_idle.go |
Where it applies
The 3% bar, and why it is not zero
Real nodes are never at literal zero: kubelet, system daemons, and the container runtime produce a permanent murmur of CPU. The idle floor sits at 3%, comfortably above that baseline and far below any workload, and both the 30-day average and the peak must stay under it. A group that bursts weekly above the floor is being used, however idle it looks between bursts, and abstains.
Memory can veto what CPU approves
A node group can be CPU-silent while holding memory that matters: in-memory caches, JVM heaps, stateful sets that do their work in RAM. Tearing that down evicts state the surviving capacity cannot absorb. So the memory rollup is required alongside CPU with the same 30-day coverage, and a memory-loaded group abstains even at 0% CPU. Both series come from the CloudWatch Observability EKS add-on; clusters without the agent produce no rollups, and the rule stays silent on them rather than tearing down capacity it cannot see.
How the dollar figure is built
The node group object itself is free (the money is in its EC2 workers), so the run-rate is priced directly: the node’s live on-demand hourly rate × 730 hours × the desired node count, fully recoverable on teardown. Two pricing abstentions follow. No resolvable rate means no recommendation. And Spot groups abstain entirely: only the on-demand rate is visible to the rule, and pricing a removed Spot node at on-demand would overstate the saving roughly 2–3×.
What this rule leaves to its siblings
Groups already scaled to zero, and degraded groups, belong to the abandoned-workloads rule; unhealthy or transitioning groups are skipped so a teardown is never recommended mid-repair. Only healthy, active groups with at least one desired node land here.
See the rollup yourself
aws cloudwatch get-metric-statistics \ --namespace ContainerInsights --metric-name node_cpu_utilization \ --dimensions Name=ClusterName,Value=my-cluster Name=NodegroupName,Value=my-nodegroup \ --start-time "$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)" \ --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --period 86400 --statistics Average MaximumBefore acting, check for seasonal or intermittent jobs that schedule onto this group, drain with PodDisruptionBudgets in place, and sweep for load balancers and EBS volumes the group leaves behind. Those keep billing after the nodes are gone.