Course outline

6.5

Auto Scaling Groups and Scaling Policies

Min/desired/max, launch templates, target tracking, step, scheduled and predictive scaling, cooldowns, health checks and lifecycle hooks.

33 min Reading + animations

Tip: tap or hover any dotted-underlined term to see what it means.

By the end of this lesson you will be able to

  • Explain what an Auto Scaling group (ASG) does: keep a healthy number of instances between a minimum and a maximum, replace failed ones, and spread them across Availability Zones.
  • Say why launch templates replaced launch configurations, and what only a launch template can do.
  • Connect an ASG to a load balancer, choose the right health check type and grace period, and fix the “load balancer says unhealthy but nothing is replaced” problem.
  • Choose between target tracking, step, simple, scheduled and predictive scaling, pick a good metric, and size a group from a queue backlog.
  • Use cooldowns and the default instance warm-up correctly, and stop runaway scaling.
  • Follow an instance through its lifecycle: lifecycle hooks, standby, detach, scale-in protection, termination policies, instance refresh and warm pools.
  • Mix On-Demand and Spot in one group with capacity rebalancing, and cover the always-on part with Reserved Instances or Savings Plans.

#Why automatic scaling

Lesson 6.1 showed why web tiers scale horizontally: add servers when the load grows, remove them when it falls. Lessons 6.2 to 6.4 put a load balancer in front so the servers look like one. What is missing is the part that decides how many servers there should be and creates or removes them without anyone watching a dashboard at 3 AM.

Real load is never flat. A shop is quiet at night, busy at lunch, and crazy during a sale. If you run enough servers for the peak all day, you pay for idle machines most of the time. If you run enough for the average, the site slows down or falls over at the peak. In a data centre you had no choice, because buying a server took weeks. In AWS an instance starts in about a minute and stops costing money the second it is terminated, so the number of servers can follow the load.

#What an Auto Scaling group is

An Auto Scaling group is a collection of EC2 instances that EC2 Auto Scaling manages as one unit. You give it three numbers and a recipe:

SettingMeaningExample
Minimum capacityNever fewer than this many instances, whatever the load2
Desired capacityHow many it should run right now. Scaling policies, schedules and you change this number; the group then launches or terminates to match it4
Maximum capacityNever more than this, whatever the policies ask for: a safety limit for your bill and your quotas10
Launch templateThe recipe for every new instance: AMI, instance type, security groups, IAM role, user data…web-lt, version $Default
SubnetsWhere instances may go; choose subnets in two or more AZsone private subnet in each of 3 AZs

What the group does with them:

  • Keeps the count at desired. If an instance is terminated (by a failure, a Spot interruption, or someone clicking terminate), the group launches a replacement.
  • Replaces unhealthy instances. It checks every instance periodically, terminates the ones that fail and launches new ones (next section).
  • Scales out and in when a scaling policy or schedule changes the desired capacity, but always stays between min and max.
  • Spreads instances evenly across the AZs you chose, launching each new instance in the AZ with the fewest. If an AZ fails, it launches in the others; when the AZ recovers, it rebalances.
  • Registers instances with a load balancer automatically when you attach a target group, and deregisters them before terminating.

An ASG lives in one Region and can span all AZs of that Region, but never two Regions. The group itself is free: you pay only for the instances and other resources (EBS volumes, data transfer, CloudWatch detailed monitoring) it launches.

#The recipe: launch templates

To launch an instance, the group needs to know which AMI, which instance type, which security groups, which key pair, which IAM role, which user data, which EBS volumes. That recipe is a launch template, which lesson 4.3 covered in depth: numbered, immutable versions, a default version, and $Default / $Latest. An ASG points at a template and a version; the safe pattern is $Default (or a fixed number), promoting a new version on purpose.

Before launch templates there were launch configurations. They are legacy, and AWS is retiring them step by step:

DateWhat changed for launch configurations
31 December 2022No support for any EC2 instance type released after this date
1 June 2023Accounts created from this date cannot create them in the console
1 October 2024Accounts created from this date cannot create them by any method (console, CLI, API, CloudFormation)

Older accounts can still use the ones they have, but AWS asks everyone to migrate. What a launch configuration never could do is exactly what exam questions ask for:

NeedLaunch templateLaunch configuration
Versions, a default version, rollbackYesNo: immutable, one per change
Mixed instances policy: several instance types, On-Demand plus Spot in one groupYesNo
Newest instance types and features (IMDSv2 options, capacity reservations, placement groups…)YesNo
Used by EC2 Fleet, Spot Fleet and plain launches tooYesAuto Scaling only

#ASG with a load balancer

Most ASGs sit behind an Application or Network Load Balancer. You attach a target group (lessons 6.2 and 6.3) to the group, and from then on:

  1. Registration is automatic

    Every instance the group launches is registered in the target group when it enters service. You never add instances to the load balancer by hand, which matters when instances come and go every hour.

  2. Deregistration comes before termination

    When the group scales in or replaces an instance, it first deregisters it. The load balancer stops sending new requests and lets in-flight ones finish for up to the deregistration delay (connection draining, 300 seconds by default, lesson 6.4). Only then is the instance terminated. If users see errors during scale-in, increase the deregistration delay.

  3. The group can use the load balancer’s health checks

    The load balancer already probes every target (for example GET /health every 30 seconds). The ASG can be told to trust that verdict too. Whether it does is the health check type, and it is the most common misconfiguration in this whole topic.

#Health check types

All instances start as healthy. An instance becomes unhealthy when one of the enabled sources says so, and the group then terminates it and launches a replacement:

Health checkWhat it detectsOn by default?
EC2 status checks and scheduled eventsThe instance is not in the running state (stopped, terminated, shutting down), its system or instance status check is impaired, or AWS scheduled a retirement event for itYes, always, and cannot be turned off. The default type
ELBThe load balancer reports the target as unhealthy: the application does not answer its health checkNo, you must turn it on
VPC LatticeThe VPC Lattice target group reports the instance as unhealthyNo
EBSAn attached EBS volume is unreachable or failing its I/O status checksNo
CustomYour own monitoring calls set-instance-health to mark an instance unhealthyWhenever you call it

The key point: with the default EC2 type, the group only knows whether the machine is alive. A hung web server, a crashed application process or a full disk returns errors to users but leaves the instance running with passing status checks. The load balancer sees the failure and stops sending traffic, yet the ASG keeps the instance as healthy: a zombie you keep paying for. Turning on the ELB health check type adds the load balancer’s verdict (EC2 checks stay on as well), so the group terminates and replaces the instance.

A few details worth knowing:

  • An instance that leaves the running state is replaced immediately. A failing status check gets a few minutes’ patience first, in case AWS fixes the underlying problem.
  • If many instances turn unhealthy at once, the group replaces them in batches (by default up to 10% of desired capacity at a time, waiting for each batch to warm up), so a broken health check page does not wipe out the whole fleet in one go.
  • To work on an instance without the health checks killing it, put it in standby (later in this lesson), or suspend the HealthCheck and ReplaceUnhealthy processes.

#The health check grace period

A new instance needs time to boot and start its application, and during that time the load balancer’s check fails. The health check grace period is how long the group waits after an instance enters service before it acts on failed health checks.

  • Console default: 300 seconds. CLI and SDK default: 0, which turns it off. A group created with the CLI and the ELB type, whose application takes four minutes to start, will terminate every new instance before it is ready, forever.
  • Set it a little longer than the real start-up time. Too long, and a genuinely broken new instance lives on for that long.
  • It applies to newly launched instances, to instances returning from standby, and to instances you attach by hand.
  • It never protects an instance that has stopped: that is replaced at once, grace period or not.
  • If you use a launch lifecycle hook that holds the instance until it is fully ready, the grace period can be 0.

An ASG replaces unhealthy instances

Desired capacity 3, one instance per AZ, registered in the ALB’s target group. What the ASG treats as unhealthy depends on its health check type.

Scenario

The default health check type, EC2, catches an instance that is no longer running.

Regionus-east-1
web-asg
AZ a
AZ b
AZ c
ALBtarget group
Auto Scalingmin 3 · max 6
web-1
web-2
web-3
Step 1 of 5
1

Three healthy instances, one per AZ. The ALB spreads requests across them. The ASG’s job is to keep the number of healthy instances equal to the desired capacity, 3.

#When an ASG misbehaves

SymptomLikely causeFix
The load balancer shows a target as unhealthy for hours, but the ASG does nothingHealth check type is EC2 onlySet the group’s health check type to ELB
New instances are terminated within a minute or two, again and againGrace period 0 (or shorter than start-up) with the ELB type, or the health check path is wrongSet the grace period above the start-up time, use a launch hook, check the target group’s health check path and the security group
Every new instance is unhealthyThe security group does not allow the load balancer to reach the health check port, or the application fails at boot (bad user data, missing secret)Allow the load balancer’s security group on the port; read the instance’s console log and the ASG activity history
Group stuck below desired, activity shows launch failuresInsufficient capacity for that instance type in an AZ, an AMI that was deregistered, a quota limit, an invalid launch template versionAdd more instance types or AZs (mixed instances policy), fix the template, request a quota increase
Users see errors during scale-inInstances terminated with requests still in flightIncrease the deregistration delay on the target group; add a terminating lifecycle hook if the application needs a graceful shutdown
Group never scales inMin reached, scale-in disabled in the target tracking policy, all instances protected from scale-in, or instances still warming upCheck min, the policy, instance protection and warm-up

Self-checkQuestion

An Auto Scaling group uses only the default EC2 health checks. Which of these failures will it NOT detect and fix by itself?

#Scaling policies

The desired capacity changes for one of three reasons: you change it (manual scaling), a schedule changes it, or a policy changes it from metrics. The options:

TypeDriven byBest for
Target tracking (dynamic)Keep one metric near a target value, like a thermostat. AWS creates and manages the CloudWatch alarmsMost workloads. AWS’s recommendation for anything that scales in proportion to load
Step scaling (dynamic)Your CloudWatch alarm; the size of the breach picks the adjustment (“CPU 70–85%: add 1; above 85%: add 3”)When you want explicit control over how much to add or remove
Simple scaling (dynamic)Your CloudWatch alarm; one fixed adjustment, then a cooldownLegacy. AWS recommends target tracking or step scaling instead
Scheduled actionsDate and time, once or recurring (cron)Known events: business hours, a weekly sale, a batch window
Predictive scalingA machine-learning forecast built from the metric’s historyRegular daily or weekly cycles, applications that start slowly

Several policies can be active together. Each one computes the capacity it wants and the group takes the largest: it scales out if any policy says so, and scales in only if all of them agree.

#Target tracking

You pick a metric and a target value, for example average CPU at 50%. Auto Scaling then works out how many instances keep the metric at the target. If 4 instances run at 75% average CPU and the target is 50%, the same work spread at 50% needs 4 × 75 ÷ 50 = 6 instances, so desired goes to 6. If the load halves, it removes instances until the average is back near 50%.

Behind the scenes Auto Scaling creates two alarms: a scale-out alarm that reacts after the metric has been above the target for a few minutes (3 one-minute data points), and a scale-in alarm that waits much longer (15 data points). Scaling in slowly is deliberate: removing capacity too early hurts users, adding a bit too much only costs a little money. You can even disable scale-in for a policy, for example during a deployment, and let something else remove capacity.

Good metrics are ones that rise and fall in proportion to how busy each instance is:

MetricPredefined nameWhen to use it
Average CPU of the groupASGAverageCPUUtilizationCPU-bound applications: the usual default
Requests per instance from an ALBALBRequestCountPerTargetWeb apps where each instance comfortably handles a known number of requests. Target is requests per target per minute, for example 1,000 (if one instance handles about 20 RPS, 1,200 a minute is its limit, so aim lower)
Average network in / outASGAverageNetworkIn / ASGAverageNetworkOutNetwork-bound workloads (streaming, proxies, file transfer)
Your own metriccustom CloudWatch metricAnything else that is per instance: memory (published by the CloudWatch agent), active sessions per instance, queue backlog per instance

Metrics that do not work for target tracking, because they do not fall when you add instances: the load balancer’s total RequestCount, response latency, and the raw number of messages in an SQS queue. Turn totals into per-instance values (requests per target, backlog per instance) and they work.

Two practical points. EC2 publishes CPU every 5 minutes unless you turn on detailed monitoring (1 minute), and a policy can only react as fast as its data, so enable it on scaling groups. And for very spiky traffic, target tracking can also use high-resolution custom metrics published every few seconds, so it reacts in well under a minute.

Target tracking simulator

Pick a load pattern and the settings, then press play. The policy keeps average CPU near the target by changing the desired capacity, always between min and max.

Load pattern

Target average CPU

Capacity (min is 2)

Time to become ready

Default instance warm-up

Minute

0

Load (instances’ worth)

0.6

Desired

2

Serving

2

CPU the policy sees

30%

min 2: always on, cover with RI / Savings Plan048max 10target 50%%0153045607590
DesiredServingNeeded at targetCPU the policy sees

Scaling activity

Nothing yet. Press play.

0

Two instances serve a quiet site. Press play and watch the desired capacity (orange) chase the load, and the serving instances (green) follow a few minutes later.

Try these in the simulator: “Beyond max” shows what happens when the policy wants more than max allows (CPU stays above the target until the load falls); “Flash sale spike” with a 6-minute start-up shows how long a slow AMI keeps users waiting; and the same spike with warm-up set to 0 shows runaway scaling, explained below.

#Step and simple scaling with CloudWatch alarms

Here you create the CloudWatch alarm, for example “average CPU of the group above 70% for 2 consecutive minutes”, and attach a policy to it. Usually you create a pair: a scale-out policy on a high alarm and a scale-in policy on a low alarm (“below 30%, remove 1”).

  • Step scaling looks at how far the metric is past the threshold and picks a step: 70–85% → add 1, 85% and above → add 3. Adjustments can be a number of instances, an exact capacity, or a percentage of the group. It reacts to every new breach immediately, and instances that are still warming up count as capacity already added, so it does not overshoot.
  • Simple scaling has one adjustment (“add 2”) and then waits for the cooldown to finish before it will act again. That makes it slow when the load keeps rising, which is why AWS recommends against it.

#Scheduled actions

A scheduled action sets min, max and/or desired at a given time, once (“on 11 November at 08:00, min 20”) or recurring with a cron expression and a time zone (“every Friday at 16:45, min 10”). Use it when you know the load is coming: the action raises capacity before the rush, and a second action lowers min again afterwards. Scheduled actions start immediately without waiting for any cooldown, and dynamic policies keep working on top of them.

#Predictive scaling

Predictive scaling learns the pattern from history and scales ahead of the load:

  • It needs at least 24 hours of the metric before it can forecast, and looks at up to 14 days (two full weeks gives the best forecasts).
  • It produces an hourly forecast for the next 48 hours, refreshed every 6 hours.
  • It starts in forecast only mode, so you can compare the forecast with reality; you then switch to forecast and scale.
  • It launches at the start of each forecast hour, or earlier with a pre-launch buffer (SchedulingBufferTime) so instances are ready when the hour begins.
  • It only scales out. It never removes capacity, so pair it with a target tracking policy that scales in (and covers surprises the forecast did not see).
  • By default it cannot exceed max; it can be allowed to raise max automatically (MaxCapacityBreachBehavior), but that raised max stays until you lower it.

Good fits: office-hours applications, daily batch windows, and applications that take many minutes to initialise. Poor fits: random, irregular traffic, and mixed groups whose instance types differ a lot in size.

#Scaling on a queue: backlog per instance

Worker fleets that read from an SQS queue should scale on the backlog per instance, not on the queue length. The raw number of visible messages does not change in proportion to the number of workers, so target tracking cannot use it. Backlog per instance can:

  1. Decide the acceptable latency: a message may wait at most, say, 20 seconds.
  2. Measure the processing time: one worker handles a message in 0.2 seconds, so 5 messages per second.
  3. Target backlog per instance = acceptable latency ÷ processing time = 20 ÷ 0.2 = 100 messages.
  4. With 1,800 messages in the queue, the policy wants 1,800 ÷ 100 = 18 instances.

You can publish this as a custom metric, or let target tracking compute it with metric math: the queue’s visible message count divided by the group’s in-service instance count. If the processing time changes (bigger images, a slower dependency), revisit the target.

Scaling with CloudWatch alarms

Every dynamic policy is driven by CloudWatch. Step and simple policies use alarms you create; target tracking creates and manages its own.

Scenario

Alarm: average CPU above 70%. Steps: 70–85% add 1, 85% and above add 3.

CloudWatchmetric
Alarm
Auto Scalingdesired 4
Instances
avg CPU 74%
Step 1 of 6
1

Four instances report CPU. CloudWatch averages it across the whole group: 74%.

#Cooldowns and warm-up

After the group adds instances, it takes a while before they help: they launch, boot, start the application, register with the load balancer and only then take traffic. If the policy keeps looking at the metric during that time, it sees the old, high value and adds even more instances. Two settings deal with this, and the exam likes to mix them up.

Default cooldownDefault instance warm-up
Applies toSimple scaling policies (and manual scaling only if you ask)Target tracking and step scaling policies, instance refresh, instance replacement
What it doesAfter a scaling activity, simple policies may not launch or terminate anything until the cooldown endsA new instance is left out of the group’s aggregated metrics until its warm-up ends, and counts as capacity already on its way
Default value300 secondsNot set by default. When unset, target tracking and step scaling fall back to the cooldown value, and instance refresh to the grace period
AWS guidanceAvoid simple scaling and cooldowns where you canStrongly recommended: set it to the time an instance needs to be ready (start with 300 seconds and tune)

What the warm-up changes, exactly:

  • While instances are warming up, the group scales out again only if the metric from the other instances is still above the target, and it counts the warming ones as part of capacity, so several alarms in a row produce one sensible scale-out instead of a pile-up.
  • Scale-in is blocked until the new instances finish warming up.
  • Scheduled actions, health replacements and manual changes never wait for either setting.

#Runaway scaling

Runaway scaling is the group launching instance after instance before the earlier ones are working. The classic cause: new instances run a long user data script (install packages, download assets, compile) at high CPU. With no warm-up, or a warm-up much shorter than that script, their busy CPU joins the group’s average, the average gets worse right after a scale-out, and the policy adds more. The group races to max, you pay for the overshoot, and scale-in later takes 15 minutes or more to undo it.

The fixes, best first:

  1. Make instances ready faster. Use a ready-to-serve (golden) AMI with the software already installed (lesson 4.3), and keep user data small. This shortens everything: warm-up, grace period and time to help users.
  2. Set the default instance warm-up to the real time from in service to serving traffic.
  3. For simple scaling, increase the cooldown.
  4. Use a launch lifecycle hook so instances enter service only when ready.

