Deploying an AWS Autoscaling Group with Terraform

In this walkthrough I will be showing how to deploy a AWS Autoscaling group, a External Load Balancer and EC2s with Apache installed with Terraform. Your environment will need Terraform installed. I am using Cloud9 so Terraform is already preloaded.

Let’s get started

Step 1.

Make a directory for your Terraform files and then create a main.tf, variables.tf, output.tf, and a .sh file for the Apache server.

touch main.tf touch variables.tf touch output.tf touch yourbash.sh

Step 2.

Let’s initialize our directory

terraform init

Then write our code in the main.tf
First we will start with our VPC

resource "aws_vpc"  "vpc" {
cidr_block = var.vpc_cidr

tags  = {
Name = local.vpc_name
}
}

resource "aws_internet_gateway"  "internet_gateway" {
vpc_id = aws_vpc.vpc.id

tags  = {
Name = local.internet_gateway_name
}
}

resource "aws_subnet"  "public_subnet" {
count = 2
vpc_id = aws_vpc.vpc.id
cidr_block  =  var.public_subnet_cidr[count.index]
map_public_ip_on_launch = true
availability_zone  =  var.az_names[count.index]

tags = {
Name = join("-", [local.public_subnet_name, var.az_names[count.index]])
}
}

resource "aws_subnet"  "private_subnet" {
count = 2
vpc_id = aws_vpc.vpc.id
cidr_block  =  var.private_subnet_cidr[count.index]
availability_zone = var.az_names[count.index]

tags = {
Name = join("-", [local.private_subnet_name, var.az_names[count.index]])
}
}

resource "aws_route_table"  "public_route_table" {
vpc_id = aws_vpc.vpc.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.internet_gateway.id
}

tags = {
Name = local.public_route_table_name
}
}

resource "aws_eip"  "elastic_ip" {
tags = {
Name = local.elastic_ip_name
}
}

resource "aws_nat_gateway"  "nat_gateway" {
allocation_id = aws_eip.elastic_ip.id
connectivity_type  =  "public"
subnet_id = aws_subnet.public_subnet[0].id

tags  = {
Name = local.nat_gateway_name
}

depends_on = [aws_internet_gateway.internet_gateway]
}

resource "aws_route_table"  "private_route_table" {
vpc_id = aws_vpc.vpc.id

route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_gateway.id
}

tags = {
Name = local.private_route_table_name
}
}

resource "aws_route_table_association"  "public_rt_assoc" {
count = 2
subnet_id = aws_subnet.public_subnet[count.index].id
route_table_id  = aws_route_table.public_route_table.id
}

resource "aws_route_table_association"  "private_rt_assoc" {
count = 2
subnet_id = aws_subnet.private_subnet[count.index].id
route_table_id  = aws_route_table.private_route_table.id
}

then we will create our Security Group Resources

# Security Group Resources

resource "aws_security_group"  "alb_security_group" {
name = local.alb_security_group_name
description = "ALB Security Group"
vpc_id = aws_vpc.vpc.id

ingress {
description = "HTTP from Internet"
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 = local.alb_security_group_name
}
}

resource "aws_security_group"  "asg_security_group" {
name = local.asg_security_group_name
description = "ASG Security Group"
vpc_id = aws_vpc.vpc.id

ingress {
description = "HTTP from ALB"
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb_security_group.id]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = local.asg_security_group_name
}
}

next lets create the launch template and the auto scaling group resources

# Launch Template and ASG Resources

resource "aws_launch_template"  "launch_template" {
name = local.launch_template_name
image_id = var.ami
instance_type = var.instance_type

network_interfaces {
device_index = 0
security_groups = [aws_security_group.asg_security_group.id]
}
tag_specifications {
resource_type = "instance"

tags = {
Name = local.launch_template_ec2_name
}
}

user_data = filebase64("${path.module}/install-apache.sh")
}

resource "aws_autoscaling_group"  "auto_scaling_group" {
desired_capacity = var.desired_capacity
max_size = var.max_size
min_size = var.min_size
vpc_zone_identifier = [for i in aws_subnet.private_subnet[*] : i.id]
target_group_arns = [aws_lb_target_group.target_group.arn]

launch_template {
id = aws_launch_template.launch_template.id
version = aws_launch_template.launch_template.latest_version
}
}

next the Application Load Balancer resources

# Application Load Balancer Resources

resource "aws_lb"  "alb" {
name = local.alb_name
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_security_group.id]
subnets = [for i in aws_subnet.public_subnet : i.id]
}

resource "aws_lb_target_group"  "target_group" {
name = local.target_group_name
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.vpc.id

health_check {
path = "/"
matcher = 200
}
}

resource "aws_lb_listener"  "alb_listener" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.target_group.arn
}
}

Those will all sit in our main.tf file….CTRL-S :)

Step 3.

Lets create the variable.tf file

This creates the local values

# Terraform Variables

# Local Values
locals {
vpc_name = "wk21-vnet"
internet_gateway_name = "wk21-internet-gateway"
public_subnet_name = "wk21-public-subnet"
private_subnet_name = "wk21-private-subnet"
public_route_table_name = "wk21-public-route-table"
private_route_table_name = "wk21-private-route-table"
elastic_ip_name = "wk21-nat-elastic-ip"
nat_gateway_name = "wk21-nat-gateway"
alb_security_group_name = "wk21-alb-security-group"
asg_security_group_name = "wk21-asg-security-group"
launch_template_name = "wk21-launch-template"
launch_template_ec2_name = "wk21-asg-ec2"
alb_name = "wk21-external-alb"
target_group_name = "wk21-alb-target-group"
}

next lets create the VPC variables

# VPC Variables

variable "vpc_cidr" {
description = "VPC cidr block"
type = string
default = "172.16.0.0/16"
}

variable "az_names" {
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}

variable "public_subnet_cidr" {
description = "Public Subnet cidr block"
type = list(string)
default = ["172.16.0.0/24", "172.16.1.0/24"]
}

variable "private_subnet_cidr" {
description = "Private Subnet cidr block"
type = list(string)
default = ["172.16.10.0/24", "172.16.11.0/24"]
}

next lets create the launch template and auto scaling group variables

# Launch Template and ASG Variables

variable "ami" {
description = "ami id"
type = string
default = "ami-006dcf34c09e50022"
}

variable "aws_region" {
description = "AWS region name"
type = string
default = "us-east-1"
}

variable "server_port" {
description = "The port the web server will be listening"
type = number
default = 8080
}

variable "elb_port" {
description = "The port the elb will be listening"
type = number
default = 80
}

variable "instance_type" {
description = "The type of EC2 Instances to run (e.g. t2.micro)"
type = string
default = "t2.micro"
}

variable "min_size" {
description = "The minimum number of EC2 Instances in the ASG"
type = number
default = 2
}

variable "max_size" {
description = "The maximum number of EC2 Instances in the ASG"
type = number
default = 5
}

variable "desired_capacity" {
description = "The desired number of EC2 Instances in the ASG"
type = number
default = 3
}

Step 4.

Lets create the output file. This will show us our load balancer public url

output  "alb_public_url" {
description = "Public URL"
value = aws_lb.alb.dns_name
}

Step 5.

Lets create the bash script to install Apache

#!/bin/bash

# Install Apache on Ubuntu
sudo yum check-update
sudo yum -y update
# apache installation, enabling and status check
sudo yum -y install httpd
sudo systemctl start httpd
sudo systemctl enable httpd
sudo systemctl status httpd | grep Active
# firewall installation, start and status check
sudo yum install firewalld
sudo systemctl start firewalld
sudo systemctl status firewalld | grep Active
# adding http services
sudo firewall-cmd — permanent — add-service=http
# reloading the firewall
sudo firewall-cmd — reload


sudo cat > /var/www/html/index.html << EOF
<html>
<head>
<title> LUIT </title>
</head>
<body>
<p> TEAM BLACK TEAM
</body>
</html>
EOF

Step 6.

Now do a terraform plan

Everything should be good. Fix any errors that may pop up.

Now lets do a terraform apply -auto-approve

Notice the alb_public_url pops up :)

Step 7.

Lets verify that everything was created.

Instances are up

Subnets are up

Security groups are up

Auto scaling group was created with our specifications. Max 5, Min 2, Desired 3

Lets terminate a instance and see what happens

Our instance is now gone. Lets check the log in our ASG. Now we will see that a Ec2 was taken out of service in response to an EC2 health check indicating it has been terminate or stopped. AND that a new was deployed.

I terminated two instances for this so we were down toonly 1. But as you can see two more popped up.

Which indicates our ASG is working correctly.

Now lets check to see that our external application load balancer exists.

With two availability zones.

Lets the VPC.

VPC created.

Now lets take a look at our network

4 subnets, 2 public, 2 private, private and public routing table, IGW and NGW

Now lets make sure our apache server is doing its job.

And it is. Our External ALB is accessible from anywhere in world.

Now lets burn it down with terraform destroy -auto-approve
Be careful with -auto-approve. This bypasses the question do you really want to destroy this? In this case I use it because I know I don’t want what I just stood up.

And what we just deployed is now gone

And that is it. Thank you for sticking around I hope you enjoyed.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics