AWS with Terraform Tutorial: AWS Auto Scaling (15)

· 10 min read · Terraform & OpenTofu Tutorials

How to create AWS Auto Scaling Groups with Terraform #

Using the Terraform aws_launch_template, aws_autoscaling_group and aws_autoscaling_policy resource blocks to run a self-healing group of EC2 instances that grows and shrinks with demand.

Welcome to our tutorial series about Terraform or OpenTofu on AWS. In the previous sections a VPC, subnets, security groups, EC2 instances, a database and DNS were defined. In this section the front-end server stops being a single instance and becomes a group of instances that AWS replaces when they fail and scales when the load changes.

Terraform is an Infrastructure as Code (IaC) tool used to provision and manage infrastructure. It helps define and deploy resources across various cloud providers using code, making it easier to maintain and scale infrastructure.Terraform is an Infrastructure as Code (IaC) tool used to provision and manage infrastructure. It helps define and deploy resources across various cloud providers using code, making it easier to maintain and scale infrastructure.
Terraform
Basics
Terraform...
AWS is the world’s leading cloud platform, it is used by a wide range of organizations, from startups to large enterprises, to power their online businesses. AWS offers a wide range of services, including computing, storage, database, networking, analytics, machine learning, and artificial intelligence.AWS is the world’s leading cloud platform, it is used by a wide range of organizations, from startups to large enterprises, to power their online businesses. AWS offers a wide range of services, including computing, storage, database, networking, analytics, machine learning, and artificial intelligence.
AWS
Basics
AWS...
The Terraform official AWS provider acts as an abstraction layer that lets Terraform configurations written in HCL define AWS services and infrastructure using code (IaC). Internally Terraform and the AWS provider handle authentication, and make the necessary AWS API calls to query, create, modify, and destroy the resources.The Terraform official AWS provider acts as an abstraction layer that lets Terraform configurations written in HCL define AWS services and infrastructure using code (IaC). Internally Terraform and the AWS provider handle authentication, and make the necessary AWS API calls to query, create, modify, and destroy the resources.
Terraform
AWS Provider
Terraform...
How to Create, and Manage AWS VPCs with TerraformHow to Create, and Manage AWS VPCs with Terraform
AWS VPC
AWS VPC
How to configure and use the Terraform aws_subnet resource block to create and manage AWS Subnets inside a VPC.How to configure and use the Terraform aws_subnet resource block to create and manage AWS Subnets inside a VPC.
AWS Subnets
AWS Subnets
How to configure and use the Terraform aws_internet_gateway resource block to create and manage AWS Internet Gateway inside a VPC to enable Internet access to and from instances. How to configure and use the Terraform aws_internet_gateway resource block to create and manage AWS Internet Gateway inside a VPC to enable Internet access to and from instances.
AWS Internet
Gateway
AWS Internet...
How to configure and use the Terraform aws_nat_gateway and aws_eip resource blocks to create and manage AWS NAT Gateway and its corresponding Public IPs inside each availability zone to enable Internet access from instances in private subnets.How to configure and use the Terraform aws_nat_gateway and aws_eip resource blocks to create and manage AWS NAT Gateway and its corresponding Public IPs inside each availability zone to enable Internet access from instances in private subnets.
AWS NAT
Gateway
AWS NAT...
How to configure and use the Terraform aws_route_table, aws_route, and aws_main_route_table_association resource blocks to create and manage AWS Routing Tables.How to configure and use the Terraform aws_route_table, aws_route, and aws_main_route_table_association resource blocks to create and manage AWS Routing Tables.
AWS Routing
Tables
AWS Routing...
How to configure and use the Terraform aws_security_group and aws_security_group_rule resource blocks to create and manage AWS Security Groups and secure the infrastructure.How to configure and use the Terraform aws_security_group and aws_security_group_rule resource blocks to create and manage AWS Security Groups and secure the infrastructure.
AWS Security
Groups
AWS Security...
How to configure and use the Terraform aws_key_pair resource block to create and manage AWS Key Pairs for performing SSH Public Key Authentication into EC2 instances.How to configure and use the Terraform aws_key_pair resource block to create and manage AWS Key Pairs for performing SSH Public Key Authentication into EC2 instances.
AWS Key
Pairs
AWS Key...
How to configure and use the Terraform aws_ami data source block to find and use AWS AMIs as templates (root volume snapshot with operating system and applications) for EC2 instances.How to configure and use the Terraform aws_ami data source block to find and use AWS AMIs as templates (root volume snapshot with operating system and applications) for EC2 instances.
AWS AMIs
AWS AMIs
Using the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.Using the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.
AWS EC2
Instances
AWS EC2...
AWS RDS
AWS RDS
Using the Terraform aws_route53_delegation_set, aws_route53_zone, and aws_route53_record resource blocks to configure DNS in AWS. Using the Terraform aws_route53_delegation_set, aws_route53_zone, and aws_route53_record resource blocks to configure DNS in AWS.
AWS Route 53
(DNS)
AWS Route 53...
Amazon EC2 Auto Scaling groups keep the right number of instances running, replace failed ones and scale with the load.Amazon EC2 Auto Scaling groups keep the right number of instances running, replace failed ones and scale with the load.
AWS Auto
Scaling
AWS Auto...
Elastic Load Balancing distributes the traffic between healthy instances. The tutorial creates an Application Load Balancer with HTTPS.Elastic Load Balancing distributes the traffic between healthy instances. The tutorial creates an Application Load Balancer with HTTPS.
AWS Load
Balancers
AWS Load...
This tutorial shows how to create infrastructure in AWS using Terraform and configure the operating system and applications using Ansible.This tutorial shows how to create infrastructure in AWS using Terraform and configure the operating system and applications using Ansible.
Terraform,
AWS & Ansible
Terraform,...
Modules package resources behind variables and outputs so they can be reused. The tutorial refactors the network into a module.Modules package resources behind variables and outputs so they can be reused. The tutorial refactors the network into a module.
Terraform
Modules
Terraform...
A backend stores the Terraform state. The tutorial uses an encrypted, versioned S3 bucket with state locking.A backend stores the Terraform state. The tutorial uses an encrypted, versioned S3 bucket with state locking.
Terraform
Backends
Terraform...
fmt, validate, TFLint, Trivy, Checkov, terraform-docs, Infracost and pre-commit: check the code before it reaches AWS.fmt, validate, TFLint, Trivy, Checkov, terraform-docs, Infracost and pre-commit: check the code before it reaches AWS.
Terraform
Tools
Terraform...
Run checks, plans and approved applies automatically with GitHub Actions and OIDC, without access keys.Run checks, plans and approved applies automatically with GitHub Actions and OIDC, without access keys.
Terraform
CI/CD
Terraform...
Select a tutorial section 
Select a tutorial sectio...

Prerequisites #

Read the previous sections of the tutorial, listed in the series index at the end of this page. This section reuses:

  • the public subnets ditwl-sn-za-pro-pub-00 and ditwl-sn-zb-pro-pub-04 from AWS Subnets,
  • the security groups ditwl-sg-base-ec2 and ditwl-sg-front-end from AWS Security Groups,
  • the key pair ditwl-kp-config-user from AWS Key Pairs,
  • the AMI data source ubuntu-23-04-arm64-minimal from AWS AMIs.

AWS Auto Scaling #

An Amazon EC2 Auto Scaling group (ASG) is a collection of EC2 instances managed as a single unit. It keeps the number of instances between a minimum and a maximum, spreads them across Availability Zones, replaces the ones that fail their health checks and adds or removes instances following scaling policies.

Three resources are needed:

  • Launch template (aws_launch_template): the blueprint of every instance in the group: AMI, instance type, key pair, security groups, user data and tags.
  • Auto Scaling group (aws_autoscaling_group): how many instances, in which subnets (Availability Zones) and how to check their health.
  • Scaling policy (aws_autoscaling_policy) and optionally scheduled actions (aws_autoscaling_schedule): when to add or remove instances.

What changes in the infrastructure #

The EC2 section defined one front-end server, ditwl-ec-front-end-001. Instances in an Auto Scaling group are created and destroyed by AWS, so they cannot be defined one by one in Terraform and their IP addresses change. Make these changes in terraform-aws-tutorial.tf:

  1. Remove (or comment out) the resource aws_instance.ditwl-ec-front-end-001. The back-end server ditwl-ec-back-end-123 is not changed.
  2. Remove the Route 53 records that point to that instance (ditwl-r53-public-front-end-001 and ditwl-r53-private-front-end-001). The next section, AWS Load Balancers, adds one stable DNS name for the whole group.

