AWS Auto-Scaling Groups

Deploy an Auto Scale Group and Application Load Balancer in AWS

AWS no longer provides a NAT AMI. This exercise is based on utilizing AWS NAT AMIs. Therefore, at this time, this exercise will not work.

Application Load Balancer

This exercise will demonstrate using Terraform to deploy an AWS Auto Scaling Group and an application load balancer.

A simple website that shows EC2 Instance

I have created a bit of code that is a simple HTML page that will display some information about the AWS EC2 instance that is the host server of the web page. When you connect to our load balancer, the load balancer will route the end user to one of the EC2 Instances within the auto-scaling group. The web page display will show you the EC2 server details. When closing the web page and reconnecting to the load balancer, you will most likely see different host details, proving the load balancer connects to different servers.

The web page will look something like the following:

ALB – Application Load Balancer

A bit of information on AWS load balancers first:

Classic Load BalancerLayer 4/7 (HTTP/TCP/SSL traffic)
Network Load BalancerLayer 4 (TLS/TCP/UDP traffic)
Application Load BalancerLayer 7 (HTTP/HTTPS traffic)

Classic Load Balancer (CLB) – AWS recommends that you do not use their classic load balancer. The classic load balancer will eventually be deprecated.

Network Load Balancer (NLB) – The network load balancer works at layers 3 & 4 (network and transport layers). The NLB only cares about TLS, TCP, or UDP traffic and port numbers. The network load balancer just forward requests, whereas the application load balancer examines the contents of the HTTP request header to determine where to route the request. This is the distribution of traffic based on network variables, such as IP address and destination ports.

It is layer 4 (TCP) and below and is not designed to take into consideration anything at the application layer such as content type, cookie data, custom headers, user location, or the application behavior.

Application Load Balancer (ALB) – The application load balancer is the distribution of requests based on multiple variables, from the network layer to the application layer. The ALB can route HTTP and HTTPS traffic based on host or path-based rules. Like an NLB, each Target can be on different ports.

The NLB bases its route decisions solely on network and TCP-layer variables and has no awareness of the application. Generally, a network load balancer will determine “availability” based on the ability of a server to respond to ICMP ping or to complete the three-way TCP handshake correctly. Whereas, an application load balancer goes much deeper and can determine availability based on a successful HTTP GET of a particular page and the verification that the content is as expected based on the input parameters.

ASG – Auto Scaling Group

The auto-scaled web servers can automatically scale up or down according to load on the servers using an Auto-Scaling Group (ASG). However, this is a demonstration and not written for production deployments, so this code does not provide scaling servers based on demand, and instead, the code is written to provide scaling based on desired capacity.

I also have some code about using Terraform to deploy a Load Balanced WordPress Server with ASG and EFS as the persistent storage. That will probably be my next post.

This code can be found in my GitHub Repository


Features

Application Load Balancer – to distribute load amongst more than one server
Auto Scaling Group – with launch template and ELB health check
Simple Web servers – That will display EC2 instance data like Region, ID, and IP address
Using Terraform to deploy infrastructure as code into the AWS cloud

All resources created in this exercise are compliant with an AWS Free Tier Plan

The resources are free only if you don’t leave them running! There is a limit of EC2 hours allowed per month!

You might incur a charge if you leave the Application Load Balancer running for very long. I usually spin this up, prove that it works for about 10 minutes, then run “Terraform Destroy” to ensure I’ve accomplished this exercise for free.

This exercise will perform the following tasks:

  • Create a VPC with two public and two private subnets
  • Create NAT instances instead of a NAT Gateway, security groups and network routing
  • Create an Auto Scaling Group with a launch template
    • The Auto Scaling Group will create EC2 instances running Apache Webpage
      • I created a webpage displaying EC2 ID, EC2 hostname, Region, and private IP address. It will demonstrate which EC2 server you connect to via the load balancer by showing its unique IP address.
  • Create an Application Load Balancer that automatically registers the EC2 servers created by the Auto Scaling Group

Requirements

So let’s get started

In previous exercises, I demonstrated Terraform using modular code. In this exercise, the code will not be modular. All of the code will be placed in one folder.

So first, create your folder to place our code, a folder named “ALB-Website,” perhaps?

Building the VPC

You do not need to create a “Terraform remote state” for this exercise. However, as a best practice, I use an S3 bucket to hold “Terraform’s remote state.” And I will write code that provides output data such that if I need a jump server to troubleshoot an EC2 server in the private network, I can use my modular code to deploy a server I call “the Controller (jump server).”

vpc.tf

variables.tf

In the “variables.tf” file, we usually declar a default value to each variable. With this exercise, though, I’m creating a “terraform.tfvars” file. This allows us to add “terraform.tfvars” into our gitignore. A “gitignore” file controls GIT publishing to GitHub, by providin a list of files that informs GITl not be push the list of files in GitHub. The reason we do this is so that some values are not published to the public. By adding “terraform.tfvars” as part of the list in “.gitignore” file, we are informing GIT not to publish the file “terraform.tfvars” This allows us to safely assign values to our variables in “terraform.tfvars” file, like “my public IP address”, I really don’t want to my public IP address to be available to the public in GITHUB.

Terraform will by default look for a “terraform.tfvars” file in your folder. When declared variables do not include a default assignment (as is the case above, the variables are not assigned a default value.”

terraform.tfvars

security_groups.tf

bootstrap_nat.sh

Lines 24-33, queries AWS API to retrieve the latest image for an EC2 instance that is configured as a NAT Server. You will notice that I was getting the instance manually and hardcoding the AMI ID (using the method in the following paragraph). Then I realized that all I have to do is query AWS API for the latest AWS NAT instance AMI ID.

Note: How to manually get an AMI Instance ID for a AWS NAT server:
To find a “NAT AMI” for your AWS region, open the AWS Management Console. Go to the EC2 services. Select the AWS region of your choice in the Menu Bar. In the left-hand panel, find “AMIs” and click on the Amazon Machine Images (AMIs) panel. Select “Public Images” and filter on “amzn-ami-vpc-nat,” then find the most recent creation date and copy the AMI ID to use as your NAT AMI image for your selected region.


Creating the Auto-Scaling Group

One of the early decisions on using an Auto-Scaling Group (ASG) is how the ASG will determine “Load” to scale up or scale down according to a load on our application. I’m not going to write about the different health checks used to determine “Load” on our application in this exercise. That will be a post to write at a later date. Suffice it to say that I’ve selected “Elastic Load Balancing health checks” to check whether the load balancer reports an instance is healthy, confirming whether the instance is available to handle requests to our website.

Building the Auto Scaling Group Code

First, we need to declare to Terraform that we are creating an ASG resource and give our ASG a name.

Then declare the health check and parameters of our health check.

You’ll notice we’ve stated the health check type as “ELB” and provided the ALB target group name and ARN (find the ALB target group Terraform code below). We have also selected 300 seconds (5 minutes) for a grace period. And finally, “force_delete = true”. We are telling AWS that if any of our website servers are unhealthy for more than 5 minutes, delete the server, which then causes ASG to build another server to meet our desired capacity.

Our next step is to declare ASG sizing by stating the minimum, maximum, and desired number of servers.

Next and perhaps the most essential part of our ASG code is to declare if we are using a launch configuration or a launch template. We are going to use a launch template. Amazon Web Services recommends the use of launch templates but still supports (at the time of this writing) launch configurations.

We need to inform Terraform which launch template to use. We’ll use the “latest” parameter in this case as we have only one template version. Specifying a different version of our template is useful for Blue/Green deployments as an example.

Now, let’s put all of this together.

asg.tf

Building the Launch Template

I always throw in a bit of code to obtain the latest data for an AMI (in this case, Ubuntu vs. Amazon Linux). This sets up the ability for Terraform to query AWS API for data regarding the latest regional AMI image to use in a Launch template.

We first declare the type of resource and the resource name.

Then we declare the image ID, instance type, lifecycle rule, and security groups. Note the usage of “data.aws_ssm_parameter” obtaining the AMI by telling Terraform to query AWS API for ssm_parameters for AMI ID. The EC2 key pair is not required in the launch template; however, I always include the EC2 key name so that I can SSH into the servers in case of the need to troubleshoot the deployment.

We, of course, need to configure our servers

We’ll be using launch templates to configure our servers. For ASG the configuration code is required to be encrypted. So in this example, we add a line that tells Terraform to encode with Base64 and render a template file with the following line:

Next up is the reference to a template file. We are using a file with an extension “TPL.” A bootstrap.tpl appears the same as creating a simple shell command like bootstrap.sh file. The contents look the same, and the difference is how Terraform handles the file in a launch template. Using the extension “.tpl” will allow us to pass external variables into the script (which I will demonstrate in my next exercise).

We need an IAM policy for our server

I have created some code to create an HTML page for our auto-scaled Apache Web Servers. The code will allow our Web server to show information about the host server, specifically the EC2 details. The launched server web page is just a few lines that display EC2 attributes. The HTML will show the region, the AMI ID, the server’s hostname, and the IP address. Seeing host server information will demonstrate which server our browser has been connected to via our load balancer.

So first, let’s create the IAM policy for our servers that enable our servers to describe the EC2 host details. I have two JSON files. One for creating a role that allows our servers to assume a role that will enable our EC2 to use the service “ec2.amazonaws.com”. The second JSON file creates the IAM policy, allowing the action to “ec2:Describe*”. Our Terraform code below creates the role, the policy, and the profile of our EC2 servers.

Now let’s put this all together (asg.tf)


Create the Application Load Balancer (ALB)

To build our application load balancer, we need to create several key elements:

  • ALB
  • ALB listener
  • Target

ALB

The Terraform code to create an Application Load Balancer requires several key components:

  • Resource type and name
    • resource “aws_lb” “website-alb”
  • Type of load balancer(the load balancer will be an application load balancer in this exercise)
    • “load_balancer_type = “application”
  • Name of the Load Balancer in AWS, this must be a unique name so we have a random string generator in our code. We’ll append the string to the ALB name
  • Public or Private load balancer(internal vs. external load balancer)
    • “internal = false” (so it will be publicly addressable from the internet)
  • Subnets to locate our load balancer(since this will be a public-facing load balancer, Terraform will place the load balancer into our public subnets)
    • “subnets = [aws_subnet.public-1.id, aws_subnet.public-2.id]”
  • Declare the security group or groups for our load balancer

Alb.tf

ALB Listener

Key components of our listener:

  • resource type and name
    • resource “aws_lb_listener” “website-alb-listener”
  • Make reference to which ALB is using this “listener” setup
    • “load_balancer_arn = aws_lb.website-alb.arn”
  • Specify port and protocol
  • Default action (forward, redirect, fixed response, and/or authenticate)
    • Some of these actions can be combined, for example, forward and authenticate. This exercise will be using a simple webpage, so we will be simply forwarding it to our Web servers
    • We will use the action “forward” and set stickiness to false. Stickiness is when our ALB sends clients to the same server in the auto-scaling group in case the client gets disconnected. Since this is a simple demonstration, we don’t care which server a user is connected to when hitting refresh or reconnecting to our servers.

alb_listener.tf

The ALB target

When you create a target group, you specify its target type, which determines the type of target you specify when registering targets with this target group. After you create a target group, you cannot change its target type.

The following are the possible target types:

  • Instance (The targets are specified by instance ID)
  • IP (The targets are IP addresses)
  • Lambda (The target is a Lambda function)
  • Use the attachment function (in our case to an Auto-scaling Group ARN)

We could just put a couple of servers placed in one or more availability zones and list the Instance IDs or the IP addresses of those servers. Or, we could simply list the CIDR blocks of one or more private subnets, and the target would be any server in the private subnet(s). You could do this, but in our exercise, we want to use an auto-scaling group (ASG) and have the ALB health checks work with the ASG to rebuild servers if they become unhealthy. Therefore we do not want to point at IP addresses or instance IDs.

alb_target.tf

output.tf

Deploy our Resources using Terraform

Be sure to edit the variables in terraform.tfvars (currently, it has bogus values)

If you are placing this into any other region than us-west-1, you will have to change the AMI ID for the NAT instances in the file “vpc.tf”.

In your terminal, go to the VPC folder and execute the following commands:

  1. Terraform init
  2. terraform validate
  3. Terraform apply

Once the deployment is successful, the terminal will output something like the following output:

Copy the lb_dns_name, without the quotes, and paste the DNS name into any browser. If you have followed along and placed all of the code correctly, you should see something like the following:

Screen Shot

Notice Sometimes servers in an ASG take a few minutes to configure. Wait a couple of minutes if you get an error from our website and try again.

Open up your AWS Management Console, and go to the EC2 dashboard. Configure your EC2 dashboard to show tag columns with a tag value “Name.” A great way to identify your resources is by using TAGS!!

If you have configured the dashboard to display the tag column "Names" in your EC2 dashboard, you should quickly be able to see TWO NAT instances with the tag name "Test-NAT1" and "Test-NAT2" and TWO servers with the Tag Name "Website_ASG".

As an experiment, perhaps you would like to expand the number of Web servers. We can manually expand the number of desired capacity, and the Auto Scaling Group will automatically scale up or down the number of servers based on your command to change desired capacity.

The AWS CLI command is as follows:

Where ASG_Name in the command line above will be the terminals output of lb_dns_name (without the quotes, of course). If you successfully execute the command line in your terminal, you should eventually see in the EC2 dashboard four instances with the tag name “Website_ASG.” Demonstrating our ability to manually change the number of servers to four instead of two.

Once completed with this exercise, feel free to remove all resources by issuing the following command in the terminal:

  • terraform destroy

This is not for production!

All public websites should have an application firewall in between the Web Server and its internet connection, this exercise doesn’t create the application firewall. So do not use this configuration for production

All websites should have monitoring and a method to scrape log events to detect and alert for potential problems with the deployment.

This exercise uses resources compatible with the AWS Free Tier plan. It does not have sufficient compute sizing to support a production workload.

It is a good idea to remove All resources when you have completed this exercise so as not to incur costs

Author: Joseph OMara

Solution Architect for Information Technology. Hobbies include amateur photography, backpacking, camping, hiking, and travel

One thought on “AWS Auto-Scaling Groups”

Leave a ReplyCancel reply

Discover more from Joseph O'Mara

Subscribe now to keep reading and get access to the full archive.

Continue reading

Exit mobile version
%%footer%%