Deploying a Highly Available Web App on AWS Using Terraform

Deploying a Highly Available Web App on AWS Using Terraform

# terrafo
Deploying a Highly Available Web App on AWS Using Terraformmarvintowett

Today we will be looking at deploying a cluster of web servers. To make all this possible we are...

Today we will be looking at deploying a cluster of web servers. To make all this possible we are going to employ the use of the following AWS technologies:

  1. Auto Scaling Group (ASG)
  2. Application Load Balancer
  3. Launch Configuration

Auto Scaling Group (ASG)

Auto Scaling Group handles:

  • Launching a cluster of EC2 Instances
  • Monitoring health of each instance
  • Replacing failed instances
  • Adjusting Size of each of the cluster in response to load

Creating an ASG:
In order to create an ASG you must first create a launch configuration that specifies how to configure each EC2 instance in the ASG

This Terraform configuration creates an Auto Scaling group with a launch configuration that sets up a simple HTTP server using BusyBox. The server listens on a port defined by the server_port variable, which defaults to 8080. The security group allows incoming traffic on the specified port from any IP address.

resource "aws_launch_configuration" "example" {
    image_id = "ami-0735c191cf914754d"
    instance_type = "t2.micro"
    security_groups = [aws_security_group.instance.id]

    user_data = <<-EOF
                    #!/bin/bash
                    echo "Hello, World" > index.html
                    nohup busybox httpd -f -p ${var.server_port} &
                EOF

    // Ensure that the new launch configuration is created before the old one is destroyed            
    lifecycle {
      create_before_destroy = true
    }

}

Enter fullscreen mode Exit fullscreen mode

We added lifecycle block. this ensures that when updating/creating a new EC2 instance, Terraform will first create the new instance update then delete the old instance

For this ASG to work we need to add another value subnet_ids.
This parameter specifies which subnet the EC2 instances should be deployed to the ASG.

Each subnet lives in an isolated AWS Availability Zone (AWS AZ) to ensure your instances keep running even if some of the data centres have an outage

Data Source:

In order to get the ips of each subnet we are going to use data sources.
A data sources represents a piece of read-only information that is fetched from the provider whenever you run Terraform.
Data source is used to simply query information from the providers IP.

The Syntax for using a data source:

data "<PROVIDER_NAME>_<TYPE>" "<NAME>" {
 [CONFIG ...]
}

Enter fullscreen mode Exit fullscreen mode

We will go ahead and look up data for our Default VPC and then combine that with another data source aws_subnets to look upthe subnets within the VPC.

// Data sources to get the default VPC
data "aws_vpc" "default" {
    default = true
}

// Get the subnets in the default VPC
data "aws_subnets" "default" {
    filter {
        name = "vpc-id"
        values = [data.aws_vpc.default.id]
    }
}

Enter fullscreen mode Exit fullscreen mode

Now we can pull the Ids out of the aws_subnets required by the ASG.

// Create an Auto Scaling group that uses the launch configuration
resource "aws_autoscaling_group" "example" {
    launch_configuration = aws_launch_configuration.example.name
    vpc_zone_identifier = data.aws_subnets.default.ids

    min_size = 2
    max_size = 10

    tag{
        key = "Name"
        value = "mk-terraform-instance"
        propagate_at_launch = true
    }

}

Enter fullscreen mode Exit fullscreen mode

Deploying Load Balancer

A Load balance is a server that distributes traffic across your server while using the load balancer's IP.
we have 3 types of Load balancers:

  1. Application Load Balancers(ALB): Best suited for load balancing of HTTP and HTTPS request
  2. Network Load Balancers(NLB): Best suited for load balancing of TCP, UDP and TLS traffic.
  3. Classic Load Balancer(CLB): CLB predates ALB and NLB, it loab balance's for HTTP, HTTPS, TCP and UDP.However it has fewer features than ALB and NLB.

For this example since we are deploying a simple server we will be using ALB Application Load Balancer.
To create a load balance we will use the aws_lb resource.

resource "aws_lb" "example" {
    name = "terraform-asg-example"
    load_balancer_type = "application"
    subnets = data.aws_subnets.default.ids
}

Enter fullscreen mode Exit fullscreen mode

Our load balancer is configured to use all subnets.

Now we need to configure listeners to the loab balancer using the aws_lb_listener resource.

resource "aws_lb_listener" "http" {
    load_balancer_arn = aws_lb.example.arn
    port = var.http_port
    protocol = "HTTP"

    // Set up a fixed response for any requests that do not match the target group
    default_action {
        type = "fixed-response"
        fixed_response {
            content_type = "text/plain"
            message_body = "${var.status_code}: Page Not Found"
            status_code = var.status_code
        }

    }

}

Enter fullscreen mode Exit fullscreen mode

The listener above is configured to listen to default port 80, use the HTTP protocol and send 404 page for request that don't match listeners rules.

Now we will need to create a new security group for our ASG since AWS does not allow any incoming or outgoing traffic by default.

resource "aws_security_group" "alb" {
    name = "mk-terraform-alb-security-group"

    // Allow incoming traffic on the HTTP port from any IP address
    ingress{
        from_port = var.http_port
        to_port = var.http_port
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    // Allow all outbound traffic from the load balancer
    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

Enter fullscreen mode Exit fullscreen mode

The next Step is now to pass this security group in the
security_group arguments of our load balance.

// Create an Application Load Balancer to distribute traffic to the instances in the Auto Scaling group
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]

}

Enter fullscreen mode Exit fullscreen mode

Next we will have to create a target group for our ASG using the aws_b_target_group resource. a target group is a part of the ASG that receives request from the load balancer. it also performs health checks on the server and sends request to healthy nodes only.

// Create a target group for the Auto Scaling group instances
resource "aws_lb_target_group" "asg" {
    name = "terraform-asg-example"
    port = var.server_port
    protocol = "HTTP"
    vpc_id = data.aws_vpc.default.id

    health_check {
        path = "/"
        protocol = "HTTP"
        matcher = "200"
        interval = 15
        timeout = 3
        healthy_threshold = 2
        unhealthy_threshold = 2
    }

}

Enter fullscreen mode Exit fullscreen mode

The target groups sends request to EC2 Instance by setting target_group_arns in the aws_autoscaling_group resource.

// Create an Auto Scaling group that uses the launch configuration
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"

    min_size = 2
    max_size = 10

    tag{
        key = "Name"
        value = "mk-terraform-instance"
        propagate_at_launch = true
    }

}

Enter fullscreen mode Exit fullscreen mode

Note: we updated the health_check_type to "ELB" health check as it is more robust and instructs ASG to use the target group health check to determine whether an instance is healthy.

Finally, we create a listener_rule. This is the last part of the ASG we need to configure. The Listener Rule takes a request that's come in and send's those that match specific paths or hostnames to specific target_group.

// Create a listener rule that forwards traffic to the target group for the Auto Scaling group instances
resource "aws_lb_listener_rule" "asg" {
    listener_arn = aws_lb_listener.http.arn
    priority = 100

    // Forward traffic to the target group for the Auto Scaling group instances if the path is "/"
    condition {
        path_pattern {
            values = ["/"]
        }
    }

    // Forward traffic to the target group for the Auto Scaling group instances
    action {
        type = "forward"
        target_group_arn = aws_lb_target_group.asg.arn
    }
}

Enter fullscreen mode Exit fullscreen mode