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 psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESd502674bd9af 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 dashboardacaa41abacd3 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_1586c1391eca1 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_11a32298cca9f hashicorp/counting-service:0.0.2 "./counting-service" About a minute ago Up About a minute 0.0.0.0:9001->9001/tcp counting-service70892a8be152 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_1245a5d1bbef7 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_1a624ddba3a71 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 bootstrapAccessorID: aaf45cbf-2293-59f8-dacb-d473e35d111cSecretID: 91f5e30c-c51b-8399-39bb-a902346205c4Description: Bootstrap Token (Global Management)Local: falseCreate Time: 2020-02-19 21:36:04.9283912 +0000 UTCPolicies: 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 versionterraform { required_providers { consul = { source = "hashicorp/consul" version = "2.14.0" } }}# Configure the Consul providerprovider "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 - countingresource "consul_node" "counting" { name = "counting" address = "localhost" meta = { "external-node" = "true" "external-probe" = "true" }}# Register external node - dashboardresource "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 Serviceresource "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 Serviceresource "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 servicesdata "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 informationdata "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 portsdata "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 ... doneStopping counting-service ... doneStopping consul-playground_consul-agent-1_1 ... doneStopping consul-playground_consul-server-bootstrap_1 ... doneStopping consul-playground_consul-server-2_1 ... doneStopping consul-playground_consul-server-1_1 ... doneStopping consul-playground_consul-agent-2_1 ... doneRemoving dashboard ... doneRemoving counting-service ... doneRemoving consul-playground_consul-agent-1_1 ... doneRemoving consul-playground_consul-server-bootstrap_1 ... doneRemoving consul-playground_consul-server-2_1 ... doneRemoving consul-playground_consul-server-1_1 ... doneRemoving consul-playground_consul-agent-2_1 ... doneRemoving 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.