Automate reverse proxy configuration with consul-template
Load balancers play an important role in ensuring the equal distribution of traffic to backend services. Keeping load balancers configuration updated is often performed via manual processes which can affect the delivery time of a solution. In a modern environment, where multiple replicas of a service exist and where services scale or change over time, maintaining a load balancer configuration accurate is a risk-prone and time consuming activity.
Consul, with its built-in load balancing features, helps applications automatically adapt to changes in services. Consul, in its service discovery configuration, automatically provides round-robin traffic shaping across multiple instances of the same service and, when used in its service mesh configuration, can be configured to use different load-balancing profiles to fine tune how different instances are exposed to traffic.
In scenarios where a load balancer is already present, Consul provides external tools that integrate the configuration process for the load balancer and automate the load balancer configuration. This eliminates the need for manual process and greatly reduces the risk for errors and misconfiguration as well as reducing the reaction time to service landscape change in your datacenter. These tools are consul-template and Consul-Terraform-Sync.
In this tutorial you will use consul-template to automate the configuration for an NGINX server used as a reverse proxy. You will first deploy a basic configuration that will update the NGINX configuration file to automatically react to changes in the Consul catalog, and then change the configuration to use Consul KV store to dynamically change load balancing across multiple instances of a service.
Tutorial scenario
This tutorial uses HashiCups, a demo coffee shop application made up of several microservices running on VMs.
At the beginning of the tutorial, you have a fully deployed Consul datacenter with an instance of the HashiCups application, composed by four services, NGINX, Frontend, API, and Database, deployed and registered in Consul catalog. Two instances of the Frontend services are registered in the Consul datacenter.
Prerequisites
This tutorial assumes you are already familiar with Consul service discovery and its core functionalities. If you are new to Consul refer to refer to the Consul Getting Started tutorials collection.
If you want to follow along with this tutorial and you do not already have the required infrastructure in place, the following steps guide you through the process to deploy a demo application and a configured Consul datacenter on AWS automatically using Terraform.
To create a Consul deployment on AWS using terraform, you need the following:
Clone GitHub repository
Clone the GitHub repository containing the configuration files and resources.
Enter the directory that contains the configuration files for this tutorial.
Create infrastructure
With these Terraform configuration files, you are ready to deploy your infrastructure.
Issue the terraform init
command from your working directory to download the
necessary providers and initialize the backend.
Then, deploy the resources. Confirm the run by entering yes
.
Tip
The Terraform deployment could take up to 15 minutes to complete. Feel free to explore the next sections of this tutorial while waiting for the environment to complete initialization.
After the deployment is complete, Terraform returns a list of outputs you can use to interact with the newly created environment.
The Terraform outputs provide useful information, including the bastion host IP address. The following is a brief description of the Terraform outputs:
- The
ip_bastion
provides IP address of the bastion host you use to run the rest of the commands in this tutorial. - The
remote_ops
lists the bastion host IP, which you can use access the bastion host. - The
retry_join
output lists Consul'sretry_join
configuration parameter. The next tutorial uses this information to generate Consul server and client configuration. - The
ui_consul
output lists the Consul UI address. The Consul UI is not currently running. You will use the Consul UI in a later tutorial to verify that Consul started correctly. - The
ui_grafana
output lists the Grafana UI address. You will use this address in a future tutorial. - The
ui_hashicups
output lists the HashiCups UI address. You can open this address in a web browser to verify the HashiCups demo application is running properly.
List AWS instances
The scenario deploys seven virtual machines.
After deployment, six virtual machines, consul_server[0]
, database[0]
, frontend[0]
, frontend[1]
, api[0]
, and nginx[0]
are configured in a Consul datacenter with service discovery.
The remaining node, bastion
is used to perform the tutorial steps.
Login into the bastion host VM
Login to the bastion host using ssh
.
Verify consul-template is installed
The tutorial scenario automatically install consul-template on all the nodes of the datacenter. If you want to install consul-template in your environment refer to the installation instructions for consul-template.
Configure CLI to interact with Consul
Configure your bastion host to communicate with your Consul environment using the two dynamically generated environment variable files.
That will produce no output.
After loading the needed variables, verify you can connect to your Consul datacenter.
Create ACL token for consul-template
Consul-template requires the ability to query Consul catalog to retrieve data about the hashicups-frontend and hashicups-api services, as well as data about the nodes running those services. For this reason, you need a token providing read
permissions on both hashicups-api and hashicups-frontend nodes and services.
First, create the proper configuration file for the policy.
Then, create the policy using the generated file.
That will produce an output similar to the following.
Finally, generate the token from the policy.
That will produce an output similar to the following.
Set your newly generated token as the CONSUL_TEMPLATE_TOKEN
environment variable. You will use this variable later in the tutorial to generate the consul-template configuration file.
Configure consul-template
Consul-template requires:
- a configuration file, to configure the consul-template process
- a template file to use to generate the application configuration
Configuration file
First, generate the consul-template configuration file. To do so you need:
- a
consul
section, containing the address and permissions to connect to your Consul datacenter - a
template
section, containing instructions for the template, a source file path, a destination file to save the generated configuration, and a command to execute every time the configuration file is generated.
The configuration used as example also contains some extra parameters to define logging and signal handling.
Copy the configuration file on the hashicups-nginx-0
node.
The remaining part of the configuration will be performed directly on the hashicups-nginx-0 node.
Login to hashicups-nginx-0
from the bastion host.
Verify the configuration file for consul-template got correctly copied on the node.
Template file
The template file is used as a model to render the configuration file for your service. For this reason it needs to be composed using the desired output file as a model.
In this scenario, consul-template will generate the upstream definition for the NGINX process. Check the original configuration file.
NGINX is configured to redirect requests to hashicups-frontend on port 3000 and hashicups-api on port 8081. The port values are hardcoded inside the configuration file. This can be an issue in scenarios where the port numbers are not known or fixed.
Note
If you use Consul service mesh this issue is not present. In Consul service mesh you define the ports for the upstreams in the service definition file and Consul makes them accessible on the port you defined on the loopback interface, no matter the actual port the services are using.NGINX is configured to use the Consul FQDN for the nodes where the services hashicups-frontend and hashicups-api are running. This binds the configuration to one single instance of the service, no matter how many service instances are registered in the Consul catalog. Also, using the Consul FQDN requires the use of Consul as DNS for the node where NGINX is running, when this is not an option IP addresses are usually required.
Note
In Consul service discovery environments, you can use the service FQDN for service resolution. Using the service FQDN, `_service-name_.service._datacenter_._domain_`, Consul will automatically load balance traffic across the healthy available instances of the service. The only load balancing policy in this case is round robin.List the different tags for services registered in Consul and make sure that hashicups-frontend
has two tags, one per each instance.
The hashicups-frontend application has two tags, each representing one application instance. Currently, NGINX is configured to send traffic to the address of only one of these instances.
In order for NGINX to send traffic to all available service instances, create a template file that dynamically generates the related NGINX configuration.
The template iterates instances of hashicups-frontend and hashicups-api, using the range
function, and then generates the configuration using the Address
and Port
values returned from Consul catalog. Hardcoding the port number or the instance addresses is not necessary anymore.
For the full list of consul-template functions and parameters, refer to the Templating Language documentation.
Test consul-template configuration
Templates for consul-template can be tricky to write correctly at the first attempt. The -dry
option provided by consul-template prints the rendered template on stdout
without modifying the destination files. This makes testing your templates a safe operation. Also, use the -once
execution mode to stop consul-template after the first iteration.
That will produce an output similar to the following.
The output shows that the generated configuration file now contains both hashicups-frontend
instance addresses.
Start consul-template
Once you tested the configuration, start consul-template as a long lived process.
The process is started in the background, you can check the logs for the process using the log file specified in the configuration.
Inspect the contents of the NGINX configuration file.
If the file was generated properly, there should be two server
addresses in the upstream frontend_upstream
code block, and one server
address in the api_upstream
code block.
To continue with the tutorial, exit the ssh session to return to the bastion host.
Verify configuration is generated dynamically
From this moment on, the configuration file is managed directly by consul-template and it is automatically updated when there are changes in the Consul catalog regarding the instances of hashicups-frontend and hashicups-api. Test configuration dynamic change by removing one of the two instances of hashicups-frontend
.
Login to hashicups-frontend-1
from the bastion host.
That will produce an output similar to the following.
To check the configuration file, return to the hashicups-nginx-0
node. First, exit the ssh session to return to the bastion host.
Then, login to hashicups-nginx-0
from the bastion host.
Verify that the configuration file was updated to reflect the change in the hashicups-frontend services.
The file should now show only one instance for the frontend_upstream
.
As a last test, restart the second instance of the hashicups-frontend.
First , exit the ssh session to return to the bastion host.
Then, login to hashicups-frontend-1
from the bastion host.
Start the frontend service on the hashicups-frontend-1
instance.
That will produce an output similar to the following.
Verify that the configuration file was updated to reflect the change in the hashicups-frontend services.
To check the configuration file, return to the hashicups-nginx-0
node. First, exit the ssh session to return to the bastion host.
Then, login to hashicups-nginx-0
from the bastion host.
Verify that the configuration file was updated to reflect the change in the hashicups-frontend services.
To continue with the tutorial, exit the ssh session to return to the bastion host.
Tune configuration with Consul KV
The configuration obtained configures a basic round robin load balancing approach. Round robin approach is useful for scenarios where nodes are equivalent in terms of application version or capabilities but it is not effective when testing new version of a service, like during blue-green or canary deployments. In these cases, you want to have a fine grained approach to the load balancing by defining a different traffic balance across the different instances of the same service.
NGINX uses the weight
parameter to distribute traffic across the different available upstreams. In a static configuration file you can manually define the weight settings for each service instance but, in a situation where the content of the file is generated automatically by consul-template, you need a different way to pass configuration parameters to NGINX. In this scenario you will use Consul KV to define the weights for the different instances and will change the template to take these changes into consideration.
Add configuration in Consul KV
The convention adopted for this tutorial is that the KV store will contain a folder weights/
. Inside that folder, there will be a key, named as the node you want to configure, that defines the value for the weight to apply to that node in the NGINX configuration. The higher the value you set for the weight
parameter, the higher the amount of requests that will be sent to that node.
For example, to define a weight=3
for the second instance of the Frontend service, add a key at weights/hashicups-frontend-1
with value 3
.
Update ACL policy
Having the configuration written in Consul KV means that consul-template needs permissions to read keys from the KV store at least on the paths where the configuration is located. For this example you will only need read
access to the weights/
path.
First, create the proper configuration file for the policy.
Then, use the configuration file to update the policy consul-template-policy
that you created earlier.
That will produce an output similar to the following.
Updating the policy automatically extends permissions to the tokens associated with the policy. You will now verify that the token attached to this policy has the correct permissions to read from the KV store.
First, set the token as the CONSUL_TEMPLATE_TOKEN
environment variable.
Then, query the Consul KV store for the weights/hashicups-frontend-1
key.
With the updated permissions, you can now re-use the same token to continue with the configuration.
Generate new configuration file for consul-template
The previous configuration only used a single template
section to generate the configuration file that would react to changes in the Consul catalog for service changes. In the new scenario you want to add another section that will react to KV changes and will cause a configuration reload when something changes in the weights/
path. To do so, add a new template
section in the configuration file that will reload consul-template when something changes in the KV store. You also need to replace the previous template file used to generate the NGINX configuration file. The new template will include the weight
parameters.
Copy the configuration file on the hashicups-nginx-0
node.
The remaining part of the configuration will be performed directly on the hashicups-nginx-0 node.
Login to hashicups-nginx-0
from the bastion host.
Verify the configuration file for consul-template got correctly copied on the node.
Generate new template file for consul-template
Create a template file that dynamically generates the related NGINX configuration and includes weight
values.
Restart consul-template to use new configuration
Stop the running consul-template process.
Then, start consul-template with the new configuration file.
The process is started in the background, you can check the logs for the process using the log file specified in the configuration.
Verify that the configuration file was generated correctly.
You now have a way to dynamically generate the configuration for your NGINX that will get automatically updated in case the instances of hashicups-frontend and hashicups-api change over time. Plus you have a way to tune balancing across different instances of hashicups-frontend using Consul KV.
Verify configuration is generated dynamically
Verify that the configuration changes dynamically with the KV content.
Exit the ssh session to return to the bastion host.
Change the value for weights/hashicups-frontend-1
Return to the hashicups-nginx-0
node.
Verify that the configuration file was generated correctly.
Verify that with no keys present, the weight
defaults to 1
. This fall-back value is specified in the template file nginx-upstreams-weights.tpl
as default for the keyOrDefault
function.
Exit the ssh session to return to the bastion host.
Remove the weights/hashicups-frontend-1
key.
Return to the hashicups-nginx-0
node.
Verify that the configuration file got generated correctly.
Destroy the infrastructure
Now that the tutorial is complete, clean up the infrastructure you created.
From the ./self-managed/infrastruture/aws
folder of the repository, use terraform
to destroy the infrastructure.
Next steps
In this tutorial you learned how to integrate an existing NGINX load balancer with Consul catalog to balance traffic across multiple instances of the same service. You used consul-template to automatically generate the configuration and used Consul KV to further tune the weight applied to each instance of the service.
For more information about the topics covered in this tutorial, refer to the following resources:
- Service configuration with Consul Template
- consul-template repository
- Go-template documentation
To learn more about other load balancing capabilities provided by Consul: