Managing external traffic with application load balancing
Application load balancing is a concept that focuses on routing and balancing traffic based on the seventh layer of the OSI model, known as the application layer. In practice, for web applications this usually means routing based on the URL path or query parameters. For example, a request to /app/account/balance
will be routed to a node running the application and serve the account balance while /html/about-us
will be routed to a static GitHub Pages website.
These destinations are referred to as targets and are part of a target group, which can contain one or many targets. Targets can be the final destination for a request and have an application running on the node directly serving the response. In cases where orchestration tools like Nomad or Kubernetes are used, these targets can be nodes running several services, each on a different port.
The Load Balancing with NGINX tutorial shows you how to configure one instance of Nginx to balance traffic between three web application instances, each running on a different node. This tutorial extends that knowledge and teaches you how to add an external application load balancer (ALB) to both allow Internet traffic to your internal services and further balance traffic to different instances of Nginx. In this way, the ALB is responsible for forwarding traffic based on which application service is being requested, and Nginx is responsible for balancing traffic between the multiple instances of the same application service.
Though Nginx was chosen as the internal load balancer for this scenario, other load balancing applications like Fabio, HAProxy, and Traefik can also be used.
In this tutorial, you will create a Nomad cluster, deploy an example web application, deploy Nginx to balance requests to the webapp, and create an external ALB to forward traffic to Nginx. You will then add custom rules to the ALB allowing it to forward requests to the different webapp services based on the URL path parameter.
Architecture overview
Infrastructure diagram
The cluster created in this tutorial consists of three Nomad server nodes and five Nomad client nodes. The Nomad clients are split into two logical datacenters: three of them are in dc1
and two are in dc2
. Additionally, the Nomad clients contain custom metadata identifying which hypothetical services should be run on them: the clients in dc1
have a metadata tag of api
for the API service while those in dc2
have a payments
tag for the payments service. A real-world explanation of splitting these services based on client attributes like this might be that clients with the payments
tag have faster storage and higher memory resources enabling quicker processing of payments.
Application diagram
The ALB listens for user requests on port 80
and forwards them to the Nomad clients on port 8080
, which is where the Nginx service is listening. Nginx then forwards the request to an instance of the web application running on one of the clients in the same Nomad datacenter as it. The port for the web application is a dynamic port in the range of 20000
to 32000
and is allocated by the Nomad scheduler.
Prerequisites
For this tutorial, you will need:
- Packer 1.7.7 or later installed locally
- Terraform 1.0.5 or later installed locally
- Nomad 1.1.5 or later installed locally
- An AWS account with credentials set as local environment variables and an AWS keypair
Note
This tutorial creates AWS resources that may not qualify as part of the AWS free tier. Be sure to follow the Cleanup process at the end so you don't incur any additional unnecessary charges.
Clone the example repository
The example repository contains configuration files for creating a Nomad cluster on AWS. It uses Consul for the initial setup of the Nomad servers and clients, enables Access Control Lists for Consul and Nomad, and creates an elastic load balancer for easier access to Consul and Nomad.
Clone the example repository.
Navigate to the cloned repository folder.
Check out the v0.1
tag of the repository as a local branch named nomad-alb
.
Review repository contents
The shared
top level directory contains configuration files and scripts for building the Amazon Machine Image (AMI), the respective ACL policy files for Consul and Nomad, and the agent configuration files for running Consul and Nomad on the AWS instances.
The shared/config
directory contains configuration files for the Consul and Nomad agents. Those configuration files contain capitalized placeholder strings that get replaced with proper values during the provisioning process. For example, in the nomad_client.hcl
agent file, this includes the datacenter, values for the Consul token, and other custom metadata attributes.
1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728
The aws
top level directory contains the Packer build file used to create and publish the AMI to AWS as well as the Terraform configurations and additional files necessary for the infrastructure provisioning process. The post-setup.sh
script here retrieves the Nomad token from the Consul KV store once the cluster is up and running. Lastly the nomad
folder contains the Nomad job spec files for the demo web application and Nginx.
The user-data-client.sh
script replaces the placeholder strings in the Nomad client agent file referenced above with actual values based on the AWS metadata tags for the instance.
Note that the nomad_consul_token_secret
value will be placed there by Terraform during the provisioning process as it renders the file.
1 2 3 4 5 6 7 8 9 101112131415161718192021222324252627
The user-data-server.sh
script handles the bootstrapping of the ACL systems for both Consul and Nomad and saves the Nomad user token to the Consul KV store for temporary storage. The post-setup.sh
script deletes the token from Consul KV once it's been retrieved and saved locally.
1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728293031323334
The aws/nomad/webapp.nomad
job spec file runs two services of the demo web application: one acting as the api and the other acting as the payments service. These services are configured to run only on the instances that have the corresponding meta.service-client
attribute of the Nomad client, which was placed in the agent file by the user-data-client.sh
script mentioned above. This is specified in the constraint
attribute.
1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526272829303132333435363738394041
Finally, the aws/nomad/nginx.nomad
job spec file runs two instances of Nginx to balance traffic between the different clients associated with the simulated api and payments services – one Nginx for each respective service. It retrieves the IP addresses of the clients running the service by querying Consul. The service name used in the template nginx configuration file matches the name of the service defined in the aws/nomad/webapp.nomad
file.
The nginx services also use the constraint
attribute mentioned above to run on specific clients.
1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
Create the Nomad cluster
Build the AWS Machine Image (AMI)
Navigate to the aws
directory.
Be sure to set your AWS environment variables as Packer uses these to build the image and register the AMI in AWS.
Initialize Packer to have it retrieve the required plugins.
Then build the image.
Provision the Nomad cluster
Open the aws/terraform.tfvars
file with your text editor and update the key_name
variable with the name of your AWS keypair and the ami
variable with the value output from the Packer build command above. These are the only required variables that need to be updated but you can modify the other values if you want to provision in a different region or change the cluster size, for example. Save the file.
Open your terminal and use the built-in uuid()
function of the Terraform console to generate the required UUIDs for the token's credentials.
Generate and save the UUIDs as Terraform specific environment variables.
First, the token ID.
Then, the token secret.
Initialize Terraform to have it retrieve any required plugins and set up the workspace.
Provision the resources. Respond yes
to the prompt to confirm the operation and then press Enter to start the process. This will take a few minutes to provision.
Once Terraform finishes provisioning the resources, verify that the services are healthy by navigating to the Consul UI in your web browser with the link in the Terraform output.
Click on the Log in button and use the bootstrap token secret from the Terraform output to login.
Click on the Nodes page from the left navigation. Note that there are 8 healthy nodes, which include the 3 servers and 5 clients created by Terraform.
Next, run the post-setup.sh
script. This script retrieves the Nomad bootstrap token from the Consul KV store, saves it locally to nomad.token
, and then deletes the token from the Consul KV store.
Warning
If the nomad.token
file already exists, the script won't work until it has been deleted. Delete the file manually and re-run the script or use rm nomad.token && ./post-script.sh
instead.
Copy the export
commands from the output, paste them into your terminal, and press Enter.
Finally, verify connectivity to the cluster by running a Nomad command.
Be aware that you can also navigate to the Nomad UI in your web browser with the link in the post-setup.sh
script output and login with the bootstrap token provided by setting the Secret ID to the token's value, and then clicking on the Clients page from the left navigation.
Run the application job
Open your terminal and submit the demo web application job.
View information about the web application job with the status
command.
Navigate back to the Nomad UI in your web browser, click on the Jobs link in the left navigation, and then the demo-webapp job to see similar information.
Note
The Nomad UI topology page displays a great visualization of the cluster, the resources available and in use on each of the nodes, and which jobs are using those resources. You can find it by clicking on the Topology link in the left navigation.
The application instances are ready to handle requests and in the next section you'll use Nginx to balance those incoming requests between the instances.
Run the internal Nginx load balancer job
The Nginx job installs one instance of Nginx on one node in both datacenters. Nginx listens on port 8080
and balances traffic to the web application running on the clients in the same datacenter. Nginx uses consul-template to retrieve the client IPs and ports from the demo-webapp
services registered as part of the demo-webapp job.
Open your terminal and submit the Nginx job.
The Nginx instances are filling the role of internal load balancer to the webapp services running within the cluster and will become accessible externally with the application load balancer you'll create in the next section.
Create the application load balancer
Create a new file named alb.tf
in the aws
directory with the other Terraform configuration files. Copy and paste the contents below into the file and save it.
This creates an application load balancer (ALB) with two target groups containing the Nomad client nodes. The first group contains the clients in the dc1
datacenter that have the api
meta tag while the second group contains the clients in dc2
with the payments
tag. The ALB listens on port 80
and forwards requests to the Nomad clients on port 8080
where the Nginx service is listening. The target groups each have an equal weighting for requests which means that incoming requests will alternate between being sent to the first group and the second.
Apply the changes with Terraform and respond yes
to the prompt to confirm the operation. This will take a few minutes.
Next, verify that the application load balancer is working correctly.
Note
It may take a few minutes for DNS to propagate and the ALB to become available.
Run the curl
command below. Notice that each response comes from one of the five Nomad client nodes. Press ctrl
and c
to stop the command when you're ready.
Target specific clients
You may want to target a specific client node or group of nodes based on attributes like physical resource configuration (presence of GPUs, higher memory, larger and/or faster storage) or software configuration (presence of a certain part of an application like a DB layer). In these cases, an application load balancer can help direct traffic to the appropriate nodes based on the layer 7 properties like the URL path.
To illustrate this type of scenario, the cluster has been set up with nodes separated into two logical datacenters that each contain a specific piece of an application: the api
and payments
services.
Currently the ALB directs traffic to both groups of clients evenly. In the next section, each group will be mapped to a specific URL path related to their part of the application.
Update the ALB configuration
Copy the contents below, add them to the end of the alb.tf
file, and save the file.
This will map the /api
path to the Nomad clients in dc1
with the api
metadata tag and /payments
to the ones in dc2
with the payments
tag.
Open your terminal and apply the changes with Terraform. Respond yes
to the prompt to confirm the operation.
With these changes, any requests to the ALB on the /api
path will be forwarded to the Nginx service running in the dc1
datacenter and served by the web application service running in the same datacenter. Requests to the /payments
path will be forwarded to the Nginx and web application services running in dc2
. Finally, any other requests will be split evenly between both Nginx services and their respective web application services – this is just used as an illustration and may not make sense in your application depending on the configuration of that application.
Run the curl
command below which specifies the service path and note that now only certain clients respond based on the path in the request. Verify this by checking the node addresses in the output against the client list in the Nomad UI.
Query the /api
path. Note the three unique addresses and how they match up to the Nomad clients in dc1
.
Query the /payments
path. Note the two unique addresses and how they match up to the Nomad clients in dc2
.
Cleanup
Run terraform destroy
to clean up your provisioned infrastructure. Respond yes
to the prompt to confirm the operation.
Your AWS account still has the AMI and its S3-stored snapshots, which you may be charged for depending on your other usage. Delete the AMI and snapshots stored in your S3 buckets.
Note
Remember to delete the AMI images and snapshots in the region where you created them. If you didn't update the region
variable in the terraform.tfvars
file, they will be in the us-east-1
region.
In your us-east-1
AWS account, deregister the AMI by selecting it, clicking on the Actions button, then the Deregister AMI option, and finally confirm by clicking the Deregister AMI button in the confirmation dialog.
Delete the snapshots by selecting the snapshots, clicking on the Actions button, then the Delete snapshot option, and finally confirm by clicking the Delete button in the confirmation dialog.
Next steps
In this tutorial you created a Nomad cluster, deployed an example web application, deployed Nginx to balance requests to the web application, created an external ALB to forward traffic to Nginx, and modified the ALB to forward requests to different instances of Nginx based on the request path in the URL.
For more information, check out the folowing resources.
- Learn more about the benefits of an ALB
- Read more about the integration between Consul and Nomad
- Try swapping out Nginx for another internal load balancing application