LAB11_AWS_Auto Scaling Group with Launch Template

Auto Scaling Group with Launch Template

☁ AWS ⭐ Beginner ASG Launch Template Scaling Policies User Data

Scenario

Traffic to your web application spikes every weekday morning. A single EC2 instance can't handle peak load, and you can't afford to run large instances 24/7. The solution is an Auto Scaling Group that launches additional instances during peak load and terminates them during off-peak hours.

Build an ASG using a Launch Template that installs nginx via a user data script on boot, with a target tracking scaling policy that maintains average CPU at 50%.

Your Objectives
  • Create an aws_launch_template with the latest Amazon Linux 2 AMI, t3.micro, user data, and a security group.
  • Create an aws_autoscaling_group with min_size=1, max_size=4, desired_capacity=2 across two subnets.
  • Add a target tracking scaling policy that maintains average CPU at 50%.
  • Enable instance refresh so new launch template versions roll out gracefully.
  • Output the ASG name and ARN.

Additional Context

Launch Templates (not the older Launch Configurations) are the current best practice. They support versioning, which enables the instance_refresh feature — when you update the template (new AMI, new user data), the ASG can gradually replace existing instances with the new configuration without downtime.

The user_data argument expects a base64-encoded string. Use the built-in base64encode() function or the HCL filebase64() function to encode a shell script.

versions.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

variables.tf

variable "aws_region"  { type = string; default = "us-east-1" }
variable "vpc_id"      { type = string; description = "VPC for the ASG instances." }
variable "subnet_ids"  { type = list(string); description = "List of subnet IDs (min 2 AZs)." }
variable "environment" { type = string; default = "prod" }

main.tf

# main.tf

provider "aws" {
  region = var.aws_region
}

data "aws_ami" "amazon_linux_2" {
  most_recent = true
  owners      = ["amazon"]
  filter { name = "name"; values = ["amzn2-ami-hvm-*-x86_64-gp2"] }
  filter { name = "virtualization-type"; values = ["hvm"] }
}

resource "aws_security_group" "asg" {
  name   = "asg-web-sg-${var.environment}"
  vpc_id = var.vpc_id
  ingress { from_port = 80; to_port = 80; protocol = "tcp"; cidr_blocks = ["0.0.0.0/0"] }
  egress  { from_port = 0;  to_port = 0;  protocol = "-1"; cidr_blocks  = ["0.0.0.0/0"] }
  tags = { Name = "asg-web-sg", Environment = var.environment }
}

# Launch Template — the reusable instance blueprint for the ASG.
resource "aws_launch_template" "web" {
  name_prefix   = "web-server-"
  image_id      = data.aws_ami.amazon_linux_2.id
  instance_type = "t3.micro"

  # User data runs once on first boot — installs and starts nginx.
  # base64encode() encodes the shell script as required by the API.
  user_data = base64encode(<<-EOF
    #!/bin/bash
    yum update -y
    yum install -y nginx
    systemctl enable nginx
    systemctl start nginx
    echo "

Hello from $(hostname)

" > /usr/share/nginx/html/index.html EOF ) network_interfaces { associate_public_ip_address = true security_groups = [aws_security_group.asg.id] } tag_specifications { resource_type = "instance" tags = { Name = "asg-web-instance", Environment = var.environment, ManagedBy = "terraform" } } lifecycle { create_before_destroy = true # create new version before deleting old template } } # Auto Scaling Group. resource "aws_autoscaling_group" "web" { name = "web-asg-${var.environment}" vpc_zone_identifier = var.subnet_ids # spread instances across these subnets/AZs min_size = 1 max_size = 4 desired_capacity = 2 launch_template { id = aws_launch_template.web.id version = "$Latest" # always use the latest launch template version } # Instance refresh: when launch template changes, gradually replace instances. # min_healthy_percentage=90 means at least 90% of desired capacity stays healthy. instance_refresh { strategy = "Rolling" preferences { min_healthy_percentage = 90 } } tag { key = "ManagedBy" value = "terraform" propagate_at_launch = true } } # Target Tracking Scaling Policy — maintains average CPU at 50%. # ASG adds instances when above 50%, removes instances when below. resource "aws_autoscaling_policy" "cpu_target" { name = "web-cpu-target-tracking" autoscaling_group_name = aws_autoscaling_group.web.name policy_type = "TargetTrackingScaling" target_tracking_configuration { predefined_metric_specification { predefined_metric_type = "ASGAverageCPUUtilization" } target_value = 50.0 # maintain average CPU at 50% } }

outputs.tf

output "asg_name" { value = aws_autoscaling_group.web.name }
output "asg_arn"  { value = aws_autoscaling_group.web.arn }

Workflow Commands

terraform init
terraform apply -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-aaa","subnet-bbb"]'

# List running instances in the ASG
aws autoscaling describe-auto-scaling-instances \
  --query "AutoScalingInstances[?AutoScalingGroupName=='web-asg-prod']"

terraform destroy -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-aaa","subnet-bbb"]'
✓ Expected Output After Apply
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

asg_arn  = "arn:aws:autoscaling:us-east-1:123456789012:autoScalingGroup:..."
asg_name = "web-asg-prod"