Home / AWS

AWSHow-toIntermediate

EC2 Spot interruptions: catch the two-minute notice, drain cleanly and keep capacity

Detect Spot interruption notices and rebalance recommendations, drain work before the instance goes, and set up Auto Scaling groups so lost Spot capacity comes back on its own.

Deepak Bhardwaj7 min read

Applies toAmazon EC2 Spot Instances, EC2 Auto Scaling mixed instances groups, Amazon EventBridge, AWS FIS, AWS CLI v2

Spot Instances save money, but EC2 can take them back. You get a two-minute interruption notice, and sometimes an earlier rebalance recommendation. If nothing on the instance reacts, in-flight requests fail, jobs restart from zero and the Auto Scaling group sits below capacity until a replacement boots.

This post covers four things: confirming that interruptions are the cause, catching the signals, draining in under two minutes, and configuring Auto Scaling so capacity recovers without anyone stepping in.

Confirm Spot interruptions are the cause

First, check whether instances were actually reclaimed. Scale-in, failed health checks and other events can look the same from the application side.

# Spot requests closed by an interruption in this Region
aws ec2 describe-spot-instance-requests \
  --filters Name=status-code,Values=instance-terminated-no-capacity,instance-terminated-capacity-oversubscribed,instance-terminated-by-price,instance-stopped-no-capacity \
  --query "SpotInstanceRequests[].{id:InstanceId,type:LaunchSpecification.InstanceType,az:LaunchedAvailabilityZone,code:Status.Code,time:Status.UpdateTime}" \
  --output table

CloudTrail records each interruption as a BidEvictedEvent:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=BidEvictedEvent \
  --start-time "$(date -u -d '-7 days' +%FT%TZ)" \
  --query "Events[].{time:EventTime,resources:Resources[].ResourceName}" --output json

For an Auto Scaling group, the activity history shows why each instance left:

aws autoscaling describe-scaling-activities --auto-scaling-group-name web-asg \
  --max-items 20 --query "Activities[].{time:StartTime,cause:Cause}" --output text
Symptom Likely cause Fix
Many interruptions of one instance type in one AZ Not enough instance pools Add more instance types and AZs; use price-capacity-optimized
Users get 502s while instances are reclaimed Instance not deregistered before shutdown Deregister on notice; keep deregistration delay under 120 s
Batch jobs restart from zero No checkpointing Checkpoint to S3/EFS when a rebalance recommendation arrives
Group stays below desired capacity for minutes Replacement only launched after termination Turn on Capacity Rebalancing
instance-terminated-by-price Max price set below the current Spot price Remove the max price; it defaults to the On-Demand price

The two signals

Signal When Guaranteed? IMDS path EventBridge detail-type
Rebalance recommendation Often before the interruption notice, when the instance is at higher risk No; it can arrive late, or not at all /latest/meta-data/events/recommendations/rebalance EC2 Instance Rebalance Recommendation
Interruption notice Two minutes before stop, hibernate or terminate Yes, if an interruption happens /latest/meta-data/spot/instance-action EC2 Spot Instance Interruption Warning

Use the rebalance recommendation to start slow work early, such as checkpointing or launching a replacement. Use the interruption notice as the hard deadline.

Catch the notice on the instance

Both IMDS paths return 404 until a signal exists. Here is a small watcher that uses IMDSv2:

#!/usr/bin/env bash
# /usr/local/bin/spot-watch.sh - run as a systemd service
IMDS=http://169.254.169.254
while true; do
  TOKEN=$(curl -s -X PUT "$IMDS/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
  CODE=$(curl -s -o /tmp/spot-action -w '%{http_code}' \
    -H "X-aws-ec2-metadata-token: $TOKEN" "$IMDS/latest/meta-data/spot/instance-action")
  if [ "$CODE" = "200" ]; then
    logger -t spot-watch "Interruption notice: $(cat /tmp/spot-action)"
    /usr/local/bin/drain.sh
    exit 0
  fi
  sleep 5
done

The response body looks like {"action": "terminate", "time": "2026-10-31T14:02:00Z"}. The action can also be stop or hibernate, depending on the interruption behavior you chose.

Drain in under two minutes

Keep drain.sh short and run its steps in order: stop taking new work, finish or hand off current work, then flush state.

#!/usr/bin/env bash
# /usr/local/bin/drain.sh
TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
TG_ARN=arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/web/0123456789abcdef

aws elbv2 deregister-targets --target-group-arn "$TG_ARN" --targets Id="$ID"
systemctl stop my-worker        # stop pulling from the queue; unfinished SQS messages become visible again
sync                            # flush buffered writes

The instance role needs elasticloadbalancing:DeregisterTargets. Check the target group's deregistration delay. If it's longer than about 90 seconds, connections are still draining when the instance disappears:

aws elbv2 modify-target-group-attributes --target-group-arn "$TG_ARN" \
  --attributes Key=deregistration_delay.timeout_seconds,Value=60

Warning

Spot Instances can be terminated with only two minutes' notice, and instance store data is lost on termination. Never keep the only copy of anything on a Spot Instance. Write checkpoints and output to S3, EFS or a database while the job runs, not after the notice arrives.

For containers, let the platform do the drain. On ECS on EC2, set ECS_ENABLE_SPOT_INSTANCE_DRAINING=true in /etc/ecs/ecs.config so the agent sets the container instance to DRAINING. On EKS, Karpenter or the AWS Node Termination Handler cordons and drains the node.

Catch the notice centrally with EventBridge

An EventBridge rule gives you a record of every interruption, and it fires even when the instance is too busy to react itself:

aws events put-rule --name spot-interruptions \
  --event-pattern '{"source":["aws.ec2"],"detail-type":["EC2 Spot Instance Interruption Warning","EC2 Instance Rebalance Recommendation"]}'

aws events put-targets --rule spot-interruptions \
  --targets Id=log,Arn=arn:aws:logs:eu-west-1:111122223333:log-group:/aws/events/spot

The event's detail contains instance-id and, for warnings, instance-action. Sending events to a log group lets you query interruption counts by instance type and AZ in CloudWatch Logs Insights. A CloudWatch Logs target also needs a resource policy that lets EventBridge write to the log group. The console adds it for you; with the CLI, use aws logs put-resource-policy.

Configure Auto Scaling to recover on its own

A Spot-friendly Auto Scaling group has three things: many instance pools, the price-capacity-optimized allocation strategy and Capacity Rebalancing.

{
  "LaunchTemplate": {
    "LaunchTemplateSpecification": { "LaunchTemplateName": "web-lt", "Version": "$Latest" },
    "Overrides": [
      { "InstanceType": "m6i.large" }, { "InstanceType": "m5.large" },
      { "InstanceType": "m6a.large" }, { "InstanceType": "m7i.large" },
      { "InstanceType": "r6i.large" }
    ]
  },
  "InstancesDistribution": {
    "OnDemandBaseCapacity": 2,
    "OnDemandPercentageAboveBaseCapacity": 0,
    "SpotAllocationStrategy": "price-capacity-optimized"
  }
}
aws autoscaling update-auto-scaling-group --auto-scaling-group-name web-asg \
  --mixed-instances-policy file://mip.json \
  --capacity-rebalance \
  --vpc-zone-identifier "subnet-aaa,subnet-bbb,subnet-ccc"
  • OnDemandBaseCapacity keeps a floor that Spot can't take away. Size it for the minimum load you must serve.
  • Capacity Rebalancing starts a replacement when a rebalance recommendation arrives, then terminates the at-risk instance. Add a termination lifecycle hook if the drain needs more time than the instance gets by default.
  • Instead of listing types, you can set InstanceRequirements (vCPU and memory ranges) in the overrides. This is attribute-based instance type selection, and it widens the pool list automatically.

Tip

Before you pick instance types, check how likely a request is to succeed. aws ec2 get-spot-placement-scores --instance-types m6i.large m5.large m6a.large --target-capacity 20 --region-names eu-west-1 --single-availability-zone scores each AZ from 1 to 10. A low score means you need more types or more AZs.

Test it before EC2 does

AWS Fault Injection Service can send real interruption notices to your instances. This tests the watcher, the drain and the Auto Scaling replacement from start to finish:

{
  "description": "Interrupt 1 Spot instance in web-asg",
  "roleArn": "arn:aws:iam::111122223333:role/fis-spot-role",
  "stopConditions": [{ "source": "none" }],
  "targets": {
    "SpotInstances": {
      "resourceType": "aws:ec2:spot-instance",
      "resourceTags": { "aws:autoscaling:groupName": "web-asg" },
      "filters": [{ "path": "State.Name", "values": ["running"] }],
      "selectionMode": "COUNT(1)"
    }
  },
  "actions": {
    "interrupt": {
      "actionId": "aws:ec2:send-spot-instance-interruptions",
      "parameters": { "durationBeforeInterruption": "PT2M" },
      "targets": { "SpotInstances": "SpotInstances" }
    }
  }
}
TID=$(aws fis create-experiment-template --cli-input-json file://spot-test.json --query experimentTemplate.id --output text)
aws fis start-experiment --experiment-template-id "$TID"

While the experiment runs, watch three things: the instance logs spot-watch, the target leaves the target group, and the Auto Scaling group launches a replacement before or right after termination.

Prevent it next time

  • Use at least five or six instance types across three AZs for each Spot group, with price-capacity-optimized.
  • Turn on Capacity Rebalancing and keep an On-Demand base for the minimum load you must serve.
  • Run a watcher on every Spot instance, or use the platform's drain support (ECS agent draining, Karpenter or Node Termination Handler).
  • Keep the deregistration delay and drain time under two minutes. Checkpoint long jobs as they run.
  • Log every interruption with EventBridge and review counts by type and AZ each month.
  • Run the FIS experiment after every change to the launch template or drain logic.