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:
| Setting | Meaning | Example |
|---|---|---|
| Minimum capacity | Never fewer than this many instances, whatever the load | 2 |
| Desired capacity | How many it should run right now. Scaling policies, schedules and you change this number; the group then launches or terminates to match it | 4 |
| Maximum capacity | Never more than this, whatever the policies ask for: a safety limit for your bill and your quotas | 10 |
| Launch template | The recipe for every new instance: AMI, instance type, security groups, IAM role, user data… | web-lt, version $Default |
| Subnets | Where instances may go; choose subnets in two or more AZs | one 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:
| Date | What changed for launch configurations |
|---|---|
| 31 December 2022 | No support for any EC2 instance type released after this date |
| 1 June 2023 | Accounts created from this date cannot create them in the console |
| 1 October 2024 | Accounts 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:
| Need | Launch template | Launch configuration |
|---|---|---|
| Versions, a default version, rollback | Yes | No: immutable, one per change |
| Mixed instances policy: several instance types, On-Demand plus Spot in one group | Yes | No |
| Newest instance types and features (IMDSv2 options, capacity reservations, placement groups…) | Yes | No |
| Used by EC2 Fleet, Spot Fleet and plain launches too | Yes | Auto 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:
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.
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.
The group can use the load balancer’s health checks
The load balancer already probes every target (for example
GET /healthevery 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 check | What it detects | On by default? |
|---|---|---|
| EC2 status checks and scheduled events | The 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 it | Yes, always, and cannot be turned off. The default type |
| ELB | The load balancer reports the target as unhealthy: the application does not answer its health check | No, you must turn it on |
| VPC Lattice | The VPC Lattice target group reports the instance as unhealthy | No |
| EBS | An attached EBS volume is unreachable or failing its I/O status checks | No |
| Custom | Your own monitoring calls set-instance-health to mark an instance unhealthy | Whenever 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
runningstate 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
HealthCheckandReplaceUnhealthyprocesses.
#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.
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
| Symptom | Likely cause | Fix |
|---|---|---|
| The load balancer shows a target as unhealthy for hours, but the ASG does nothing | Health check type is EC2 only | Set the group’s health check type to ELB |
| New instances are terminated within a minute or two, again and again | Grace period 0 (or shorter than start-up) with the ELB type, or the health check path is wrong | Set 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 unhealthy | The 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 failures | Insufficient capacity for that instance type in an AZ, an AMI that was deregistered, a quota limit, an invalid launch template version | Add more instance types or AZs (mixed instances policy), fix the template, request a quota increase |
| Users see errors during scale-in | Instances terminated with requests still in flight | Increase the deregistration delay on the target group; add a terminating lifecycle hook if the application needs a graceful shutdown |
| Group never scales in | Min reached, scale-in disabled in the target tracking policy, all instances protected from scale-in, or instances still warming up | Check 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:
| Type | Driven by | Best for |
|---|---|---|
| Target tracking (dynamic) | Keep one metric near a target value, like a thermostat. AWS creates and manages the CloudWatch alarms | Most 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 cooldown | Legacy. AWS recommends target tracking or step scaling instead |
| Scheduled actions | Date and time, once or recurring (cron) | Known events: business hours, a weekly sale, a batch window |
| Predictive scaling | A machine-learning forecast built from the metric’s history | Regular 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:
| Metric | Predefined name | When to use it |
|---|---|---|
| Average CPU of the group | ASGAverageCPUUtilization | CPU-bound applications: the usual default |
| Requests per instance from an ALB | ALBRequestCountPerTarget | Web 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 / out | ASGAverageNetworkIn / ASGAverageNetworkOut | Network-bound workloads (streaming, proxies, file transfer) |
| Your own metric | custom CloudWatch metric | Anything 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%
Scaling activity
Nothing yet. Press play.
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:
- Decide the acceptable latency: a message may wait at most, say, 20 seconds.
- Measure the processing time: one worker handles a message in 0.2 seconds, so 5 messages per second.
- Target backlog per instance = acceptable latency ÷ processing time = 20 ÷ 0.2 = 100 messages.
- 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.
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 cooldown | Default instance warm-up | |
|---|---|---|
| Applies to | Simple scaling policies (and manual scaling only if you ask) | Target tracking and step scaling policies, instance refresh, instance replacement |
| What it does | After a scaling activity, simple policies may not launch or terminate anything until the cooldown ends | A 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 value | 300 seconds | Not set by default. When unset, target tracking and step scaling fall back to the cooldown value, and instance refresh to the grace period |
| AWS guidance | Avoid simple scaling and cooldowns where you can | Strongly 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:
- 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.
- Set the default instance warm-up to the real time from in service to serving traffic.
- For simple scaling, increase the cooldown.
- 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:
| State | What is happening |
|---|---|
Pending | Launching and booting. With a launch hook: Pending:Wait, then Pending:Proceed |
InService | Running, registered with the load balancer, counted in capacity, health-checked |
Standby | Temporarily out of service: deregistered from the load balancer and not health-checked, but still part of the group |
Terminating | Chosen for removal: deregistered (draining), then terminated. With a terminating hook: Terminating:Wait, then Terminating:Proceed |
Detaching | Leaving 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 inPending:Waitbefore it is registered with the load balancer. A script installs software, fills a cache, runs a self-test, then callscomplete-lifecycle-actionwithCONTINUE.ABANDONtells the group to terminate it and try again. - Terminating hook (
autoscaling:EC2_INSTANCE_TERMINATING): the instance waits inTerminating:Waitafter 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 (
ABANDONorCONTINUE) applies when it expires. A script can extend the wait withrecord-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-standbybrings 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:
- 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.
- 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.
- 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
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:
| Setting | Meaning | API default |
|---|---|---|
On-Demand base capacity (OnDemandBaseCapacity) | The first N units of capacity are always On-Demand | 0 |
On-Demand percentage above base (OnDemandPercentageAboveBaseCapacity) | Of everything above the base, this percentage is On-Demand; the rest is Spot | 100 (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-optimizedis recommended: it picks pools that are both cheap and least likely to be interrupted.capacity-optimized(and-prioritized) look at capacity only.lowest-priceis 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) orlowest-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
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.
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.
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.
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).
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 human | Auto Scaling group (even min = max = 1) |
| Load balancer says unhealthy, ASG does not replace | Enable the ELB health check type on the ASG |
| New instances terminated before they finish starting | Increase 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 settings | Launch template (mixed instances policy) |
| Keep average CPU around 50% | Target tracking on average CPU |
| Keep the number of requests per instance stable | Target tracking on ALBRequestCountPerTarget |
| Scale workers on an SQS queue | Target 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 time | Scheduled action |
| Recurring daily/weekly pattern, capacity ready before the load | Predictive 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-out | Golden AMI, smaller user data, warm pool |
| Run a script / save logs before an instance terminates | Terminating lifecycle hook |
| Patch or troubleshoot one instance without it being replaced | Standby |
| A long job must not be killed by scale-in | Scale-in protection |
| In-flight requests dropped during scale-in | Increase the deregistration delay |
| Which instance goes first on scale-in | Default termination policy: AZ balance, then oldest configuration, then billing hour |
| Spot instances keep getting interrupted | price-capacity-optimized, more instance types and AZs, capacity rebalancing |
| Always-on minimum capacity, cut cost | Reserved 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.
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
What do you pay for when you use an EC2 Auto Scaling group?
Self-checkQuestion
A group has min 2, desired 4 and max 8. During a traffic spike its target tracking policy calculates that 11 instances are needed. What happens?
Self-checkQuestion
Which health check does an Auto Scaling group use by default?
Self-checkQuestion
A team in an AWS account opened in 2025 tries to create a launch configuration with the AWS CLI for a new Auto Scaling group. What happens, and what should they use?
Self-checkQuestion
Which two requirements can be met only with a launch template, not a launch configuration?
Choose 2
Self-checkQuestion
A legacy application must run on exactly one EC2 instance. If the instance becomes impaired, it must be replaced automatically without human intervention. What is the simplest solution?
Self-checkQuestion
Users report errors. The ALB console shows one target as unhealthy for the last two hours, but the Auto Scaling group still lists the instance as healthy and has not replaced it. What should a solutions architect change?
Self-checkQuestion
An Auto Scaling group created with the AWS CLI uses ELB health checks. Each new instance is terminated about a minute after launch and replaced, over and over. The application needs about 4 minutes to start. What is the most likely cause?
Self-checkQuestion
Which two metrics are predefined for EC2 Auto Scaling target tracking policies?
Choose 2
Self-checkQuestion
Which of these metrics should NOT be used directly as the metric of a target tracking policy?
Self-checkQuestion
A target tracking policy keeps average CPU at 50%. The group runs 4 instances at an average of 75% CPU. Roughly how many instances will the policy ask for?
Self-checkQuestion
A step scaling policy is attached to an alarm on average CPU above 70%, with steps: 70–85% add 1 instance, 85% and above add 3. The group has 4 instances, none warming up, and CPU reaches 88%. What is the new desired capacity?
Self-checkQuestion
An image-processing fleet reads jobs from an SQS queue. Each instance processes one message in 0.2 seconds, and a message should wait no more than 20 seconds. The queue holds 1,800 messages. Using backlog per instance, how many instances should target tracking aim for?
Self-checkQuestion
A simple scaling policy adds 2 instances when CPU exceeds 70%. The group uses the default cooldown. Two minutes after the scale-out, the alarm is still in the alarm state. What does the group do?
Self-checkQuestion
A group uses a target tracking policy on CPU. Each new instance runs a 6-minute user data script at high CPU before it serves traffic. After every traffic increase the group races to its maximum, and then slowly scales in. What should a solutions architect do?
Self-checkQuestion
Study notes say: ‘If an ASG launches too many instances before the earlier ones are working (runaway scaling), increase the cooldown period.’ What is the most accurate statement?
Self-checkQuestion
Which two statements about predictive scaling are true?
Choose 2
Self-checkQuestion
An online shop runs a sale every Friday at 5 PM. Traffic triples within minutes, and new instances take 5 minutes to become ready. Which solution prepares the capacity in time with the least effort?
Self-checkQuestion
A business application is busy every weekday from 9 AM to 6 PM and almost idle at night, and its instances take 10 minutes to initialise. The team wants capacity in place before the morning load without maintaining schedules by hand. What should they use?
Self-checkQuestion
The Triangle Sunglasses website runs on EC2 instances in an Auto Scaling group behind an ALB. The group scales on the traffic through the ALB, and its minimum capacity was set to 2 for high availability. How can costs be reduced further while respecting the requirements?
Self-checkQuestion
A group uses the default termination policy and launch template web-lt. AZ a has three instances (one from version 1, two from version 2) and AZ b has two (one from version 1, one from version 2). Desired drops from 5 to 4. Which instance is terminated?
Self-checkQuestion
Before an Auto Scaling instance is terminated during scale-in, its application logs must be copied to Amazon S3. What is the best solution?
Self-checkQuestion
An engineer must patch one instance in an Auto Scaling group and reboot it several times. It must stop receiving traffic, must not be replaced by health checks, and must return to the group afterwards. What should the engineer do?
Self-checkQuestion
One instance in a group runs a two-hour reporting job that must not be interrupted when the group scales in. Other instances may be removed as usual. What should be used?
Self-checkQuestion
A mixed instances policy sets On-Demand base capacity 2 and On-Demand percentage above base capacity 25. Desired capacity is 10. How many On-Demand and Spot instances run?
Self-checkQuestion
Which two statements about Spot instances in an Auto Scaling group are true?
Choose 2
Self-checkQuestion
Which two statements about the health check grace period are true?
Choose 2
Self-checkQuestion
A group spans three AZs and runs at its maximum of 6 instances. One AZ fails and the group launches replacements in the other two. When the AZ recovers, what does the group do?
Self-checkQuestion
A web tier must keep running if an Availability Zone fails and must cost as little as possible. Which configuration fits best?
Self-checkQuestion
Which statement about EC2 Auto Scaling groups is NOT true?