Scale EC2 with Auto Scaling Groups
Learn how to scale EC2 with Auto Scaling groups in this hands-on AWS tutorial. Understand core concepts, step-by-step setup, troubleshooting, and what to study next.
Focus: scale ec2 with auto scaling groups
You've deployed an EC2 instance, configured a security group, and maybe even SSH'd in to run your Python app. But what happens when traffic spikes at 2 PM on a Tuesday and your single t2.micro starts choking? If you're manually launching instances or resizing them by hand, you're one busy afternoon away from downtime. This lesson ends that pain: you'll learn how to scale EC2 with Auto Scaling groups — AWS's managed way to keep the right number of instances running, automatically, based on demand (or schedule). By the end, you'll not only understand the core concepts but also launch a working Auto Scaling group from the AWS CLI, verify it, and know exactly what to do when things misbehave.
Core Concept / Mental Model
Think of an Auto Scaling group (ASG) as a self-adjusting fleet manager for your EC2 instances. At its heart, an ASG is a collection of EC2 instances that AWS starts and stops based on rules you define. It's not a new type of instance — it's a layer of orchestration that sits on top of your instances, ensuring your desired capacity is always met.
The Three Decisions an ASG Makes
- How many instances? — The
DesiredCapacityis the target number you want running at any given time. The ASG constantly tries to match this number. - When to change that number? — You set scaling policies that adjust the desired capacity based on CloudWatch metrics (like CPU utilization) or schedules (like "increase at 9 AM on weekdays").
- Where to run them? — The ASG uses your launch template or launch configuration to decide instance type, AMI, security group, and subnet placement. It also distributes instances across Availability Zones (AZs) for high availability.
A Quick Analogy
Imagine you're managing a fleet of delivery vans. You have a baseline fleet (desired capacity) for normal days. When orders spike (CPU > 70%), you automatically dispatch more vans (scale out). When orders drop, you retire some vans (scale in). You never want to be understaffed (downtime) or have idle vans (wasted money). The ASG is your dispatcher, making these calls 24/7.
Definitions You'll See in the Console
- Launch Template: The blueprint for new instances — AMI, type, key pair, security groups, user data.
- Minimum / Maximum: The lower and upper bounds for instance count. The ASG will never go below or above these.
- Scaling Policy: A rule (e.g., target tracking, step scaling, simple scaling) that triggers scale-out or scale-in.
- Health Check: The ASG checks instance health (via EC2 status checks or your own ELB health check) and replaces unhealthy ones.
How It Works Step by Step
Scaling EC2 with Auto Scaling groups happens in a clear sequence. Let's trace through the lifecycle:
- You create a launch template that specifies exactly what each new instance looks like.
- You create an Auto Scaling group linked to that template, setting the AZs, desired/min/max, and health check type.
- The ASG spins up your desired capacity — instantly, using the template. You now have a fleet.
- A CloudWatch alarm watches a metric (say, average CPU utilization across the group). When it crosses your threshold (e.g., > 70% for 5 minutes), it triggers the scaling policy.
- The scaling policy increases DesiredCapacity (e.g., +1 instance). The ASG launches a new instance using the template.
- When the alarm drops below threshold, the policy decreases DesiredCapacity, and the ASG terminates instances — ideally the oldest first.
- If an instance fails health checks, the ASG replaces it automatically, maintaining your desired count.
Pro tip: The ASG always works to align the current instance count with DesiredCapacity. If you manually terminate an instance, the ASG will launch a new one to replace it within minutes. That's your safety net.
Key Components in the Console (and CLI)
- Launch configuration (legacy) vs. Launch template (recommended) — templates support versioning and spot instances.
- Scaling policy types:
- Target tracking — you set a target metric value (e.g., average CPU 50%), and AWS adjusts the desired count to keep it near that.
- Step scaling — you define steps (e.g., if CPU > 80%, add 2; if > 60%, add 1).
- Simple scaling — a single alarm triggers a fixed adjustment (less flexible).
- Cooldown periods — time after a scaling action before another can trigger, to prevent flapping.
Hands-On Walkthrough
Let's get our hands dirty. We'll use the AWS CLI (v2) with jq for pretty output. Make sure your AWS credentials are configured and you have a default VPC (the CLI will use it).
Step 1: Create a Launch Template
We'll use a basic Amazon Linux 2 AMI and a t2.micro type. Save this as a JSON file or inline with --cli-input-json. Here's a clean CLI command:
aws ec2 create-launch-template \
--launch-template-name my-python-app-template \
--version-description v1 \
--launch-template-data '{
"ImageId": "ami-0c55b159cbfafe1f0",
"InstanceType": "t2.micro",
"SecurityGroupIds": ["sg-0123456789abcdef0"],
"KeyName": "my-key-pair",
"UserData": "IyEvYmluL2Jhc2gKc3VkbyB5dW0gdXBkYXRlIC15CnN1ZG8geXVtIGluc3RhbGwgLXkgIHB5dGhvbjMgcGlwIHB5dGhvbi1mbGFzawpzbHVkbyBweXRob24zIC1tIGZsYXNrIHJ1biBhcHAucHk=",
"TagSpecifications": [{
"ResourceType": "instance",
"Tags": [{"Key": "Role", "Value": "my-app"}]
}]
}'
The
UserDatabase64 string runs a simple script to install Python and run an app. In practice, you'd use a real AMI or a startup script that pulls your code from S3.
Expected output: A LaunchTemplate object with an ID like lt-0123456789abcdef0. Note the template ID — you'll use it in the next step.
Step 2: Create the Auto Scaling Group
Now we create the ASG, referencing the template, across two AZs for high availability:
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name my-python-app-asg \
--launch-template '{"LaunchTemplateName": "my-python-app-template", "Version": "1"}' \
--min-size 1 \
--max-size 5 \
--desired-capacity 2 \
--availability-zones "us-east-1a" "us-east-1b" \
--health-check-type EC2 \
--health-check-grace-period 300
The ASG will immediately launch 2 instances (desired capacity). Wait a few minutes and check:
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names my-python-app-asg --query "AutoScalingGroups[0].Instances"
You'll see an output with instance IDs, their health status Healthy, and lifecycle state InService.
Step 3: Attach a Target Tracking Scaling Policy
Now for the magic — automatic scaling based on CPU. We'll set a target average CPU utilization of 50%:
aws autoscaling put-scaling-policy \
--auto-scaling-group-name my-python-app-asg \
--policy-name cpu-target-tracking \
--policy-type TargetTrackingScaling \
--target-tracking-configuration '{"PredefinedMetricSpecification": {"PredefinedMetricType": "ASGAverageCPUUtilization"}, "TargetValue": 50.0}'
This policy tells the ASG: "Keep average CPU across all instances at 50%. If it goes above, add instances; if it drops below, remove some."
The output includes a PolicyARN — useful for CloudWatch alarms, but AWS manages them for you with target tracking.
Step 4: Simulate Load and Watch It Scale
SSH into one of your instances and generate CPU load with a simple stress tool:
# From your local machine, get the public IP of one instance (or use SSM)
ssh -i my-key.pem ec2-user@<public-ip>
# On the EC2 instance:
sudo amazon-linux-extras install -y epel
sudo yum install -y stress
stress --cpu 2 --timeout 120
After a few minutes, check the scaling activity:
aws autoscaling describe-scaling-activities \
--auto-scaling-group-name my-python-app-asg
You'll see an activity like Launching a new EC2 instance with a status of Successful. The ASG detected the CPU spike and scaled out.
Step 5: Verify and Clean Up
After the stress test ends, the ASG will scale back in (scale-in) — note that it may take several minutes due to cooldown. To confirm, check the instance count:
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names my-python-app-asg --query "AutoScalingGroups[0].DesiredCapacity"
Finally, clean up to avoid charges:
aws autoscaling delete-auto-scaling-group --auto-scaling-group-name my-python-app-asg --force-delete
aws ec2 delete-launch-template --launch-template-name my-python-app-template
Compare Options / When to Choose What
You don't have to build your own ASG from scratch — AWS offers several ways to scale. Here's a comparison to guide your choice:
| Option | Best For | Scaling Trigger | Complexity | Pros | Cons |
|---|---|---|---|---|---|
| Auto Scaling group (ASG) + target tracking | Most apps with predictable load | CloudWatch metric (e.g., CPU) | Medium | Hands-off, cost-effective | Requires tuning target value |
| ASG + scheduled scaling | Known traffic patterns (e.g., work hours) | Time-based | Low | Predictable, no alarms needed | Doesn't react to surprises |
| AWS Elastic Load Balancer + ASG | Apps needing traffic distribution and health checks | ELB metrics + instance health | Higher | Combines load balancing with scaling | More moving parts |
| AWS Fargate / ECS with Service Auto Scaling | Containerized apps | Custom metrics | Medium | Scales containers, not VMs | Requires containerization |
| Spot Fleet (via ASG with mixed instances) | Cost-sensitive batch jobs | Custom or target | High | Huge cost savings | Spot instances can be reclaimed |
When to choose what
- Start with ASG + target tracking — it's the sweet spot for most web apps. Set your target CPU to ~50% to leave headroom for spikes.
- Use scheduled scaling if your load is predictable — e.g., scale out at 6 AM and scale in at 10 PM. You'll save money by not over-provisioning at night.
- Pair with an Application Load Balancer when you have multiple instances and need to distribute traffic. The ALB health checks can drive ASG replace decisions.
- Choose Fargate if you're already containerized — it abstracts away instance management entirely.
Troubleshooting & Edge Cases
Even with managed services, things can go wrong. Here are the most common pitfalls and fixes:
1. "Scaling activity succeeded but instances are not healthy"
Symptom: The ASG launches new instances but they show as Unhealthy and get terminated.
Fix: Check your health check grace period — it's typically 300 seconds, but your app may take longer to boot. Increase it if needed. Also, verify your security group allows health checks from the ALB or that your instance passes EC2 status checks.
2. "The ASG keeps launching and terminating — flapping"
Symptom: You see frequent scale-out/scale-in records with no actual load.
Fix: Cooldown periods are your friend. Set them to at least 300 seconds. Also check your target tracking value — if it's too low (e.g., CPU 10%), the ASG will overreact to tiny spikes. Raise the target.
3. "My instances never launch — 'Launch failed' error"
Symptom: The ASG shows Launch failed in activities.
Fix: The most common cause is incorrect AMI ID or insufficient permissions (the ASG needs ec2:RunInstances). Ensure your launch template references a valid AMI in the same region, and that the instance type is allowed in that AZ. Check CloudTrail for role permission errors.
4. "The ASG scaled out but my app didn't see the new instance"
Symptom: The ASG says Successful, but your app traffic doesn't reach the new instance.
Fix: If you're using an ALB, ensure your ASG is registered as a target group. If not, you need a way to discover instances (e.g., via DNS, ECS, or a load balancer). Remember: an ASG alone does not route traffic — you need an ALB or service discovery.
5. "My ASG won't scale in — it stays at max size"
Symptom: The ASG stays at max even when load is low.
Fix: Look at your scale-in policy. With target tracking, AWS may take a long time if the metric is close to target. Check your policy cooldown and the metric's statistic (average across all instances vs. sum). Also, ensure you haven't set -min-size too high by mistake.
What You Learned & What's Next
Let's recap what we covered:
- The core problem: Manual scaling of EC2 instances is error-prone and slow. ASGs solve this by automating the process.
- The mental model: An ASG is a fleet manager that maintains a desired instance count, adjusts on demand, and replaces unhealthy instances.
- Step-by-step: We created a launch template, an ASG, a target tracking policy, and simulated load with
stress— and watched it scale out and in. - Comparison: We evaluated ASG vs. scheduled scaling vs. ALB+ASG vs. Fargate. For most apps, target tracking wins.
- Troubleshooting: We covered common pitfalls like health check failures, flapping, and launch failures.
You've now mastered the fundamentals of scaling EC2 with Auto Scaling groups. You can apply this to any workload — from a simple Python API to a distributed worker pool.
Next in the track: You'll dive into Elastic Load Balancing with Application Load Balancer — see how to distribute traffic across your ASG and make your app truly resilient. The ALB will also provide the health checks that make your ASG even smarter. You're not just scaling — you're building a self-healing system.
Practice recap
Try this now: create a second ASG with a scheduled scaling policy that scales out at 5 PM and scales in at 1 AM. Use the same launch template and observe how the instance count changes. Then, attach an Application Load Balancer to distribute traffic across the ASG. If you get stuck, revisit the troubleshooting section — and get ready for the next lesson on ELB.
Common mistakes
- Setting the desired capacity to a fixed number instead of a range (min/max) — you lose elasticity.
- Forgetting to set a cooldown period → the ASG flaps, launching and terminating instances rapidly.
- Using an AMI that's not in the same region as your ASG → launch failures. Always verify the AMI ID region.
- Not setting a health check grace period → AWS terminates instances before your app is ready.
- Confusing a launch template's instance type with your ASG's capacity — you still need to set min/max correctly.
Variations
- Use scheduled scaling instead of target tracking for predictable workloads — e.g., scale out every weekday at 8 AM and scale in at 8 PM.
- Combine your ASG with an Application Load Balancer (ALB) to route traffic and use ALB health checks for instance replacement.
- Use EC2 Spot instances within the ASG (via mixed instances policy) to reduce costs by up to 90% for fault-tolerant workloads.
Real-world use cases
- A Python Flask API behind an ALB that auto-scales based on CPU, handling traffic spikes during product launches.
- A batch processing pipeline that uses scheduled scaling to spin up 20 instances at night for heavy number crunching, then scales to 0.
- A microservices architecture where each service runs its own ASG with target tracking on latency, ensuring consistent performance.
Key takeaways
- An Auto Scaling group is a fleet manager — it maintains desired capacity, replacing unhealthy instances automatically.
- Always define a launch template with a base64-encoded user data script to bootstrap your app on new instances.
- Target tracking scaling is the easiest way to scale — set a CPU target (e.g., 50%) and AWS does the rest.
- Health check grace periods are crucial; set them long enough for your app to become ready.
- ELB or service discovery is required for traffic distribution — an ASG alone doesn't route requests.
- Clean up your ASGs and templates to avoid unexpected charges — use
--force-deletewhen needed.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.