Introduction
In the first Infrastructure as Code (IaC) tutorial using Terraform, we explored how to install the CLI and launch an EC2 instance with external input variables. The Terraform CLI version was v0.12.29 then, but the latest version at the time of writing is v.1.2.3; hence, some syntaxes will be deprecated. This tutorial will launch an Application Load Balancer (ALB) and Auto Scaling Group (ASG) in a VPC and security groups in an AWS environment. This example is adopted from the book Terraform: Up & Running, 2nd Edition by O’Reilly Media. Note that some resource syntaxes here are updated and may differ slightly from the book.
Prerequisites
- Install any Terraform extension into the code editor for highlighting and auto-completion on *.tf files.
- An active AWS account to test and deploy this Iac script using aws configure and Terraform CLI
- A keen eye to google for solutions after encountering terraform apply errors because, by the time you attempt this Iac script, some syntax might be deprecated.
- Last, REMEMBER tearing down all resources (terraform destroy) deployed in AWS because resources such as Application Load Balancer and EC2 instances in the Auto Scaling group are not free.
1. Codes
1.1. Launch Configuration and Auto Scaling Group
A launch configuration is a template that an Auto Scaling group (ASG) uses to launch EC2 instances. We will specify the Amazon Machine Image (AMI), instance type, key pair, and security group(s). For example, if an instance is terminated, ASG will launch a new instance and all future instances based on these configurations. Next, we define the minimum and maximum size of kept-alive EC2 instances in the ASG. The user_data installs Apache (httpd) and creates an index.html file, while the rest tags the EC2 instances to a security group and launches the ASG in a virtual private cloud (VPC).
# Launch configuration
resource "aws_launch_configuration" "example" {
image_id = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
security_groups = [aws_security_group.instance.id]
# Create a simple "Hello, World" HTML and install Apache (listen port 8080)
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p ${var.server_port} &
EOF
# Required when using launch configuration with auto scaling group
lifecycle {
create_before_destroy = true
}
}
# Auto Scaling group
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnets.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
# Minimum and maximum size of Auto Scaling Group
min_size = 2
max_size = 10
# Name the ASG (optional)
tag {
key = "Name"
value = "terraform-asg-example"
propagate_at_launch = true
}
}
1.2. Load Balancer and Target Group
Here, we configure an application-based load balancer (ALB) and associate it with a subnet and security group. The load balancer will listen on HTTP port 80 and return a 404 Page not found error on status_code = 404. The ALB will then route all requests (“/”) to a target group with conditions such as healthy and unhealthy instances threshold.
# Application Load Balancer
resource "aws_lb" "example" {
name = "terraform-asg-example"
load_balancer_type = "application"
subnets = data.aws_subnets.default.ids
security_groups = [aws_security_group.alb.id]
}
# Listening port for Application Load Balancer
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = 80
protocol = "HTTP"
# Default return 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
# Listener rule for Application Load Balancer
resource "aws_lb_listener_rule" "asg" {
listener_arn = aws_lb_listener.http.arn
priority = 100
condition {
path_pattern {
values = ["*"]
}
}
# Distribute requests to 1 or more target groups
action {
type = "forward"
target_group_arn = aws_lb_target_group.asg.arn
}
}
# Target group 1 for Application Load Balancer
resource "aws_lb_target_group" "asg" {
name = var.alb_name
port = var.server_port
protocol = "HTTP"
vpc_id = data.aws_vpc.default.id
health_check {
path = "/"
protocol = "HTTP"
matcher = "200"
interval = 15
timeout = 3
# Number of consecutive health checks before consider target as unhealthy
healthy_threshold = 2
unhealthy_threshold = 2
}
}
1.3. Security Groups
The EC2 instances will exchange traffic with the target group via Port 8080 (var.server_port) only and deny all others. The Application Load Balancer (ALB) listens to incoming traffic at Port 80 and imposes an any-to-any rule on outbound traffic.
# Security group for EC2 instances
resource "aws_security_group" "instance" {
name = var.security_group_name
# Destination:TCP/8080 | Source:Any
ingress {
from_port = var.server_port
to_port = var.server_port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Security group for Application Load Balancer
resource "aws_security_group" "alb" {
name = var.alb_security_group_name
# Allow inbound
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow outbound
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
1.4. VPC and Subnets
We will use the default VPC and subnet for the region to keep things simple. Lastly, we associate the default subnet to the VPC.
# Default VPCs
data "aws_vpc" "default" {
default = true
}
# Default subnet for VPC
data "aws_subnets" "default" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}
2. Putting All Together
2.1. main.tf
Below is the full Iac Terraform script (main.tf). The region in provider “aws” will overwrite the default region name settings in aws configure
# Choose a low-cost region near to your Terraform CLI server
provider "aws" {
region = "us-east-2"
}
# Launch configuration
resource "aws_launch_configuration" "example" {
image_id = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
security_groups = [aws_security_group.instance.id]
# Create a simple "Hello, World" HTML and install Apache (listen port 8080)
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p ${var.server_port} &
EOF
# Required when using launch configuration with auto scaling group
lifecycle {
create_before_destroy = true
}
}
# Auto Scaling group
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnets.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
# Minimum and maximum size of Auto Scaling Group
min_size = 2
max_size = 10
# Name the ASG (optional)
tag {
key = "Name"
value = "terraform-asg-example"
propagate_at_launch = true
}
}
# Application Load Balancer
resource "aws_lb" "example" {
name = "terraform-asg-example"
load_balancer_type = "application"
subnets = data.aws_subnets.default.ids
security_groups = [aws_security_group.alb.id]
}
# Listening port for Application Load Balancer
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = 80
protocol = "HTTP"
# Default return 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
# Listener rule for Application Load Balancer
resource "aws_lb_listener_rule" "asg" {
listener_arn = aws_lb_listener.http.arn
priority = 100
condition {
path_pattern {
values = ["*"]
}
}
# Distribute requests to 1 or more target groups
action {
type = "forward"
target_group_arn = aws_lb_target_group.asg.arn
}
}
# Target group 1 for Application Load Balancer
resource "aws_lb_target_group" "asg" {
name = var.alb_name
port = var.server_port
protocol = "HTTP"
vpc_id = data.aws_vpc.default.id
health_check {
path = "/"
protocol = "HTTP"
matcher = "200"
interval = 15
timeout = 3
# Number of consecutive health checks before consider target as unhealthy
healthy_threshold = 2
unhealthy_threshold = 2
}
}
# Security group for EC2 instances
resource "aws_security_group" "instance" {
name = var.security_group_name
# Destination:TCP/8080 | Source:Any
ingress {
from_port = var.server_port
to_port = var.server_port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Security group for Application Load Balancer
resource "aws_security_group" "alb" {
name = var.alb_security_group_name
# Allow inbound
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow outbound
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Default VPCs
data "aws_vpc" "default" {
default = true
}
# Default subnet for VPC
data "aws_subnets" "default" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}
2.2. variables.tf
Port 8080 is repeated in both the security group and User Data configuration. The (variables.tf) enables us to make changes to the port easily and quickly and gives us an overall view of configuration parameters that might otherwise be clustered among other resource codes in main.tf
variable "server_port" {
description = "HTTP request non-port 80"
type = number
default = 8080
}
variable "security_group_name" {
description = "Name of security group"
type = string
default = "terraform-example-instance1"
}
variable "alb_name" {
description = "ALB name"
type = string
default = "ALB-up-and-running"
}
variable "alb_security_group_name" {
description = "ALB security group"
type = string
default = "security-group-up-and-running"
}
variable "vpc_id" {
description = "VPC name"
type = string
default = "VPC-up-and-running"
}
3. AWS Console Screenshots after “terraform apply”
Conclusion
It has been almost two years since we last checked in at Terraform. Many syntaxes such as resource aws_xxx have since been deprecated, but one can easily google for the up-to-date syntax on the web. IMO, Kubernetes is Terraform’s direct competitor, and there is strong demand for Kubernetes enablers in the IT industry now. Some companies are already starting to migrate from Terraform to Kubernetes. These days, if you are hunting for a DevOps role, 8 out of 10, you will be tested on Iac using Terraform. Therefore, this script that deploys AWS ASG, ALB, Target group, Security groups, VPC, etc., will give you a little head start.