Symptom
aws ecs describe-tasksshows tasks withlastStatus: PROVISIONINGfor many minutes.- Service events say
service X was unable to place a task...or nothing at all, and deployments stall. - The ASG's desired capacity doesn't change. Or it scales out, the tasks still don't place, and later it can't scale in because instances stay protected or
DRAINING.
With a managed-scaling capacity provider, tasks that don't fit on existing instances enter PROVISIONING instead of failing immediately. ECS publishes a metric, and a target-tracking policy on the ASG reacts to it. When any part of that loop breaks, tasks wait indefinitely.
How the loop works
ECS calculates CapacityProviderReservation = M / N × 100, where M is the number of instances needed to run all running plus provisioning tasks and N is the number of instances currently in the ASG. A target-tracking policy (named ECSManagedAutoScalingPolicy-...) drives this value towards your targetCapacity.
| Reservation vs target | Meaning |
|---|---|
| Above target | Needs more instances: ASG scales out |
| Equal to target | Steady state |
| Below target | Spare instances: ASG scales in (unprotected, empty instances only) |
| 200 with N = 0 | Scale from zero: ECS needs at least one instance |
Step 1: confirm the state
CLUSTER=prod-ecs
aws ecs list-tasks --cluster $CLUSTER --desired-status RUNNING --query taskArns --output text \
| xargs -n 100 aws ecs describe-tasks --cluster $CLUSTER --query \
'tasks[?lastStatus==`PROVISIONING`].[taskArn,capacityProviderName,cpu,memory,createdAt]' --output table --tasks
aws ecs describe-capacity-providers --capacity-providers prod-ec2-cp \
--query 'capacityProviders[].{status:status,asg:autoScalingGroupProvider.autoScalingGroupArn,scaling:autoScalingGroupProvider.managedScaling,termProtect:autoScalingGroupProvider.managedTerminationProtection,draining:autoScalingGroupProvider.managedDraining,update:updateStatus}'
aws ecs describe-clusters --clusters $CLUSTER --include ATTACHMENTS \
--query 'clusters[].{cps:capacityProviders,default:defaultCapacityProviderStrategy,attachments:attachments[].status}'
Check that the capacity provider is ACTIVE, associated with the cluster, and actually used by the service's or task's capacity provider strategy. A task launched with --launch-type EC2 doesn't use the capacity provider at all.
Step 2: read the metric and the ASG's activity
aws cloudwatch get-metric-statistics --namespace AWS/ECS/ManagedScaling \
--metric-name CapacityProviderReservation \
--dimensions Name=ClusterName,Value=$CLUSTER Name=CapacityProviderName,Value=prod-ec2-cp \
--start-time "$(date -u -d '-1 hour' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 60 --statistics Maximum --query 'sort_by(Datapoints,&Timestamp)[].[Timestamp,Maximum]' --output text
ASG=prod-ecs-asg
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ASG \
--query 'AutoScalingGroups[].{min:MinSize,max:MaxSize,desired:DesiredCapacity,protect:NewInstancesProtectedFromScaleIn,inst:Instances[].[InstanceId,LifecycleState,ProtectedFromScaleIn]}'
aws autoscaling describe-scaling-activities --auto-scaling-group-name $ASG --max-items 10 \
--query 'Activities[].[StartTime,StatusCode,StatusMessage]' --output text
aws autoscaling describe-policies --auto-scaling-group-name $ASG \
--query 'ScalingPolicies[].[PolicyName,PolicyType,TargetTrackingConfiguration.TargetValue]' --output text
Step 3: match the pattern to the cause
| What you see | Cause | Fix |
|---|---|---|
| Reservation > target, desired = max | ASG at MaxSize | Raise MaxSize; review why the fleet needs more |
Reservation > target, scaling activity Failed | Launch failure: capacity, quota, launch template, AMI, subnet IPs | Fix the launch error in StatusMessage; add instance types/AZs |
ASG scaled out, instances InService, reservation still high | Instances not registering with the cluster | Check ECS_CLUSTER in /etc/ecs/ecs.config, the instance role, and the network path to ECS endpoints; list-container-instances |
| Instances registered, task still won't place | Task can't fit on any instance type in the ASG (CPU/memory/GPU/ENI) or placement constraints exclude them | Use a bigger instance type or a smaller task; review constraints |
awsvpc tasks, lots of free CPU/memory | ENI limit per instance reached | Enable awsvpcTrunking, use instance types that support trunking |
No ECSManagedAutoScalingPolicy on the ASG | Policy deleted or managed scaling disabled | Re-enable managed scaling with update-capacity-provider |
| Reservation stuck at 100 with tasks provisioning | Instances needed match instances present, so placement is the problem, not capacity | Look for daemon tasks, port conflicts (bridge/host mode static ports) or placement constraints |
Check registration and remaining resources:
aws ecs list-container-instances --cluster $CLUSTER --status ACTIVE --query containerInstanceArns --output text \
| xargs aws ecs describe-container-instances --cluster $CLUSTER --query \
'containerInstances[].[ec2InstanceId,agentConnected,status,remainingResources[?name==`CPU`].integerValue|[0],remainingResources[?name==`MEMORY`].integerValue|[0]]' \
--output table --container-instances
agentConnected: false means the agent lost contact with ECS. Restart the agent through SSM (sudo systemctl restart ecs) and check its log at /var/log/ecs/ecs-agent.log.
Tip
Task memory has to fit in the instance's registered memory, which is less than the instance type's total memory (the OS and the agent reserve some). A task asking for 16 GiB won't fit on a 16 GiB instance. The
MEMORYrow inregisteredResourcesis the real ceiling.
Step 4: apply the fix
Raise the ceiling or tune managed scaling:
aws autoscaling update-auto-scaling-group --auto-scaling-group-name $ASG --max-size 40
aws ecs update-capacity-provider --name prod-ec2-cp --auto-scaling-group-provider \
'managedScaling={status=ENABLED,targetCapacity=90,minimumScalingStepSize=1,maximumScalingStepSize=10,instanceWarmupPeriod=180},managedTerminationProtection=ENABLED,managedDraining=ENABLED'
targetCapacity< 100 keeps spare instances warm so new tasks place immediately. 100 packs instances tightly, but every new task waits for an instance launch.instanceWarmupPeriodshould be about how long an instance takes to boot and register. If it's too short, ECS scales out again before the first instances are counted.maximumScalingStepSizelimits how many instances one scale-out adds. Raise it for bursty deployments.
For ENI-bound awsvpc workloads:
aws ecs put-account-setting-default --name awsvpcTrunking --value enabled
# Applies to instances registered after the change; replace existing instances (instance refresh)
Scale-in problems: protected or draining instances that never go away
Managed termination protection requires NewInstancesProtectedFromScaleIn=true on the ASG. ECS then removes protection only from instances that are running no non-daemon tasks. If an instance never becomes empty (a long-running task, or a service whose placement strategy is spread across instances), it stays protected and the ASG can't scale in.
aws autoscaling update-auto-scaling-group --auto-scaling-group-name $ASG --new-instances-protected-from-scale-in
Managed draining moves an instance to DRAINING before ASG scale-in, Spot interruption or instance refresh, so ECS can reschedule its service tasks. Standalone tasks (run-task) aren't rescheduled. They run until they stop, which can hold an instance in DRAINING for a long time.
aws ecs list-container-instances --cluster $CLUSTER --status DRAINING
# Find what keeps it busy
aws ecs list-tasks --cluster $CLUSTER --container-instance <arn> --desired-status RUNNING
Caution
aws ecs deregister-container-instance --forceand turning off scale-in protection on an instance with running tasks kill those tasks without draining. Use them only after you've confirmed the remaining tasks are disposable or have been rescheduled elsewhere.
Prevent it next time
- Alarm on the number of
PROVISIONINGtasks (Container InsightsPendingTaskCountper service) and onCapacityProviderReservationstaying above target for 15+ minutes. - Alarm when ASG desired capacity reaches
MaxSize. - Include instance types in the ASG that can fit the largest task definition, and validate this in CI against registered memory, not nominal memory.
- Use a mixed-instances ASG across several AZs so a single capacity shortage doesn't stall scale-out.
- Keep the ECS agent current through an AMI pipeline. Old agents miss newer features and have placement bugs.
- Don't create the capacity provider, ASG and cluster associations by hand. Define them in infrastructure as code so the protection flag, policy and strategy stay consistent.