Self-checkQuestion

Which scaling policy type waits for the default cooldown of 300 seconds before it can scale again?

#The instance lifecycle

An instance in an ASG moves through states of its own, on top of the normal EC2 states:

StateWhat is happening
PendingLaunching and booting. With a launch hook: Pending:Wait, then Pending:Proceed
InServiceRunning, registered with the load balancer, counted in capacity, health-checked
StandbyTemporarily out of service: deregistered from the load balancer and not health-checked, but still part of the group
TerminatingChosen for removal: deregistered (draining), then terminated. With a terminating hook: Terminating:Wait, then Terminating:Proceed
DetachingLeaving the group for good; afterwards it is an ordinary instance that keeps running
Warmed:*In a warm pool (below), stopped, hibernated or running, waiting to be needed

#Lifecycle hooks

A lifecycle hook pauses an instance at launch or at termination so you can run your own action:

  • Launching hook (autoscaling:EC2_INSTANCE_LAUNCHING): the instance waits in Pending:Wait before it is registered with the load balancer. A script installs software, fills a cache, runs a self-test, then calls complete-lifecycle-action with CONTINUE. ABANDON tells the group to terminate it and try again.
  • Terminating hook (autoscaling:EC2_INSTANCE_TERMINATING): the instance waits in Terminating:Wait after deregistration. Typical actions: copy logs to S3, finish or hand back the current job, deregister from a service registry or DNS, take a final backup. For a terminating hook both results end in termination; the hook is best effort.
  • The hook sends an event to EventBridge (or a notification to SNS or SQS), which can start a Lambda function, an SSM Run Command or a script. The instance can also read its own target lifecycle state from instance metadata.
  • Heartbeat timeout: 1 hour by default. If nobody completes the action, the default result (ABANDON or CONTINUE) applies when it expires. A script can extend the wait with record-lifecycle-action-heartbeat, up to a global limit of 48 hours or 100 heartbeats, whichever is shorter.

#Standby, detach and scale-in protection

  • Standby (enter-standby): take an instance out of service to patch or troubleshoot it. It leaves the load balancer and is not health-checked. You choose whether desired drops by one or a replacement launches. exit-standby brings it back (the grace period applies again).
  • Detach (detach-instances): remove an instance from the group permanently. It keeps running as a normal instance; you can let the group launch a replacement or lower desired. Attach does the opposite.
  • Scale-in protection (set-instance-protection, or for all new instances on the group): scale-in will not choose this instance, for example while it runs a long job. It does not protect against health replacement, Spot interruption or manual termination.

#Which instance is terminated on scale-in

The termination policy decides which instance goes when the group scales in. The default policy works in this order:

  1. Availability Zone balance first. Pick the AZ with the most instances (that has at least one unprotected instance). This rule always comes first, whatever termination policy you choose.
  2. Outdated configuration. Within that AZ, prefer an instance launched from a launch configuration, then from a different launch template than the current one, then from the oldest version of the current template.
  3. Closest to the next billing hour, and if still tied, random. (Most EC2 is billed per second now, so this rule matters less than it used to.)

For mixed instances groups, the group first decides whether to remove an On-Demand or a Spot instance (to keep the configured ratio) and which instance best fits the allocation strategy.

Other predefined policies: OldestInstance, NewestInstance, OldestLaunchTemplate, OldestLaunchConfiguration, ClosestToNextInstanceHour, AllocationStrategy, or a custom termination policy in a Lambda function. Unhealthy instances skip the policy altogether: they are simply replaced.

#Keeping the fleet fresh and fast

  • Instance refresh replaces instances in rolling batches, for example after you promote a new launch template version with a patched AMI. You set a minimum healthy percentage (90% by default) and optionally a maximum above 100% so it launches before terminating; it can skip instances that already match, pause at checkpoints, and roll back automatically if health checks fail.
  • Instance maintenance policy: the same min/max healthy percentages for every replacement the group does (health replacements, rebalancing), for example “launch before terminate, never below 100%”.
  • Maximum instance lifetime: replace every instance after, say, 7 days (at least 1 day), so nothing runs unpatched for months.
  • Warm pools: a set of pre-initialised instances kept stopped (you pay only for their EBS volumes), hibernated or running beside the group. A scale-out takes one from the pool instead of booting from scratch, which cuts minutes off applications with long start-ups.
  • Suspending processes: you can pause individual processes (Launch, Terminate, HealthCheck, ReplaceUnhealthy, AZRebalance, AlarmNotification, ScheduledActions, AddToLoadBalancer, InstanceRefresh) while investigating a problem.

Instance lifecycle, hooks and termination

Five instances in two AZs. Sub-labels show the launch template version each one was launched from.

Scenario

Region
AZ a
AZ b
Auto Scalingdesired 5desired 5 → 4
log bucket
i-a1v1
i-a2v2
i-a3v2
i-b1v1
i-b2v2
Step 1 of 5
1

Load drops and a policy lowers desired from 5 to 4. Which instance is terminated? The default termination policy decides, in a fixed order.

#Mixed instances: On-Demand and Spot together

A mixed instances policy (launch template only) lets one group use several instance types and both purchase options. Lesson 4.4 covered Spot itself: up to about 90% off, with a two-minute interruption notice. In an ASG you control the mix with two numbers:

SettingMeaningAPI default
On-Demand base capacity (OnDemandBaseCapacity)The first N units of capacity are always On-Demand0
On-Demand percentage above base (OnDemandPercentageAboveBaseCapacity)Of everything above the base, this percentage is On-Demand; the rest is Spot100 (all On-Demand)

Worked example. Base 2, 25% On-Demand above base, desired 10. The first 2 are On-Demand. The other 8 are split 25% On-Demand (2) and 75% Spot (6). Total: 4 On-Demand and 6 Spot. At desired 4 it would be 2 + (2 × 25% → about 1 On-Demand, the rest Spot).

How the group chooses instance types and pools (a pool is one instance type in one AZ):

  • Spot allocation strategy: price-capacity-optimized is recommended: it picks pools that are both cheap and least likely to be interrupted. capacity-optimized (and -prioritized) look at capacity only. lowest-price is what the API uses if you specify nothing, but AWS does not recommend it: it has the highest interruption risk.
  • On-Demand allocation strategy: prioritized (your order of types, useful to land on types your Reserved Instances cover) or lowest-price.
  • Offer many instance types (or attribute-based selection by vCPU and memory) across several AZs. The more pools, the less one shortage hurts.
  • Do not set a Spot maximum price: it only increases interruptions. You always pay the current Spot price.

Capacity rebalancing. Before interrupting a Spot instance, EC2 sends a rebalance recommendation (an elevated-risk signal, usually earlier than the two-minute notice). With capacity rebalancing turned on, the group launches a replacement in a healthier pool first, waits for it to pass its health check, and then terminates the at-risk instance, through your terminating lifecycle hook if you have one (keep it under two minutes). It may briefly exceed max by up to 10% of desired. Without it, the group only replaces a Spot instance after it has been interrupted, so capacity dips for a few minutes.

On-Demand base plus Spot in one group

Mixed instances policy: On-Demand base capacity 2, above the base 0% On-Demand (all Spot), allocation strategy price-capacity-optimized, several instance types. Desired 5.

Scenario

Region
AZ a
AZ b
AZ c
Auto Scalingdesired 5
RI / Savings Plan
On-Demandm7i.large
On-Demandm7i.large
On-DemandOn-Demand
Step 1 of 4
1

The base comes first: the first 2 units of capacity are always On-Demand, spread over AZs. This is the floor that must never disappear.

#Paying for the floor: Reserved Instances and Savings Plans

Auto Scaling saves money by removing instances you do not need. But the minimum capacity never goes away: those instances run 24 hours a day, 365 days a year. Paying On-Demand rates for something that always runs is the expensive way. Cover the floor with a commitment (lesson 4.4), and let the variable part above it run On-Demand or Spot.

  1. The question

    The Triangle Sunglasses website runs on EC2 instances in an Auto Scaling group behind an Application Load Balancer. To save money, the group already scales on the traffic through the load balancer. For high availability, its minimum capacity was set to 2. How can costs be reduced further while respecting the requirements? Options: remove the ALB and use an Elastic IP; reserve two EC2 instances; reduce minimum capacity to 1; reduce minimum capacity to 0.

  2. Read the requirement hidden in the question

    “Minimum capacity 2 for high availability” means at least two instances run all the time, traffic or no traffic. That is a constant baseline, the classic case for a long-term commitment.

  3. The answer: reserve two instances

    Buy two Reserved Instances (1 or 3 years, up to about 72% off) matching the group’s instance type and Region, or a Savings Plan for the same spend (Compute Savings Plans up to about 66%, EC2 Instance Savings Plans up to about 72%, and flexible if you later change instance types). Both requirements hold: min 2 keeps high availability, and the always-on part costs much less. Instances above the minimum stay On-Demand (or Spot).

  4. Why the other options fail

    • Remove the ALB, use an Elastic IP: an Elastic IP points at one instance at a time. There is no load balancing and no automatic failover, so availability is lost, and the group can no longer scale on ALB traffic.
    • Minimum 1: cheaper, but one instance is a single point of failure. If it crashes, the site is down until a replacement boots.
    • Minimum 0: with no traffic there are no instances at all, and the first visitors get errors. Not highly available in any sense.

One more thing the question takes for granted: two instances are only highly available if they are in two different AZs. Give the group subnets in at least two AZs, and the ASG will place one in each.

#Exam patterns at a glance

The question says…Answer
Automatically replace an impaired / failed instance, no humanAuto Scaling group (even min = max = 1)
Load balancer says unhealthy, ASG does not replaceEnable the ELB health check type on the ASG
New instances terminated before they finish startingIncrease the health check grace period (or use a launch lifecycle hook)
Mix of On-Demand and Spot, several instance types, or version control of launch settingsLaunch template (mixed instances policy)
Keep average CPU around 50%Target tracking on average CPU
Keep the number of requests per instance stableTarget tracking on ALBRequestCountPerTarget
Scale workers on an SQS queueTarget tracking on backlog per instance
Add 2 when CPU > 70%, remove 1 when CPU < 30%Step (or simple) scaling with CloudWatch alarms
Known event at a known timeScheduled action
Recurring daily/weekly pattern, capacity ready before the loadPredictive scaling
Launching too many instances before the earlier ones work (runaway)Cooldown (simple scaling) / instance warm-up (target tracking, step); ready-to-serve AMI
Instances should be ready faster after a scale-outGolden AMI, smaller user data, warm pool
Run a script / save logs before an instance terminatesTerminating lifecycle hook
Patch or troubleshoot one instance without it being replacedStandby
A long job must not be killed by scale-inScale-in protection
In-flight requests dropped during scale-inIncrease the deregistration delay
Which instance goes first on scale-inDefault termination policy: AZ balance, then oldest configuration, then billing hour
Spot instances keep getting interruptedprice-capacity-optimized, more instance types and AZs, capacity rebalancing
Always-on minimum capacity, cut costReserved Instances / Savings Plans

#How ASGs are used in system design

  • Web and API tiers: an ASG of stateless instances in private subnets across three AZs, behind an ALB in public subnets, scaling on CPU or requests per target. Sessions live in ElastiCache or DynamoDB (Chapter 7), never on the instance, so any instance can be terminated at any time.
  • Worker fleets: instances read jobs from SQS and scale on backlog per instance, often mostly on Spot with capacity rebalancing.
  • Self-healing single servers: a legacy application that cannot be clustered still gets automatic replacement from an ASG of 1, combined with an Elastic IP or DNS update in a launch lifecycle hook.
  • Immutable deployments: build a new AMI, create a launch template version, run an instance refresh (or create a new ASG and shift traffic). Instances are never patched in place.
  • Cost layering: Reserved Instances or Savings Plans cover the minimum, On-Demand covers the base above it, Spot covers the peaks, scheduled and predictive scaling prepare for known peaks.
  • Beyond EC2, Application Auto Scaling gives the same target tracking, step and scheduled ideas to ECS services, DynamoDB tables, Aurora replicas, Lambda provisioned concurrency and more.

#Try it yourself

# 1. A group of 2 to 6 instances across three private subnets, behind an existing target group,
#    using launch template web-lt (lesson 4.3) at its default version
aws autoscaling create-auto-scaling-group --auto-scaling-group-name web-asg \
  --launch-template 'LaunchTemplateName=web-lt,Version=$Default' \
  --min-size 2 --max-size 6 --desired-capacity 2 \
  --vpc-zone-identifier "subnet-0aaa1111,subnet-0bbb2222,subnet-0ccc3333" \
  --target-group-arns "$TG_ARN" \
  --health-check-type ELB --health-check-grace-period 300 \
  --default-instance-warmup 180

# 2. Target tracking: keep average CPU at 50%
aws autoscaling put-scaling-policy --auto-scaling-group-name web-asg \
  --policy-name cpu-50 --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {"PredefinedMetricType": "ASGAverageCPUUtilization"},
    "TargetValue": 50.0 }'

#    ...or keep about 1,000 requests per instance per minute
aws autoscaling put-scaling-policy --auto-scaling-group-name web-asg \
  --policy-name req-1000 --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {"PredefinedMetricType": "ALBRequestCountPerTarget",
      "ResourceLabel": "app/web-alb/50dc6c495c0c9188/targetgroup/web-tg/943f017f100becff"},
    "TargetValue": 1000.0 }'

# 3. Scheduled actions: more capacity for the Friday sale, back to normal at night
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name web-asg \
  --scheduled-action-name friday-sale --recurrence "45 16 * * 5" --time-zone "Asia/Dhaka" \
  --min-size 4 --desired-capacity 4
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name web-asg \
  --scheduled-action-name friday-night --recurrence "0 2 * * 6" --time-zone "Asia/Dhaka" \
  --min-size 2

