Initial IaC setup: Nomad, Consul, Terraform, Ansible

- Ansible roles for Nomad and Consul installation
- Nomad job specs for Traefik and monitoring stack
- Terraform configuration for Nomad job management
- Enterprise naming convention inventory
- HashiCorp stack deployment playbook
This commit is contained in:
2026-02-04 08:52:08 -06:00
commit 3d3c1af237
15 changed files with 998 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
job "monitoring" {
datacenters = ["hf-dc1"]
type = "service"
group "prometheus" {
count = 1
network {
port "prometheus" {
static = 9095
}
}
service {
name = "prometheus"
port = "prometheus"
tags = [
"traefik.enable=true",
"traefik.http.routers.prometheus.rule=Host(`prometheus.hobbs.farm`)",
]
check {
type = "http"
path = "/-/healthy"
interval = "10s"
timeout = "2s"
}
}
task "prometheus" {
driver = "docker"
config {
image = "prom/prometheus:v2.48.0"
ports = ["prometheus"]
args = [
"--config.file=/etc/prometheus/prometheus.yml",
"--storage.tsdb.path=/prometheus",
"--web.listen-address=:9095",
"--web.enable-lifecycle",
]
}
template {
data = <<-EOF
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9095']
- job_name: 'nomad'
consul_sd_configs:
- server: '{{ env "NOMAD_IP_prometheus" }}:8500'
services: ['nomad-client', 'nomad']
relabel_configs:
- source_labels: ['__meta_consul_tags']
regex: '.*,metrics,.*'
action: keep
- job_name: 'consul'
static_configs:
- targets: ['{{ env "NOMAD_IP_prometheus" }}:8500']
EOF
destination = "local/prometheus.yml"
}
resources {
cpu = 200
memory = 512
}
volume_mount {
volume = "docker-data"
destination = "/prometheus"
read_only = false
}
}
volume "docker-data" {
type = "host"
source = "docker-data"
read_only = false
}
}
group "grafana" {
count = 1
network {
port "grafana" {
static = 3001
}
}
service {
name = "grafana"
port = "grafana"
tags = [
"traefik.enable=true",
"traefik.http.routers.grafana.rule=Host(`grafana.hobbs.farm`)",
]
check {
type = "http"
path = "/api/health"
interval = "10s"
timeout = "2s"
}
}
task "grafana" {
driver = "docker"
config {
image = "grafana/grafana:10.2.2"
ports = ["grafana"]
}
env {
GF_SERVER_HTTP_PORT = "3001"
GF_SECURITY_ADMIN_PASSWORD = "admin" # TODO: Use Vault
}
resources {
cpu = 200
memory = 256
}
volume_mount {
volume = "docker-data"
destination = "/var/lib/grafana"
read_only = false
}
}
volume "docker-data" {
type = "host"
source = "docker-data"
read_only = false
}
}
group "alertmanager" {
count = 1
network {
port "alertmanager" {
static = 9093
}
}
service {
name = "alertmanager"
port = "alertmanager"
check {
type = "http"
path = "/-/healthy"
interval = "10s"
timeout = "2s"
}
}
task "alertmanager" {
driver = "docker"
config {
image = "prom/alertmanager:v0.26.0"
ports = ["alertmanager"]
}
resources {
cpu = 100
memory = 128
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
job "traefik" {
datacenters = ["hf-dc1"]
type = "service"
# Run on primary node
constraint {
attribute = "${node.class}"
value = "primary"
}
group "traefik" {
count = 1
network {
port "http" {
static = 80
}
port "https" {
static = 443
}
port "api" {
static = 8080
}
}
service {
name = "traefik"
port = "https"
tags = [
"traefik.enable=true",
"traefik.http.routers.api.rule=Host(`traefik.hobbs.farm`)",
"traefik.http.routers.api.service=api@internal",
]
check {
type = "http"
path = "/ping"
port = "api"
interval = "10s"
timeout = "2s"
}
}
task "traefik" {
driver = "docker"
config {
image = "traefik:v3.0"
ports = ["http", "https", "api"]
volumes = [
"local/traefik.yml:/etc/traefik/traefik.yml:ro",
"local/dynamic:/etc/traefik/dynamic:ro",
]
# Mount Docker socket for provider
mount {
type = "bind"
source = "/var/run/docker.sock"
target = "/var/run/docker.sock"
}
}
template {
data = <<-EOF
api:
dashboard: true
insecure: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
consulCatalog:
prefix: traefik
exposedByDefault: false
endpoint:
address: "{{ env "NOMAD_IP_http" }}:8500"
file:
directory: /etc/traefik/dynamic
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: jeremy@hobbs.farm
storage: /data/acme.json
dnsChallenge:
provider: cloudflare
ping:
entryPoint: api
log:
level: INFO
EOF
destination = "local/traefik.yml"
}
resources {
cpu = 200
memory = 256
}
volume_mount {
volume = "docker-data"
destination = "/data"
read_only = false
}
}
volume "docker-data" {
type = "host"
source = "docker-data"
read_only = false
}
}
}