Martin Buchleitner, Senior IT-Consultant

About the author

Martin Buchleitner is a Senior IT-Consultant for Infralovers and for Commandemy. Twitter github LinkedIn

See all articles by this author

Traefik and Hashicorp Consul with Terraform

Are you looking for a solution to configure a reverse proxy or load balancer without the need to step into a big long configuration file? Maybe Traefik in combination with Consul is the right solution for you.

Traefik has implemented a backend to Consul. This allows you to configure the reverse proxy configuration of frontend and backend in the key value store and Traefik will automatically reload itself according to this configuration changes.

logLevel = "INFO"
defaultEntryPoints = ["http", "https"]

[consul]
  endpoint = "${consul_backend}"
  watch = true
  prefix = "${prefix}"

[entryPoints]
  [entryPoints.http]
    address = ":80"
  [entryPoints.https]
    address = ":443"
    [entryPoints.https.tls]
  
[acme]
  email = "it-is-me@example.com"
  storage = "${prefix}/acme/account"
  entryPoint = "https"
  caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
  [acme.httpChallenge]
    entryPoint = "http"
  [acme.dnsChallenge]
    provider = "dnsimple"
    delayBeforeCheck = 0
[[acme.domains]]
    main = "*.example.com"
    sans = ["example.com"]

In our example we gonna use a docker whoami with scaling to reflect running a load balanced service in the background.

Also we gonna use terraform to rollout this configuration ;-) So first we define all our variables of docker container versions and hosts for docker. Those 2 hosts are already prepared to share the docker port in our local network.

variable "traefik_version" {
  default = "v1.7.11"
}
variable "consul_version" {
  default = "1.5"
}

variable "traefik_host" {
  default = "10.0.0.10"
}
variable "backend_host" {
  default = "10.10.0.20"
}

variable "prefix" {
  default = "traefik"
}

variable "consul_backend" {
  default = "10.10.0.20:8500"
}

variable "domain" {
  default = "example.com"
}

Next we deploy consul on the background host:


provider "docker" {
  host = "tcp://${var.backend_host}:2376/"
}

resource "docker_image" "consul" {
  name = "consul:${var.consul_version}"
}
resource "docker_container" "consul" {

  image    = "${docker_image.consul.latest}"
  name     = "consul"
  must_run = true
  restart  = "always"

  ports {
    internal = 8500
    external = 8500
    protocol = "tcp"
  }
}

And also deploy our whoami image on this host:

resource "docker_image" "whoami" {
  name = "jwilder/whoami"
}
resource "docker_container" "whoami" {

  image    = "${docker_image.whoami.latest}"
  name     = "whoami"
  must_run = true
  restart  = "always"

  ports {
    internal = 8000
    external = 8000
    protocol = "tcp"
  }
}

Then we deploy our traefik proxy

provider "docker" {
  host = "tcp://${var.traefik_host}:2376/"
}

data "template_file" "traefik_config" {
  template = "${file("templates/traefik.tpl")}"

  vars {
    consul_backend = "${var.consul_backend}"
    prefix         = "${var.prefix}"
  }
}

resource "docker_container" "traefik" {
  depends_on = ["consul_keys.whoami"]

  image    = "${docker_image.traefik.latest}"
  name     = "traefik_proxy"
  must_run = true
  restart  = "always"

  ports {
    internal = 80
    external = 80
    protocol = "tcp"
  }

  ports {
    internal = 443
    external = 443
    protocol = "tcp"
  }

  upload {
    content = "${join(",", data.template_file.traefik_config.*.rendered)}"
    file    = "/etc/traefik/traefik.toml"
  }

  volumes {
    host_path      = "/srv/traefik"
    container_path = "/srv/"
    read_only      = false
  }
}

Now all services are up and running but Traefik does not know where the backend is running. So we have to put this information into Consul:

resource "consul_keys" "whoami" {
  key {
    path  = "${var.prefix}/acme/account/"
    value = ""
  }

  key {
    path  = "${var.prefix}/backends/whoami/servers/server1/url"
    value = "http://${var.backend_host}:8000/"
  }

  key {
    path  = "${var.prefix}/backends/whoami/servers/server1/weight"
    value = "10"
  }

  key {
    path  = "${var.prefix}/frontends/frontend1/backend"
    value = "whoami"
  }

  key {
    path  = "${var.prefix}/frontends/frontend1/routes/whoami/rule"
    value = "Host: whoami.${var.domain}"
  }
}

After this configuration step Traefik will reload its configuration and when pointing whoami.example.com to the IP of traefik whoami should respond with its id.