ECS services whose tasks reserve far more CPU than they use
What does ZopNight detect here?
Fargate bills the CPU units a task definition reserves, not the cycles it burns, so a service averaging under 10% CPU is paying for roughly 10x its need. ZopNight names the next rung down the Fargate CPU ladder and prices the saving from the real per-task rate delta, memory term included.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-158 |
| Category | rightsizing |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | ecs_cpu_rightsizing.go |
Where it applies
Reservation billing makes low utilization expensive
On Fargate, the task definition’s CPU setting is the bill: 1024 units cost the same fully idle as fully busy. Its trigger, read from the utilization series ECS reports per service, is a 30-day CPU average under 10% (a service provisioned an order of magnitude above its duty cycle), with memory under 50% confirming the task is not secretly memory-bound, and services that are stopped, draining, inactive, or scaled to zero excluded because there is nothing running to resize.
The saving is a rate delta, not a ratio
An obvious-looking shortcut overstates this finding: halving CPU does not halve a Fargate task’s price, because the per-task rate is a CPU term plus a memory term and the memory usually stays put. An earlier version applied the CPU ratio to the whole cost and ran 1.2–1.4× hot. The current math steps the task one rung down the Fargate CPU ladder, clamps memory into the smaller tier’s valid band, and prices the saving as the difference between the two real per-task rates. Abstention follows when the task’s CPU or memory values are unknown, off-ladder, already at the floor, or the delta comes out non-positive.
Bursts veto the downsize
A service that idles all day and slams its full reservation at peak (payment burst patterns are the canonical case) is already throttling at the worst moment, and shrinking it would sharpen exactly that. The rule reads the trusted peak over the full metric window and suppresses the recommendation when it approaches saturation, however low the average.
Pull the same series
aws cloudwatch get-metric-statistics \ --namespace AWS/ECS --metric-name CPUUtilization \ --dimensions Name=ClusterName,Value=my-cluster Name=ServiceName,Value=my-service \ --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 MaximumApplying it
The change is a new task definition revision with the smaller CPU (and clamped memory) and a service update. ZopNight can drive this as an automated resize, and the same ladder-step-plus-clamp logic generates the target so the displayed and applied values never diverge. Watch latency and CPU for a week after; a service that lands at 40–60% on the smaller size is done, and one that climbs past the peak guard’s comfort was the veto case all along.