Consul
Use Terraform to register external services
The Terraform Consul provider exposes resources used to interact with a Consul datacenter using Terraform. This enables you to accomplish a number of tasks, including but not limited to:
- Registering external services or services that cannot be registered with local agent
- Referencing Consul keys in your Terraform configuration
- Referencing a list of nodes (IP address, ports, etc) a particular service has been registered on
Tip
The content of this tutorial also applies to Consul clusters hosted on HashiCorp Cloud Platform (HCP). HCP Consul generates a Terraform command to help you import infrastructure. To find the command in the HCP Portal, go to a cluster's Cluster details page, and then click Manage. You can copy the command from the Import to Terraform section. For more information on how to configure the provider, refer to infrastructure import usage in the Terraform documentation.
Note
Only use Terraform to register external services. Services running locally with a Consul agent should register with Consul directly to take advantage of native health checking.
In this tutorial, you will use Terraform to register two new external service and query information about it. To do this, you will:
- Register two new external nodes
- Register the counting and dashboard services with health checks
- Query all services registered with the Consul datacenter
If you get lost at anytime, you can view the final Terraform configuration here:
Prerequisites
This tutorial assumes that you are familiar with Consul external services. To learn more about this concept, visit the External Services Learn tutorial.
In addition, you'll need:
- Docker and Docker Compose installed.
- Terraform version 0.14 or higher installed.
Clone the following GitHub repo.
$ git clone https://github.com/hashicorp/getting-started-terraform-consul-provider
Next, navigate to the /getting-started-terraform-consul-provider
folder.
$ cd getting-started-terraform-consul-provider/
Checkout the following git tag.
$ git checkout v1.0.0
Change into the directory that contains the demo code.
$ cd consul-playground/
Start Consul and the services for this tutorial.
$ docker-compose up --detach
This command will take about a minute to spin up the following:
- A Consul datacenter running with ACLs pre-configured (UI on port :8500)
- 3 Consul servers
- 2 Consul clients
- Counting service running on port :9001
- Dashboard for counting service running on port :8080
Run the command docker ps
to confirm these services were successfully deployed.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d502674bd9af hashicorp/dashboard-service:0.0.4 "./dashboard-service" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp, 9002/tcp dashboard
acaa41abacd3 consul-playground_consul-agent-2 "docker-entrypoint.s…" About a minute ago Up About a minute 8300-8302/tcp, 8500/tcp, 8301-8302/udp, 8600/tcp, 8600/udp consul-playground_consul-agent-2_1
586c1391eca1 consul-playground_consul-server-1 "docker-entrypoint.s…" About a minute ago Up About a minute 8300-8302/tcp, 8500/tcp, 8301-8302/udp, 8600/tcp, 8600/udp consul-playground_consul-server-1_1
1a32298cca9f hashicorp/counting-service:0.0.2 "./counting-service" About a minute ago Up About a minute 0.0.0.0:9001->9001/tcp counting-service
70892a8be152 consul-playground_consul-agent-1 "docker-entrypoint.s…" About a minute ago Up About a minute 8300-8302/tcp, 8500/tcp, 8301-8302/udp, 8600/tcp, 8600/udp consul-playground_consul-agent-1_1
245a5d1bbef7 consul-playground_consul-server-2 "docker-entrypoint.s…" About a minute ago Up About a minute 8300-8302/tcp, 8500/tcp, 8301-8302/udp, 8600/tcp, 8600/udp consul-playground_consul-server-2_1
a624ddba3a71 consul-playground_consul-server-bootstrap "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:8400->8400/tcp, 0.0.0.0:8500->8500/tcp, 8300-8302/tcp, 8301-8302/udp, 0.0.0.0:8600->8600/tcp, 0.0.0.0:8600->8600/udp consul-playground_consul-server-bootstrap_1
Bootstrap Consul ACLs
Since this Consul datacenter has ACLs pre-configured, you need a bootstrap ACL
token to view and manipulate any Consul resources. To generate the bootstrap ACL
token, run the following docker exec
command, which runs bootstraps Consul's
ACL system and outputs the bootstrap token as the SecretID
.
$ docker exec -it consul-playground_consul-server-1_1 consul acl bootstrap
AccessorID: aaf45cbf-2293-59f8-dacb-d473e35d111c
SecretID: 91f5e30c-c51b-8399-39bb-a902346205c4
Description: Bootstrap Token (Global Management)
Local: false
Create Time: 2020-02-19 21:36:04.9283912 +0000 UTC
Policies:
00000000-0000-0000-0000-000000000001 - global-management
If you get the following failure response, the Consul datacenter has not nominated a cluster leader yet. Wait a couple of seconds before re-running the command.
Failed ACL bootstrapping: Unexpected response code: 500 (The ACL system is currently in legacy mode.)
Access the web UI
Finally, to configure your Consul UI to use the ACL token, open the Consul UI http://localhost:8500. Click on the Log in button at the center of the page, or at the top right hand side of the window.
Enter your bootstrap ACL token into the text box and click Save. After refreshing your page, you should be able to view your Consul resources via the UI.
Configure the provider
To start using the Terraform Consul provider, you will need to configure the
provider block to point to your Consul datacenter. Open the main.tf
file in your text editior.
Next, replace the placeholder with the bootstrap ACL token you retrieved in the
previous step, see line 17
.
main.tf
# Specify the Consul provider source and version
terraform {
required_providers {
consul = {
source = "hashicorp/consul"
version = "2.14.0"
}
}
}
# Configure the Consul provider
provider "consul" {
address = "localhost:8500"
datacenter = "dc1"
# SecretID from the previous step
token = "YOUR_BOOTSTRAP_TOKEN_HERE"
}
While all the provider arguments are optional, we have explicitly listed address, datacenter and token as they are unique to each configuration. For a full list of arguments and their default values, visit the Terraform Consul Provider documentation.
Next, initialize the terraform workspace.
$ terraform init
This will install the Consul provider and configure it with the arguments you
provided in main.tf
.
Register external nodes
Add the following resource blocks to your main.tf
Terraform file.
# Register external node - counting
resource "consul_node" "counting" {
name = "counting"
address = "localhost"
meta = {
"external-node" = "true"
"external-probe" = "true"
}
}
# Register external node - dashboard
resource "consul_node" "dashboard" {
name = "dashboard"
address = "localhost"
meta = {
"external-node" = "true"
"external-probe" = "true"
}
}
This will register two new external nodes with Consul, one named counting and the named dashboard. Both external nodes will have addresses localhost. In practice, you would assign the address to the external service's address you want to connect to.
In addition, both resources' metadata have external-node
and external-probe
assigned to true. If Consul ESM were running alongside Consul, these arguments
would cause it to regularly health check external nodes and update their status
in the catalog. It's been omitted from this demo because all services are
running on the same host.
You can configure ESM to work with ACLs enabled using the token
block in in
its configuration file. To learn more about Consul ESM, visit the
External Services Learn tutorial.
To apply your configuration and register the nodes, run the following command.
Confirm the proposed changes with a yes
when prompted.
$ terraform apply
Plan: 2 to add, 0 to change, 0 to destroy.
consul_node.counting: Creating...
consul_node.dashboard: Creating...
consul_node.counting: Creation complete after 0s [id=counting-localhost]
consul_node.dashboard: Creation complete after 0s [id=dashboard-localhost]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
If successfully applied, Consul UI will show the new counting and dashboard nodes in the nodes tab.
Register services
Now that you have created the external nodes, add the following resource blocks
to your main.tf
file to register the services.
main.tf
# Register Counting Service
resource "consul_service" "counting" {
name = "counting-service"
node = consul_node.counting.name
port = 9001
tags = ["counting"]
check {
check_id = "service:counting"
name = "Counting health check"
status = "passing"
http = "localhost:9001"
tls_skip_verify = false
method = "GET"
interval = "5s"
timeout = "1s"
}
}
# Register Dashboard Service
resource "consul_service" "dashboard" {
name = "dashboard-service"
node = consul_node.dashboard.name
port = 8080
tags = ["dashboard"]
check {
check_id = "service:dashboard"
name = "Dashboard health check"
status = "passing"
http = "localhost:8080"
tls_skip_verify = false
method = "GET"
interval = "5s"
timeout = "1s"
}
}
Once you've done this, apply the configuration.
$ terraform apply
Plan: 2 to add, 0 to change, 0 to destroy.
consul_service.dashboard: Creating...
consul_service.counting: Creating...
consul_service.counting: Creation complete after 0s [id=counting-service]
consul_service.dashboard: Creation complete after 0s [id=dashboard-service]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Confirm the run with a yes
.
This assigns each service to their respective nodes. In addition, it attaches health checks and tags. On successful terraform apply, the two services should appear in your Consul UI.
You have successfully scheduled two external services on Consul using Terraform!
Discover services
To discover all services on your Consul datacenter, add the following
configuration to main.tf
. This will return a list of all services in the
default datacenter. You can specify additional argument parameters in the data
block.
main.tf
# List all services
data "consul_services" "dc1" {}
output "consul_services_dc1" {
value = data.consul_services.dc1
}
Apply the configuration. This time, you will use the flag --auto-approve
to skip the prompt.
$ terraform apply --auto-approve
Changes to Outputs:
+ consul_services_dc1 = {
+ datacenter = "dc1"
+ id = "catalog-services-dc1"
+ names = [
+ "consul",
+ "counting-service",
+ "dashboard-service",
]
+ query_options = null
+ services = {
+ "consul" = ""
+ "counting-service" = "counting"
+ "dashboard-service" = "dashboard"
}
+ tags = {
+ "counting" = "counting-service"
+ "dashboard" = "dashboard-service"
}
}
You can apply this plan to save these new output values to the Terraform state,
without changing any real infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Notice the new output displayed in the plan. It contains a list of services in the Consul datacenter: the counting service, the dashboard service, and Consul itself.
Reference a specific service
You can also use Terraform to query additional information about a particular service. The following configuration snippet will return all information about the counting service you just registered.
Note
There is a difference between consul_services
and consul_service
. consul_services
lists multiple services. consul_service
references a specific service.
# List counting service information
data "consul_service" "counting" {
name = consul_service.counting.name
}
output "consul_service_counting" {
value = data.consul_service.counting
}
Apply the new configuration.
$ terraform apply --auto-approve
consul_node.dashboard: Refreshing state... [id=dashboard-localhost]
consul_node.counting: Refreshing state... [id=counting-localhost]
consul_service.dashboard: Refreshing state... [id=dashboard-service]
consul_service.counting: Refreshing state... [id=counting-service]
Changes to Outputs:
+ consul_service_counting = {
+ datacenter = "dc1"
+ filter = null
+ id = "catalog-service-dc1-\"counting-service\"-\"\""
+ name = "counting-service"
+ query_options = null
+ service = [
+ {
+ address = "localhost"
+ create_index = "34"
+ enable_tag_override = "false"
+ id = "counting-service"
+ meta = {
+ "external-source" = "terraform"
}
+ modify_index = "34"
+ name = "counting-service"
+ node_address = "localhost"
+ node_id = ""
+ node_meta = {
+ "external-node" = "true"
+ "external-probe" = "true"
}
+ node_name = "counting"
+ port = "9001"
+ tagged_addresses = {}
+ tags = [
+ "counting",
]
},
]
+ tag = ""
}
You can apply this plan to save these new output values to the Terraform state,
without changing any real infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Notice the new output displayed the terminal.
Reference an existing service
You can also reference existing Consul services. The following configuration snippet will return a list of IP addresses and ports the Consul external nodes are registered on. This information can be useful to modify firewall rules via Terraform.
# List Consul agent node address and ports
data "consul_service" "agents" {
name = "consul"
}
output "consul_agents_address_ports" {
value = {
for service in data.consul_service.agents.service:
service.node_id => join(":", [service.node_address, service.port])
}
}
Apply the configuration.
$ terraform apply --auto-approve
consul_node.counting: Refreshing state... [id=counting-localhost]
consul_node.dashboard: Refreshing state... [id=dashboard-localhost]
consul_service.counting: Refreshing state... [id=counting-service]
consul_service.dashboard: Refreshing state... [id=dashboard-service]
Changes to Outputs:
+ consul_agents_address_ports = {
+ 2071d98c-7338-d6a5-143b-3bff14a26335 = "172.18.0.3:8300"
+ 7a170a12-dbb5-4675-f9e6-2c53a122951c = "172.18.0.6:8300"
+ d82d88ed-74b6-79bb-17dd-7d0972ea70a6 = "172.18.0.7:8300"
}
~ consul_services_dc1 = {
~ names = [
+ "dashboard-service",
"consul",
"counting-service",
- "dashboard-service",
]
# (5 unchanged elements hidden)
}
You can apply this plan to save these new output values to the Terraform state,
without changing any real infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
consul_agents_address_ports = {
"2071d98c-7338-d6a5-143b-3bff14a26335" = "172.18.0.3:8300"
"7a170a12-dbb5-4675-f9e6-2c53a122951c" = "172.18.0.6:8300"
"d82d88ed-74b6-79bb-17dd-7d0972ea70a6" = "172.18.0.7:8300"
}
consul_service_counting = {
"datacenter" = "dc1"
"filter" = tostring(null)
"id" = "catalog-service-dc1-\"counting-service\"-\"\""
"name" = "counting-service"
"query_options" = toset(null) /* of object */
"service" = tolist([
{
"address" = "localhost"
"create_index" = "34"
"enable_tag_override" = "false"
"id" = "counting-service"
"meta" = tomap({
"external-source" = "terraform"
})
"modify_index" = "34"
"name" = "counting-service"
"node_address" = "localhost"
"node_id" = ""
"node_meta" = tomap({
"external-node" = "true"
"external-probe" = "true"
})
"node_name" = "counting"
"port" = "9001"
"tagged_addresses" = tomap({})
"tags" = tolist([
"counting",
])
},
])
"tag" = ""
}
consul_services_dc1 = {
"datacenter" = "dc1"
"id" = "catalog-services-dc1"
"names" = tolist([
"dashboard-service",
"consul",
"counting-service",
])
"query_options" = toset(null) /* of object */
"services" = tomap({
"consul" = ""
"counting-service" = "counting"
"dashboard-service" = "dashboard"
})
"tags" = tomap({
"counting" = "counting-service"
"dashboard" = "dashboard-service"
})
}
Notice the new output listing the consul agents, their IP addresses, and ports.
Next steps
Congrats! You successfully configured your Terraform Consul Provider and used it to register new nodes and services. Now, clean up your demo environment.
$ docker-compose down
Stopping dashboard ... done
Stopping counting-service ... done
Stopping consul-playground_consul-agent-1_1 ... done
Stopping consul-playground_consul-server-bootstrap_1 ... done
Stopping consul-playground_consul-server-2_1 ... done
Stopping consul-playground_consul-server-1_1 ... done
Stopping consul-playground_consul-agent-2_1 ... done
Removing dashboard ... done
Removing counting-service ... done
Removing consul-playground_consul-agent-1_1 ... done
Removing consul-playground_consul-server-bootstrap_1 ... done
Removing consul-playground_consul-server-2_1 ... done
Removing consul-playground_consul-server-1_1 ... done
Removing consul-playground_consul-agent-2_1 ... done
Removing network consul-playground_consul-demo
Related resources
- To discover additional capabilities, visit the Terraform Consul Provider Registry Documentation Page.
- Learn how to deploy Consul with Kubernetes on AWS.
- Learn more about Terraform.