# 4. Predictive scaling, forecast only to start with
aws autoscaling put-scaling-policy --auto-scaling-group-name web-asg \
  --policy-name predict-cpu --policy-type PredictiveScaling \
  --predictive-scaling-configuration '{
    "MetricSpecifications": [{"TargetValue": 50,
      "PredefinedMetricPairSpecification": {"PredefinedMetricType": "ASGCPUUtilization"}}],
    "Mode": "ForecastOnly", "SchedulingBufferTime": 600 }'

# 5. A terminating lifecycle hook (5-minute timeout), and how a script completes it
aws autoscaling put-lifecycle-hook --auto-scaling-group-name web-asg \
  --lifecycle-hook-name save-logs \
  --lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING \
  --heartbeat-timeout 300 --default-result CONTINUE
aws autoscaling complete-lifecycle-action --auto-scaling-group-name web-asg \
  --lifecycle-hook-name save-logs --instance-id i-0abc1234def567890 \
  --lifecycle-action-result CONTINUE

# 6. Watch what the group is doing, and why
aws autoscaling describe-scaling-activities --auto-scaling-group-name web-asg --max-items 5 \
  --query "Activities[].[StartTime,StatusCode,Description,Cause]"
aws autoscaling describe-auto-scaling-instances \
  --query "AutoScalingInstances[].[InstanceId,AvailabilityZone,LifecycleState,HealthStatus]"

# 7. Protect, park and refresh
aws autoscaling set-instance-protection --auto-scaling-group-name web-asg \
  --instance-ids i-0abc1234def567890 --protected-from-scale-in
aws autoscaling enter-standby --auto-scaling-group-name web-asg \
  --instance-ids i-0abc1234def567890 --should-decrement-desired-capacity
aws autoscaling exit-standby --auto-scaling-group-name web-asg --instance-ids i-0abc1234def567890
aws autoscaling start-instance-refresh --auto-scaling-group-name web-asg \
  --preferences '{"MinHealthyPercentage": 90, "InstanceWarmup": 180}'

# 8. Mixed instances: base 2 On-Demand, the rest 75% Spot, several types, capacity rebalancing
aws autoscaling update-auto-scaling-group --auto-scaling-group-name web-asg --capacity-rebalance \
  --mixed-instances-policy '{
    "LaunchTemplate": {"LaunchTemplateSpecification": {"LaunchTemplateName": "web-lt", "Version": "$Default"},
      "Overrides": [{"InstanceType": "m7i.large"}, {"InstanceType": "m6i.large"}, {"InstanceType": "c7i.large"}]},
    "InstancesDistribution": {"OnDemandBaseCapacity": 2, "OnDemandPercentageAboveBaseCapacity": 25,
      "SpotAllocationStrategy": "price-capacity-optimized"} }'

#Summary

Key takeaways

  • An Auto Scaling group keeps the number of healthy instances at desired, always between min and max, replaces failed instances, spreads them across AZs in one Region and rebalances. The group is free; you pay for the instances.
  • It launches from a launch template. Launch configurations are legacy: no new instance types since 2023 and impossible to create in accounts opened from October 2024. Mixed On-Demand/Spot and versioning need a launch template.
  • With a load balancer, instances are registered and deregistered automatically. The default health check type is EC2 (machine alive); turn on ELB so instances whose application fails are replaced. The grace period (console 300 s, CLI 0) gives new instances time to start.
  • Target tracking (recommended) keeps a per-instance metric near a target: CPU, ALBRequestCountPerTarget, network, or a custom metric such as SQS backlog per instance. It scales out fast and in slowly. Totals, latency and raw queue length are poor metrics.
  • Step and simple scaling act on your CloudWatch alarms; scheduled actions handle known times; predictive scaling forecasts 48 hours from up to 14 days of history, starts in forecast only mode and never scales in.
  • The cooldown (300 s) applies to simple scaling. Target tracking and step scaling use the default instance warm-up, which AWS strongly recommends setting. Runaway scaling is fixed with the right warm-up (or cooldown for simple scaling) and a ready-to-serve AMI.
  • Lifecycle hooks pause instances at launch (Pending:Wait) or termination (Terminating:Wait, default timeout 1 hour). Standby parks an instance, detach removes it, scale-in protection shields it from scale-in.
  • The default termination policy: AZ balance first, then outdated configuration (oldest template version), then closest to the billing hour. Instance refresh, maximum instance lifetime and warm pools keep the fleet fresh and fast.
  • A mixed instances policy sets an On-Demand base plus a percentage above it; use price-capacity-optimized and capacity rebalancing for Spot.
  • The minimum capacity runs all the time: cover it with Reserved Instances or Savings Plans (Triangle Sunglasses: reserve two instances), and spread it over at least two AZs.