AWS for DevOps — Auto Scaling

3 minute read

3.5 — Auto Scaling

Auto Scaling automatically adjusts the number of EC2 instances based on demand. When traffic increases, it adds instances. When traffic drops, it removes them. This is how you build systems that are both resilient and cost-efficient.

Components

  ┌── Launch Template ──────────────────────────┐
  │  AMI: ami-0custom123                        │
  │  Instance Type: t3.medium                   │
  │  Key Pair: devops-key                       │
  │  Security Groups: [sg-app123]               │
  │  User Data: setup.sh                        │
  │  IAM Instance Profile: app-role             │
  └─────────────────────────────────────────────┘
               │
               ▼
  ┌── Auto Scaling Group ──────────────────────┐
  │  Min: 2     Desired: 3     Max: 10         │
  │  Subnets: [subnet-1a, subnet-1b, subnet-1c]│
  │  Health Check: ELB                          │
  │  Target Group: app-tg                       │
  │                                             │
  │  Scaling Policies:                          │
  │  - CPU > 70% → add 2 instances             │
  │  - CPU < 30% → remove 1 instance           │
  └─────────────────────────────────────────────┘

Launch Template

A launch template defines how instances are created:

$ aws ec2 create-launch-template \
    --launch-template-name web-app-template \
    --version-description "v1 - Nginx + app" \
    --launch-template-data '{
        "ImageId": "ami-0custom123",
        "InstanceType": "t3.medium",
        "KeyName": "devops-key",
        "SecurityGroupIds": ["sg-app123"],
        "IamInstanceProfile": {"Name": "app-instance-profile"},
        "UserData": "'$(base64 -w0 setup.sh)'",
        "TagSpecifications": [
            {
                "ResourceType": "instance",
                "Tags": [
                    {"Key": "Name", "Value": "web-app"},
                    {"Key": "Environment", "Value": "production"}
                ]
            }
        ],
        "MetadataOptions": {
            "HttpTokens": "required",
            "HttpEndpoint": "enabled"
        }
    }'

Creating an Auto Scaling Group

$ aws autoscaling create-auto-scaling-group \
    --auto-scaling-group-name web-app-asg \
    --launch-template LaunchTemplateName=web-app-template,Version='$Latest' \
    --min-size 2 \
    --max-size 10 \
    --desired-capacity 3 \
    --vpc-zone-identifier "subnet-1a,subnet-1b,subnet-1c" \
    --target-group-arns arn:aws:elasticloadbalancing:ap-south-1:123456789012:targetgroup/app-tg/abc123 \
    --health-check-type ELB \
    --health-check-grace-period 300 \
    --tags Key=Name,Value=web-app,PropagateAtLaunch=true \
           Key=Environment,Value=production,PropagateAtLaunch=true
Parameter Purpose
min-size Never fewer than this many instances
max-size Never more than this many instances
desired-capacity Target number (ASG tries to maintain this)
health-check-type ELB Use ALB health checks (not just EC2 status)
health-check-grace-period Wait 300s after launch before checking health

Scaling Policies

Target Tracking (recommended) — ASG automatically adjusts to maintain a metric:

$ aws autoscaling put-scaling-policy \
    --auto-scaling-group-name web-app-asg \
    --policy-name cpu-target-70 \
    --policy-type TargetTrackingScaling \
    --target-tracking-configuration '{
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "ASGAverageCPUUtilization"
        },
        "TargetValue": 70.0,
        "ScaleInCooldown": 300,
        "ScaleOutCooldown": 60
    }'

This says: “Keep average CPU at 70%. Add instances when above, remove when below.”

Step Scaling — define specific thresholds:

$ aws autoscaling put-scaling-policy \
    --auto-scaling-group-name web-app-asg \
    --policy-name high-cpu-step \
    --policy-type StepScaling \
    --adjustment-type ChangeInCapacity \
    --step-adjustments \
        'MetricIntervalLowerBound=0,MetricIntervalUpperBound=20,ScalingAdjustment=1' \
        'MetricIntervalLowerBound=20,ScalingAdjustment=3'

Scheduled Scaling — for predictable traffic patterns:

# Scale up every weekday at 8 AM
$ aws autoscaling put-scheduled-update-group-action \
    --auto-scaling-group-name web-app-asg \
    --scheduled-action-name morning-scale-up \
    --recurrence "0 8 * * MON-FRI" \
    --desired-capacity 6

# Scale down at 8 PM
$ aws autoscaling put-scheduled-update-group-action \
    --auto-scaling-group-name web-app-asg \
    --scheduled-action-name evening-scale-down \
    --recurrence "0 20 * * MON-FRI" \
    --desired-capacity 2

Monitoring the ASG

# Check current state
$ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names web-app-asg \
    --query 'AutoScalingGroups[0].{Min:MinSize,Max:MaxSize,Desired:DesiredCapacity,Instances:Instances[*].{Id:InstanceId,Health:HealthStatus,AZ:AvailabilityZone}}'
{
    "Min": 2,
    "Max": 10,
    "Desired": 3,
    "Instances": [
        {"Id": "i-0abc1", "Health": "Healthy", "AZ": "ap-south-1a"},
        {"Id": "i-0abc2", "Health": "Healthy", "AZ": "ap-south-1b"},
        {"Id": "i-0abc3", "Health": "Healthy", "AZ": "ap-south-1c"}
    ]
}

# View scaling activities
$ aws autoscaling describe-scaling-activities \
    --auto-scaling-group-name web-app-asg \
    --query 'Activities[0:3].[StartTime,StatusCode,Description]' \
    --output table

Instance Refresh (Rolling Deployments)

When you update the launch template (e.g., new AMI), use instance refresh to roll out the change:

$ aws autoscaling start-instance-refresh \
    --auto-scaling-group-name web-app-asg \
    --preferences '{
        "MinHealthyPercentage": 90,
        "InstanceWarmup": 300
    }'

This replaces instances in batches, keeping at least 90% healthy at all times.

Real-World Analogy

Think of Auto Scaling like a restaurant staffing model:

  • Min size = skeleton crew (always at least 2 waiters)
  • Desired = normal staffing (3 waiters for typical lunch)
  • Max size = maximum capacity (10 waiters for a holiday rush)
  • Scaling policy = “if tables are more than 70% full, call in another waiter”
  • Cooldown = “wait 5 minutes before calling another one”

Common Mistakes

Mistake Why It Hurts Fix
Setting min = max = desired Defeats the purpose of Auto Scaling Set a range (e.g., 2-10)
Not using ELB health checks Unhealthy instances stay in the group Set health-check-type ELB
Too short cooldown Thrashing — adding and removing rapidly Set cooldown to 300s+
No health check grace period New instances killed before they’re ready Set grace period to match boot time
Not spreading across AZs One AZ outage takes down everything Use subnets in 2-3 AZs

Quick Reference

Task Command
Create launch template aws ec2 create-launch-template --launch-template-name NAME --launch-template-data ...
Create ASG aws autoscaling create-auto-scaling-group --auto-scaling-group-name NAME ...
Set target tracking aws autoscaling put-scaling-policy --policy-type TargetTrackingScaling ...
Set scheduled action aws autoscaling put-scheduled-update-group-action --scheduled-action-name NAME ...
Start instance refresh aws autoscaling start-instance-refresh --auto-scaling-group-name NAME
View ASG aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names NAME
Update desired capacity aws autoscaling set-desired-capacity --auto-scaling-group-name NAME --desired-capacity N

Next → Compute Exercises

Updated: