Getting Started with AWS and Terraform: Optimizing Web Application Scalability with Auto Scaling and Terraform
Tags: aws, terraform, autoscaling, upskilling
Published: 2024-01-27 10:55:31

In our previous post, we delved into the utilization of the Application Load Balancer (ALB) to enhance the resilience and scalability of a web application by distributing traffic among multiple Availability Zones (AZs) housing EC2 instances. 

Building upon that foundation, this post focuses on implementing auto scaling using Terraform to further optimize scalability and ensure the continuous availability of our web application.

## Architecture Overview
Before diving into the implementation details, let's take a moment to understand the architecture we'll be working with:

![EC2 Auto Scaling Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1pxarrk7leysax8ut1lq.png)

## Step 1: Creating the AMI from Existing Template
Auto scaling requires a pre-built Amazon Machine Image (AMI). To facilitate this, we will use a YAML template provided in an AWS workshop and use cloudformation to create AMI. Download the YAML Template [here](https://static.us-east-1.prod.workshops.aws/public/24ee3602-0f8e-46c7-a792-c90459b65664/static/common/ec2_auto_scaling/EC2-Auto-Scaling-Lab.yaml).


![Create CloudFormation Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3t20vfjgib49xsngyzle.png)

![Execute CloudFormation Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3oufmpdfhqubkh0ao1pi.png)

![Running EC2 Instance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fomvssdf2ql737uv4jf5.png)

Create AMI from above EC2 Instance:

![AMI Created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuh6k0z0f3q34xhh3hj9.png)

## Step 2: Creating VPC and Network Components
Create VPC with IGW, 4 public subnets in separate AZs and route table with association. Please refer to my github repo in resources section below.

## Step 3: Creating Linux EC2 Web Server Instances with Auto Scaling Group
Create a launch template using the AMI from Step 1.
```terraform
####################################################
# Get AMI created earlier
####################################################
data "aws_ami" "amazon-linux-ami" {
  most_recent = true
  owners      = ["self"]
  filter {
    name   = "name"
    values = ["CT_Auto_Scaling_Webhost"]
  }
}
####################################################
# Create Launch Template Resource
####################################################
resource "aws_launch_template" "aws-launch-template" {
  image_id               = data.aws_ami.amazon-linux-ami.id
  instance_type          = var.instance_type
  key_name               = var.instance_key
  vpc_security_group_ids = var.security_group_ec2
  update_default_version = true
  tag_specifications {
    resource_type = "instance"
  }
  monitoring {
    enabled = true
  }
}
```
Create an auto scaling group with a scaling policy, specifically the TargetTrackingScaling with the metric ASGAverageCPUUtilization. Auto scaling will be triggered if ASGAverageCPUUtilization exceeds 20%.
```terraform
####################################################
# Create auto scaling group
####################################################
resource "aws_autoscaling_group" "aws-autoscaling-group" {
  #  name                = "${var.project_name}-ASG-Group"
  vpc_zone_identifier = tolist(var.public_subnets)
  desired_capacity    = 1
  max_size            = 4
  min_size            = 1

  launch_template {
    id      = aws_launch_template.aws-launch-template.id
    version = aws_launch_template.aws-launch-template.latest_version
  }
}

####################################################
# Create target tracking scaling policy for average CPU utilization
####################################################
resource "aws_autoscaling_policy" "avg_cpu_scaling_policy" {
  name                   = "avg_cpu_scaling_policy"
  policy_type            = "TargetTrackingScaling"
  autoscaling_group_name = aws_autoscaling_group.aws-autoscaling-group.name
  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = 20.0
  }
  estimated_instance_warmup = 180
}
```

## Step 4: Creating an Application Load Balancer
Create an ALB with an attachment to auto scaling group
```terraform
####################################################
# create application load balancer
####################################################
resource "aws_lb" "aws-application_load_balancer" {
  internal           = false
  load_balancer_type = "application"
  security_groups    = [var.security_group_alb[0]]
  //subnets                    = [var.public_subnets[0],var.public_subnets[1] ,var.public_subnets[2],var.public_subnets[3]]
  subnets                    = tolist(var.public_subnets)
  enable_deletion_protection = false
}
####################################################
# create target group for ALB
####################################################
resource "aws_lb_target_group" "alb_target_group" {
  target_type = "instance"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = var.vpc_id

  health_check {
    enabled             = true
    interval            = 300
    path                = "/"
    timeout             = 60
    matcher             = 200
    healthy_threshold   = 5
    unhealthy_threshold = 5
  }

  lifecycle {
    create_before_destroy = true
  }
}

####################################################
# create a listener on port 80 with redirect action
####################################################
resource "aws_lb_listener" "alb_http_listener" {
  load_balancer_arn = aws_lb.aws-application_load_balancer.id
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.alb_target_group.id
  }
}

####################################################
# attach auto scaling group to Application Load Balancer ALB
####################################################
resource "aws_autoscaling_attachment" "asg_attachment_alb" {
  autoscaling_group_name = var.autoscaling_group_id
  lb_target_group_arn    = aws_lb_target_group.alb_target_group.arn
}
```

## Steps to run Terraform
```terraform
terraform init
terraform plan 
terraform apply -auto-approve
```

Upon successful completion, Terraform will provide relevant outputs.
```terraform
Apply complete! Resources: 21 added, 0 changed, 0 destroyed.

Outputs:

alb_dns_name = "tf-lb-20240115192631610100000006-1207062642.us-east-1.elb.amazonaws.com"
```

## Testing the Auto Scaling
Use the ALB DNS name to access the website. As only a single instance is running initially, all requests will be directed to that instance.

![Desired EC2 Instances](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe4w6n4bzxye3q6wgchf.png)

![Desired EC2 - Web page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rpei8hm2lcg68eb1mcms.png)

**Scale out**: Induce CPU load to stress the CPU, triggering auto scaling to increase the number of instances.

![Stress CPU](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpfvu4tlp7whm7q1rg5k.png)

![Scaled out instances](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f93ykk9krr9h6hthzl31.png)

![2nd EC2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ckx94ye0ou73me97row.png)

![3rd EC2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o095ptj6ffmli1zwag7.png)

![4th EC2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n13ybgldm3tr1dg22xss.png)

Auto Scaling Group activity log to achieve maximum state:

![ASG Scale out log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmr347or4am2gwsrsbd0.png)

**Scale in**: Reboot EC2 instance with high CPU utilization to trigger scaling in and achieve the desired value.


![Reboot Induced EC2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4s5a5v2uv9p6i2qgfq7y.png)

Instances terminated to achieve desired state:
![Scaling out](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzs2iric7no01y2a3lq1.png)

![Only EC2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9vnmhflvu74c9as67rf4.png)

Auto Scaling Group activity log to achieve desired state:

![ASG Scale in log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r32i84kp4he2riu3ktyc.png)

## Cleanup:
Remember to stop AWS components to avoid large bills.
```terraform
terraform destroy -auto-approve
```

Congratulations! You have successfully deployed and tested auto scaling for a web server application. In the upcoming module, we will explore Elastic Block Store (EBS) with multi-attach capabilities to further enhance the robustness of our architecture. Happy Coding!

## Resources:
Github Link: https://github.com/chinmayto/terraform-aws-linux-webserver-ec2-alb-autoscaling
EC2 Auto Scaling: https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html