picatz / terraform-google-nomad

📗 Terraform Module for Nomad clusters with Consul on GCP
https://registry.terraform.io/modules/picatz/nomad/google
MIT License
78 stars 16 forks source link

Add initial implementation of a traefik+consul job template #45

Closed picatz closed 2 years ago

picatz commented 2 years ago

This PR adds a better template-ized version of the current traefik.hcl job. This allows new services to be added easier without needing to repeat the same thing over-and-over.

Example Usage

$ cat services.hcl
consul_services = [
  {
    name = "grafana"
    port = 3000
  }
]
$ nomad plan -verbose -var-file=services.hcl traefik_template.hcl
$ nomad run -verbose -var-file=services.hcl traefik_template.hcl

Which will result in the following system job on each client node to handle ingress traffic with Traefik, through to a Consul service mesh using Envoy:

Screen Shot 2021-09-22 at 9 59 38 PM

So that, at a high-level, this can be achieved:

gcp_nomad_traefik_consol_envoy_grafana

Dynamically Generated Configuration

To get a better idea of how this config is dynamically generated from the consul_services variable, here are some examples:

Dynamically Generated Static Ingress Port Allocations for Services

This dynamic block in the job definition will configure a port allocation for the service:

dynamic "port" {
  for_each = var.consul_services
  iterator = service
  labels = [service.value.name]
  content {
    static = service.value.port
    to     = service.value.port
  }
}

Which, for the default value, and example variable file, would result in:

port "grafana" {
  static = 3000
  to     = 3000
}

These allocated ports are the ports that you'll use in your cloud load balancer configurations to handle sending ingress traffic to Nomad using Terraform.

module "grafana_load_balancer" {
  source            = "./modules/load-balancer"
  enabled           = var.grafana_load_balancer_enabled
  region            = var.region
  name              = "grafana-load-balancer"
  ports             = [3000]
  health_check_port = 3000
  target_tags       = ["client"]
  network           = module.network.name
  instances         = formatlist("${format("%s-%s/client", var.region, var.zone)}-%d", range(var.client_instances))
  and_depends_on    = [module.network]
}

Dynamically Generated Consul Service Upstreams

dynamic "service" {
  for_each = var.consul_services
  iterator = service
  content {
    name = "traefik-${service.value.name}"
    port  = service.value.name

    connect {
      sidecar_service {
        proxy {
          upstreams {
            destination_name = service.value.name
            local_bind_port  = service.value.port + 1
          }
        }
      }
    }
  }
}
service {
  name = "traefik-grafana"
  port = "grafana"

  connect {
    sidecar_service {
      proxy {
        upstreams {
          destination_name = "grafana"
          local_bind_port  = 3001
        }
      }
    }
  }
}

Dynamically Generated Traefik TOML

locals {
  dynamic_entry_points = [for i, service in var.consul_services : format("    [entryPoints.%s]\n      address = \":%d\"", service.name, service.port)]

  traefik_toml = <<EOT
[entryPoints]
${join("\n", local.dynamic_entry_points)}

... removed some content not related to entrypoints for brevity ...
  EOT
}
[entryPoints]
    [entryPoints.grafana]
      address = ":3000"