Service Discovery and Consul DNS

Register External Services using Terraform

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

In this guide, you will use Terraform to register two new external service and query information about it. To do this, you will:

  1. Register two new external nodes
  2. Register the counting and dashboard services with health checks
  3. Query all services registered with the Consul datacenter

If you get lost at anytime, you can view the final Terraform configuration here:

Prerequisites

This guide assumes that you are familiar with Consul external services. To learn more about this concept, please visit the External Services Learn guide.

In addition, you'll need to have Docker and Docker Compose installed.

Online tutorial: An interactive tutorial is also available if you do not want to set up the demo environment locally. Click the Show Tutorial button to launch the demo environment.

Clone the following GitHub repo. Next, navigate to /getting-started-terraform-consul-provider/consul-playground then run:

$ docker-compose up

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 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 master ACL token to view and manipulate any Consul resources. To generate the master ACL token, run the following docker exec command, which runs bootstraps Consul's ACL system and outputs the master 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 see the following failure response, the Consul datacenter has not nominated a cluster leader yet. Please 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 (localhost:8500 - Click on the Consul UI tab if you're using Katacoda). Then, navigate to the ACL page using the menu at the top. You should see a page like this:

Consul UI Nodes

Enter your master 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. Add the following snippet to your main.tf file, located in the root of the cloned directory.

Remember to replace the token with the master ACL token you retrieved in the previous step.

# main.tf 

# Configure the Consul provider
provider "consul" {
  address    = "localhost:8500"
  datacenter = "dc1"

  # SecretID from the previous step
  token      = "91f5e30c-c51b-8399-39bb-a902346205c4"
}

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, please visit the Terraform Consul Provider documentation.

Next, navigate to directory that contains your main.tf file and initialize your 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's 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 guide.

To apply your configuration and register the nodes, run the following command -- remember to confirm the run with a yes.

$ terraform apply

If successfully applied, you should see the following in your Consul UI.

Consul UI Nodes

Register services

Now that we have created the external nodes, add the following resource blocks to your main.tf file to register the services.

# 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 -- remember to confirm the run with a yes.

$ terraform apply

This assigns each service to their respective nodes. In addition, it attaches health checks and tags. On successful terraform apply, you should see the following on your Consul UI.

Consul UI Services

If you do, 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.

# List all services
data "consul_services" "dc1" {}

output "consul_services_dc1" {
    value = data.consul_services.dc1
}

You should see the following output after running applying your configuration -- remember to confirm the run with a yes.

$ terraform apply
consul_services_dc1 = {
  "datacenter" = ""
  "id" = "catalog-services-"
  "names" = [
    "consul",
    "counting-service",
    "dashboard-service",
  ]
  "services" = {
    "consul" = ""
    "counting-service" = "counting"
    "dashboard-service" = "dashboard"
  }
}

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.

# List counting service information
data "consul_service" "counting" {
  name = consul_service.counting.name
}

output "consul_service_counting" {
  value = data.consul_service.counting
}

You should see the following output after running applying your configuration -- remember to confirm the run with a yes.

$ terraform apply
counting_inf = {
  "datacenter" = ""
  "id" = "catalog-service--\"counting-service\"-\"\""
  "name" = "counting-service"
  "service" = [
    {
      "address" = "2886795318-9001-frugo01.environments.katacoda.com"
      "create_index" = "24"
      "enable_tag_override" = "false"
      "id" = "counting-service"
      "meta" = {
        "external-source" = "terraform"
      }
      "modify_index" = "24"
      "name" = "counting-service"
      "node_address" = "2886795318-9001-frugo01.environments.katacoda.com"
      "node_id" = ""
      "node_meta" = {}
      "node_name" = "counting"
      "port" = "80"
      "tagged_addresses" = {}
      "tags" = [
        "counting",
      ]
    },
  ]
  "tag" = ""
}

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])
  }
}

You should see the following output after running applying your configuration -- remember to confirm the run with a yes.

$ terraform apply
consul_agents_address_ports = {
  "2eeb51a5-6c05-4a3a-f4d8-3309f92c7ed8" = "172.19.0.3:8300"
  "93cb6357-260c-33f5-82d5-4691a85c4f50" = "172.19.0.4:8300"
  "d9e8043a-5f99-10e5-41ec-44b1df493775" = "172.19.0.2:8300"
}

Next Steps

Congrats! You successfully configured your Terraform Consul Provider and used it to register new nodes and services.

To clean up your demo environment, run:

$ docker-compose down