Idle Load Balancer
What does ZopNight detect here?
Idle load balancers show every present traffic axis at zero over 30 days. For ALBs that includes ConsumedLCUs, the only metric AWS always reports for a backend-less LB. ZopNight abstains when no traffic axis was harvested, skips LBs with registered targets, and books 100% of the monthly cost as recoverable on deletion.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-012 |
| Category | idle |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | idle_elb.go |
When an ALB counts as idle
- Per-LB-type traffic axes, read by exact name (not first-wins across the ~18 LB metrics): ALB/classic OR over all five axes RequestCount/ActiveConnectionCount/ActiveFlowCount/ProcessedBytes/ConsumedLCUs; NLB over ActiveFlowCount/ProcessedBytes/ActiveConnectionCount/ConsumedLCUs and nothing else (NLBs publish no RequestCount, so judging idle off it would be a guaranteed false positive). Fires idle only when every axis that is present reads ~0, and abstains when no traffic axis was harvested at all. Incomplete evidence must never drive a destructive delete.
- ConsumedLCUs was added because RequestCount/ActiveConnectionCount/ProcessedBytes are all reported conditionally by AWS/ApplicationELB (RequestCount only when targets are registered; the other two only when nonzero). A genuinely idle, backend-less ALB emits zero datapoints for all three (absent, not present-with-0), so the ALB path previously had no traffic axis to read and could never fire for the resource it targets. ConsumedLCUs is documented “Always reported” for both AWS/ApplicationELB and AWS/NetworkELB and is already harvested by the aggregator for load-balancer in both namespaces, so no discoverer/aggregator change was required.
- Applicability guard: Metadata[“targets”] (registered target IDs, written only when ≥1 target registered) => the LB is wired into a serving path => abstain. Fails open for a genuinely backend-less LB and on transient discoverer errors.
- RC-043 cede (2026-07, cross-resource): given EvaluationContext, RC-012 skips an idle LB that RC-043 already folds into an abandoned EKS cluster’s node-group rec. An eks-service in a cluster with an abandoned node group references it via loadBalancerIngress == dnsName, and it is priced (loadBalancerCededToRC043). This makes RC-043 the single owner of that recoverable cost (no double-count, Principle 6). Without context RC-043 abstains, so RC-012 stays the sole owner.
- isNetworkLB: Metadata[“type”] / InstanceType == “network”.
- The Description names exactly the traffic axes that were present-and-zero (the same set stamped as evidence via AddEvidenceWithThreshold), joined from evaluateTraffic’s returned axes. Previously the prose was a hardcoded string naming all four legacy axes (RequestCount/ActiveConnectionCount/ActiveFlowCount/ProcessedBytes) while, for a backend-less idle ALB, the only axis AWS actually emits (and the only one stamped/charted as “Used for this recommendation”) is ConsumedLCUs. The other four are absent, never measured-at-zero, so naming them was false. The prose and the evidence chart now derive from one source and cannot diverge.
Why an idle load balancer refunds 100%
The math is concrete: an idle LB’s hourly and LCU charges are fully recoverable on delete, so SavingsUSD = cost × idleELBSavingsFraction (1.00) and OptimizedCostUSD = 0. Skips when pricing is absent/≤0, so there is no zero-cost rec.
Deleting it safely
- Verify no scheduled or seasonal traffic is expected
- Check target groups for healthy targets
- If confirmed idle, delete the load balancer and associated target groups
- Update DNS records if applicable
The five traffic axes, and why NLBs get only four
RequestCount, ActiveConnectionCount, ActiveFlowCount, ProcessedBytes, ConsumedLCUs (30-day)