Definition of an Auto Scaling group with Terraform #

Launch template: ditwl-lt-front-end #

terraform-aws-tutorial.tf
# Launch template for the front-end servers
resource "aws_launch_template" "ditwl-lt-front-end" {
  name                   = "ditwl-lt-front-end"
  image_id               = data.aws_ami.ubuntu-23-04-arm64-minimal.id
  instance_type          = "t4g.micro"
  key_name               = "ditwl-kp-config-user"
  vpc_security_group_ids = [aws_security_group.ditwl-sg-base-ec2.id, aws_security_group.ditwl-sg-front-end.id]
  update_default_version = true # every change creates a new version and makes it the default

  metadata_options {
    http_endpoint = "enabled"
    http_tokens   = "required" # only IMDSv2 (session tokens) can read the instance metadata
  }

  # Cloud-init script: install a web server that shows the name of the instance
  user_data = base64encode(<<-EOT
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    echo "front-end $(hostname)" > /var/www/html/index.html
  EOT
  )

  tag_specifications {
    resource_type = "instance"
    tags = {
      "Name"        = "ditwl-ec-front-end"
      "app"         = "front-end"
      "os"          = "ubuntu"
      "environment" = "pro"
      "cost_center" = "marketing-department"
      "owner"       = "IT Wonder Lab"
    }
  }
}
  • user_data must be Base64 encoded in launch templates. base64encode() takes care of it.
  • http_tokens = "required" is a good security default: it disables IMDSv1, which is used in server-side request forgery attacks.
  • The provider default_tags are not copied to the instances launched by an Auto Scaling group, that is why the tags are repeated in tag_specifications.

Auto Scaling group: ditwl-asg-front-end #

terraform-aws-tutorial.tf
# Auto Scaling group for the front-end servers, one or more instances in two Availability Zones
resource "aws_autoscaling_group" "ditwl-asg-front-end" {
  name                      = "ditwl-asg-front-end"
  min_size                  = 1
  max_size                  = 4
  desired_capacity          = 2
  vpc_zone_identifier       = [aws_subnet.ditwl-sn-za-pro-pub-00.id, aws_subnet.ditwl-sn-zb-pro-pub-04.id]
  health_check_type         = "EC2"
  health_check_grace_period = 120

  launch_template {
    id      = aws_launch_template.ditwl-lt-front-end.id
    version = aws_launch_template.ditwl-lt-front-end.latest_version
  }

  # Replace the instances gradually when the launch template changes (new AMI, new user data...)
  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 50
    }
  }

  # Tag applied to the group and to every instance it launches
  tag {
    key                 = "Name"
    value               = "ditwl-ec-front-end"
    propagate_at_launch = true
  }

  # The scaling policy changes the desired capacity, Terraform must not undo it
  lifecycle {
    ignore_changes = [desired_capacity]
  }
}
  • min_size, max_size and desired_capacity are the lower limit, the upper limit (also a cost guardrail) and the number of instances at the start.
  • vpc_zone_identifier lists one subnet per Availability Zone. The group balances the instances between them.
  • version = ...latest_version makes the group change when the launch template changes, which is what triggers the instance refresh. With "$Latest" Terraform would not see any difference.

Scaling policy: keep the average CPU at 50% #

terraform-aws-tutorial.tf
# Target tracking: AWS adds or removes instances to keep the average CPU around 50%
resource "aws_autoscaling_policy" "ditwl-asp-front-end-cpu" {
  name                      = "ditwl-asp-front-end-cpu"
  autoscaling_group_name    = aws_autoscaling_group.ditwl-asg-front-end.name
  policy_type               = "TargetTrackingScaling"
  estimated_instance_warmup = 120

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = 50.0
  }
}

Target tracking works like a thermostat: it creates the CloudWatch alarms and calculates how many instances are needed. It is the simplest policy and the right choice in most cases.

Scheduled action: fewer instances at night #

terraform-aws-tutorial.tf
# Every day at 22:00 UTC reduce the group to one instance (and back to two at 06:00 UTC)
resource "aws_autoscaling_schedule" "ditwl-ass-front-end-night" {
  scheduled_action_name  = "ditwl-ass-front-end-night"
  autoscaling_group_name = aws_autoscaling_group.ditwl-asg-front-end.name
  min_size               = 1
  max_size               = 2
  desired_capacity       = 1
  recurrence             = "0 22 * * *"
}

resource "aws_autoscaling_schedule" "ditwl-ass-front-end-day" {
  scheduled_action_name  = "ditwl-ass-front-end-day"
  autoscaling_group_name = aws_autoscaling_group.ditwl-asg-front-end.name
  min_size               = 1
  max_size               = 4
  desired_capacity       = 2
  recurrence             = "0 6 * * *"
}

Run the Terraform Plan #

Plan using OpenTofu #

$ tofu plan

The plan creates the launch template, the Auto Scaling group, the scaling policy and the two scheduled actions, and destroys the instance ditwl-ec-front-end-001 (and the DNS records if they were defined).

Apply the changes using OpenTofu #

$ tofu apply

Type yes to confirm. The group starts two instances, one in each Availability Zone. List them with the AWS CLI:

$ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names ditwl-asg-front-end \
    --query 'AutoScalingGroups[0].Instances[].[InstanceId,AvailabilityZone,LifecycleState,HealthStatus]' \
    --output table --profile ditwl_infradmin

Test the self-healing #

Terminate one of the instances and watch the group create a replacement in a couple of minutes, without any change in Terraform:

$ aws ec2 terminate-instances --instance-ids i-0123456789abcdef0 --profile ditwl_infradmin
$ aws autoscaling describe-scaling-activities --auto-scaling-group-name ditwl-asg-front-end \
    --max-items 3 --profile ditwl_infradmin

To see the scale-out, connect to one instance and generate CPU load (for example with stress-ng). After a few minutes the average CPU goes over 50% and the group adds instances up to max_size.

Destroy the infrastructure using OpenTofu #

$ tofu destroy

AWS Auto Scaling Cost #

An Auto Scaling group has no additional charge: you pay for the resources it launches (EC2 instances, EBS volumes and data transfer). The cost is therefore controlled by max_size and the instance type. Detailed CloudWatch monitoring, if enabled in the launch template, is billed separately. Check the EC2 pricing page for the current prices and remember to destroy the infrastructure after the tests.

Common Questions About AWS Auto Scaling #

What is the difference between minimum, maximum and desired capacity? #

min_size and max_size are the limits that no policy can cross. desired_capacity is the number of instances the group tries to have right now. Scaling policies change the desired capacity inside those limits.

Why is desired_capacity in ignore_changes? #

Because AWS changes it while scaling. Without ignore_changes, the next tofu apply would reset the group to the value in the code and fight with the scaling policy.

How can I roll out a new AMI or a new version of the user data? #

Change the launch template. The new version is used by new instances, and the instance_refresh block replaces the existing ones gradually, keeping at least 50% of the capacity healthy.

Can I use Spot Instances to reduce the cost? #

Yes. Replace the launch_template block with a mixed_instances_policy block, which lets the group combine On-Demand and Spot capacity and several instance types. It is a good fit for stateless front-end servers.

Why not use count or for_each on aws_instance? #

Terraform would create a fixed set of instances and nothing would replace a failed one or react to the load. An Auto Scaling group delegates that work to AWS.

Next Steps #

The instances in the group are created and destroyed all the time and each one has a different IP address, so users need a single stable entry point. Continue with AWS Load Balancers.

#AWS #AWS EC2 #Terraform #OpenTofu #AWS Terraform Tutorial