From 3d3c1af237080ce36231d69f3b3ce5641b42bb5a Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 4 Feb 2026 08:52:08 -0600 Subject: [PATCH] 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 --- README.md | 117 ++++++++++++ ansible/inventory/hosts.yml | 109 +++++++++++ ansible/playbooks/hashicorp-stack.yml | 66 +++++++ ansible/roles/consul/handlers/main.yml | 6 + ansible/roles/consul/tasks/main.yml | 56 ++++++ ansible/roles/consul/templates/consul.hcl.j2 | 34 ++++ ansible/roles/consul/templates/server.hcl.j2 | 15 ++ ansible/roles/nomad/handlers/main.yml | 6 + ansible/roles/nomad/tasks/main.yml | 76 ++++++++ ansible/roles/nomad/templates/client.hcl.j2 | 34 ++++ ansible/roles/nomad/templates/nomad.hcl.j2 | 53 ++++++ ansible/roles/nomad/templates/server.hcl.j2 | 14 ++ nomad/jobs/monitoring.nomad | 186 +++++++++++++++++++ nomad/jobs/traefik.nomad | 129 +++++++++++++ terraform/environments/prod/main.tf | 97 ++++++++++ 15 files changed, 998 insertions(+) create mode 100644 README.md create mode 100644 ansible/inventory/hosts.yml create mode 100644 ansible/playbooks/hashicorp-stack.yml create mode 100644 ansible/roles/consul/handlers/main.yml create mode 100644 ansible/roles/consul/tasks/main.yml create mode 100644 ansible/roles/consul/templates/consul.hcl.j2 create mode 100644 ansible/roles/consul/templates/server.hcl.j2 create mode 100644 ansible/roles/nomad/handlers/main.yml create mode 100644 ansible/roles/nomad/tasks/main.yml create mode 100644 ansible/roles/nomad/templates/client.hcl.j2 create mode 100644 ansible/roles/nomad/templates/nomad.hcl.j2 create mode 100644 ansible/roles/nomad/templates/server.hcl.j2 create mode 100644 nomad/jobs/monitoring.nomad create mode 100644 nomad/jobs/traefik.nomad create mode 100644 terraform/environments/prod/main.tf diff --git a/README.md b/README.md new file mode 100644 index 0000000..38733fc --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# Hobbs.farm Infrastructure as Code + +Enterprise-grade infrastructure management using HashiCorp stack + Ansible. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ HOBBS.FARM IaC │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ TERRAFORM │────▶│ ANSIBLE │────▶│ NOMAD │ │ +│ │ │ │ │ │ │ │ +│ │ • Infra │ │ • Config │ │ • Deploy │ │ +│ │ • State │ │ • Install │ │ • Scale │ │ +│ │ • DNS │ │ • Secure │ │ • Monitor │ │ +│ └───────────┘ └───────────┘ └───────────┘ │ +│ │ │ │ │ +│ └────────────────┴────────────────┘ │ +│ │ │ +│ ┌───────────────────────┴───────────────────────┐ │ +│ │ HASHICORP STACK │ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ +│ │ │ NOMAD │ │ CONSUL │ │ VAULT │ │ │ +│ │ │ orchest │◄─┤ service │◄─┤ secrets │ │ │ +│ │ │ ration │ │ mesh │ │ mgmt │ │ │ +│ │ └─────────┘ └─────────┘ └─────────┘ │ │ +│ └───────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ HOSTS │ │ +│ │ hf-pdocker-01 (primary) hf-pdocker-02 (secondary) │ +│ │ 192.168.86.192 192.168.86.45 │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Directory Structure + +``` +hobbs-iac/ +├── terraform/ # Infrastructure provisioning +│ ├── environments/ # Per-environment configs +│ │ └── prod/ +│ ├── modules/ # Reusable modules +│ └── nomad-jobs/ # Nomad job definitions (HCL) +├── ansible/ # Configuration management +│ ├── inventory/ # Host inventory +│ ├── playbooks/ # Automation playbooks +│ ├── roles/ # Ansible roles +│ └── group_vars/ # Variables +├── nomad/ # Nomad-specific configs +│ ├── jobs/ # Job specifications +│ ├── policies/ # ACL policies +│ └── templates/ # Job templates +├── consul/ # Consul configs +│ ├── config/ # Consul configuration +│ └── policies/ # ACL policies +├── vault/ # Vault configs +│ ├── policies/ # Access policies +│ └── secrets/ # Secret definitions (encrypted) +└── scripts/ # Utility scripts +``` + +## Quick Start + +### 1. Bootstrap Hosts (Ansible) +```bash +cd ansible +ansible-playbook -i inventory/hosts.yml playbooks/bootstrap.yml +``` + +### 2. Install HashiCorp Stack +```bash +ansible-playbook -i inventory/hosts.yml playbooks/hashicorp-stack.yml +``` + +### 3. Initialize Terraform +```bash +cd terraform/environments/prod +terraform init +terraform plan +terraform apply +``` + +### 4. Deploy Services +```bash +# Deploy all Nomad jobs +nomad job run ../nomad-jobs/traefik.nomad +nomad job run ../nomad-jobs/monitoring.nomad +# ... etc +``` + +## Naming Convention + +See [Naming Convention Standard](docs/naming-convention.md) + +| Type | Format | Example | +|------|--------|---------| +| Physical | `hf-p-##` | `hf-pdocker-01` | +| Virtual | `hf-v-##` | `hf-vapp-01` | +| Container | `hf-c-##` | `hf-cproxy-01` | +| Network | `hf-n-##` | `hf-nfw-01` | +| Storage | `hf-s-##` | `hf-snas-01` | + +## Prerequisites + +- Ansible 2.15+ +- Terraform 1.5+ +- Nomad 1.6+ +- Consul 1.16+ +- Vault 1.15+ + +## License + +Private - Hobbs.farm internal use only diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml new file mode 100644 index 0000000..23d620b --- /dev/null +++ b/ansible/inventory/hosts.yml @@ -0,0 +1,109 @@ +--- +# Hobbs.farm Infrastructure Inventory +# Naming: -- + +all: + vars: + ansible_python_interpreter: /usr/bin/python3 + domain: hobbs.farm + timezone: America/Chicago + + # HashiCorp versions + nomad_version: "1.7.3" + consul_version: "1.17.2" + vault_version: "1.15.4" + + # Cluster settings + datacenter: hf-dc1 + + # Data paths + nomad_data_dir: /opt/nomad + consul_data_dir: /opt/consul + vault_data_dir: /opt/vault + + children: + # ========================================= + # Nomad Cluster + # ========================================= + nomad_servers: + hosts: + hf-pdocker-01: + nomad_server: true + nomad_client: true + nomad_bootstrap_expect: 1 + + nomad_clients: + hosts: + hf-pdocker-01: + nomad_client: true + nomad_node_class: "primary" + hf-pdocker-02: + nomad_client: true + nomad_node_class: "secondary" + + # ========================================= + # Consul Cluster + # ========================================= + consul_servers: + hosts: + hf-pdocker-01: + consul_server: true + consul_bootstrap_expect: 1 + + consul_clients: + hosts: + hf-pdocker-01: + hf-pdocker-02: + + # ========================================= + # Vault Cluster + # ========================================= + vault_servers: + hosts: + hf-pdocker-01: + vault_server: true + + # ========================================= + # Physical Hosts + # ========================================= + physical: + children: + docker_hosts: + hosts: + hf-pdocker-01: + ansible_host: 192.168.86.192 + ansible_user: glyptic + ansible_become_password: "{{ vault_sudo_password }}" + legacy_name: hobbs + role: primary + + hf-pdocker-02: + ansible_host: 192.168.86.45 + ansible_user: glyptic + ansible_ssh_private_key_file: ~/.ssh/id_docker_server + legacy_name: peggy + role: secondary + status: degraded + + nvr_hosts: + hosts: + hf-pnvr-01: + ansible_host: 192.168.86.181 + ansible_user: glyptic + ansible_ssh_private_key_file: ~/.ssh/id_docker_server + legacy_name: bart + + # ========================================= + # Network & Storage (reference only) + # ========================================= + network: + hosts: + hf-nfw-01: + ansible_host: 192.168.86.1 + legacy_name: firewalla + + storage: + hosts: + hf-snas-01: + ansible_host: 192.168.86.30 + legacy_name: nas diff --git a/ansible/playbooks/hashicorp-stack.yml b/ansible/playbooks/hashicorp-stack.yml new file mode 100644 index 0000000..3121057 --- /dev/null +++ b/ansible/playbooks/hashicorp-stack.yml @@ -0,0 +1,66 @@ +--- +# Install HashiCorp Stack (Nomad, Consul, Vault) +# Run: ansible-playbook -i inventory/hosts.yml playbooks/hashicorp-stack.yml + +- name: Install Consul + hosts: consul_servers:consul_clients + become: yes + roles: + - consul + tags: [consul] + +- name: Install Vault + hosts: vault_servers + become: yes + roles: + - vault + tags: [vault] + +- name: Install Nomad + hosts: nomad_servers:nomad_clients + become: yes + roles: + - nomad + tags: [nomad] + +- name: Bootstrap Nomad cluster + hosts: nomad_servers[0] + become: yes + tasks: + - name: Wait for Nomad to be ready + ansible.builtin.uri: + url: "http://{{ ansible_host }}:4646/v1/status/leader" + method: GET + status_code: 200 + register: nomad_leader + until: nomad_leader.status == 200 + retries: 30 + delay: 5 + + - name: Display Nomad leader + ansible.builtin.debug: + msg: "Nomad leader: {{ nomad_leader.json }}" + tags: [nomad, bootstrap] + +- name: Verify cluster health + hosts: nomad_servers[0] + become: yes + tasks: + - name: Check Nomad node status + ansible.builtin.command: nomad node status + register: nomad_nodes + changed_when: false + + - name: Display nodes + ansible.builtin.debug: + var: nomad_nodes.stdout_lines + + - name: Check Consul members + ansible.builtin.command: consul members + register: consul_members + changed_when: false + + - name: Display Consul members + ansible.builtin.debug: + var: consul_members.stdout_lines + tags: [verify] diff --git a/ansible/roles/consul/handlers/main.yml b/ansible/roles/consul/handlers/main.yml new file mode 100644 index 0000000..bf5c2b4 --- /dev/null +++ b/ansible/roles/consul/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart Consul + ansible.builtin.systemd: + name: consul + state: restarted + daemon_reload: yes diff --git a/ansible/roles/consul/tasks/main.yml b/ansible/roles/consul/tasks/main.yml new file mode 100644 index 0000000..f51a303 --- /dev/null +++ b/ansible/roles/consul/tasks/main.yml @@ -0,0 +1,56 @@ +--- +# Consul Installation Role + +- name: Add HashiCorp GPG key + ansible.builtin.apt_key: + url: https://apt.releases.hashicorp.com/gpg + state: present + +- name: Add HashiCorp repository + ansible.builtin.apt_repository: + repo: "deb https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main" + state: present + filename: hashicorp + +- name: Install Consul + ansible.builtin.apt: + name: "consul={{ consul_version }}-1" + state: present + update_cache: yes + +- name: Create Consul directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: consul + group: consul + mode: '0755' + loop: + - "{{ consul_data_dir }}" + - "{{ consul_data_dir }}/data" + - /etc/consul.d + +- name: Configure Consul + ansible.builtin.template: + src: consul.hcl.j2 + dest: /etc/consul.d/consul.hcl + owner: consul + group: consul + mode: '0640' + notify: Restart Consul + +- name: Configure Consul server (if server) + ansible.builtin.template: + src: server.hcl.j2 + dest: /etc/consul.d/server.hcl + owner: consul + group: consul + mode: '0640' + when: consul_server | default(false) + notify: Restart Consul + +- name: Enable and start Consul + ansible.builtin.systemd: + name: consul + enabled: yes + state: started diff --git a/ansible/roles/consul/templates/consul.hcl.j2 b/ansible/roles/consul/templates/consul.hcl.j2 new file mode 100644 index 0000000..8e154aa --- /dev/null +++ b/ansible/roles/consul/templates/consul.hcl.j2 @@ -0,0 +1,34 @@ +# Consul Configuration +# Managed by Ansible + +datacenter = "{{ datacenter }}" +data_dir = "{{ consul_data_dir }}/data" +node_name = "{{ inventory_hostname }}" + +bind_addr = "{{ ansible_host }}" +client_addr = "0.0.0.0" + +ui_config { + enabled = true +} + +# DNS settings +dns_config { + allow_stale = true +} + +# Service mesh +connect { + enabled = true +} + +# Telemetry +telemetry { + prometheus_retention_time = "24h" + disable_hostname = true +} + +# Performance +performance { + raft_multiplier = 1 +} diff --git a/ansible/roles/consul/templates/server.hcl.j2 b/ansible/roles/consul/templates/server.hcl.j2 new file mode 100644 index 0000000..d6f314e --- /dev/null +++ b/ansible/roles/consul/templates/server.hcl.j2 @@ -0,0 +1,15 @@ +# Consul Server Configuration + +server = true +bootstrap_expect = {{ consul_bootstrap_expect | default(1) }} + +# Raft settings +raft_protocol = 3 + +# Autopilot +autopilot { + cleanup_dead_servers = true + last_contact_threshold = "200ms" + max_trailing_logs = 250 + server_stabilization_time = "10s" +} diff --git a/ansible/roles/nomad/handlers/main.yml b/ansible/roles/nomad/handlers/main.yml new file mode 100644 index 0000000..4c90261 --- /dev/null +++ b/ansible/roles/nomad/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart Nomad + ansible.builtin.systemd: + name: nomad + state: restarted + daemon_reload: yes diff --git a/ansible/roles/nomad/tasks/main.yml b/ansible/roles/nomad/tasks/main.yml new file mode 100644 index 0000000..2b8cc18 --- /dev/null +++ b/ansible/roles/nomad/tasks/main.yml @@ -0,0 +1,76 @@ +--- +# Nomad Installation Role + +- name: Install dependencies + ansible.builtin.apt: + name: + - curl + - gnupg + - lsb-release + - apt-transport-https + state: present + update_cache: yes + +- name: Add HashiCorp GPG key + ansible.builtin.apt_key: + url: https://apt.releases.hashicorp.com/gpg + state: present + +- name: Add HashiCorp repository + ansible.builtin.apt_repository: + repo: "deb https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main" + state: present + filename: hashicorp + +- name: Install Nomad + ansible.builtin.apt: + name: "nomad={{ nomad_version }}-1" + state: present + update_cache: yes + +- name: Create Nomad directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: nomad + group: nomad + mode: '0755' + loop: + - "{{ nomad_data_dir }}" + - "{{ nomad_data_dir }}/data" + - /etc/nomad.d + +- name: Configure Nomad + ansible.builtin.template: + src: nomad.hcl.j2 + dest: /etc/nomad.d/nomad.hcl + owner: nomad + group: nomad + mode: '0640' + notify: Restart Nomad + +- name: Configure Nomad server (if server) + ansible.builtin.template: + src: server.hcl.j2 + dest: /etc/nomad.d/server.hcl + owner: nomad + group: nomad + mode: '0640' + when: nomad_server | default(false) + notify: Restart Nomad + +- name: Configure Nomad client + ansible.builtin.template: + src: client.hcl.j2 + dest: /etc/nomad.d/client.hcl + owner: nomad + group: nomad + mode: '0640' + when: nomad_client | default(false) + notify: Restart Nomad + +- name: Enable and start Nomad + ansible.builtin.systemd: + name: nomad + enabled: yes + state: started diff --git a/ansible/roles/nomad/templates/client.hcl.j2 b/ansible/roles/nomad/templates/client.hcl.j2 new file mode 100644 index 0000000..10df9a1 --- /dev/null +++ b/ansible/roles/nomad/templates/client.hcl.j2 @@ -0,0 +1,34 @@ +# Nomad Client Configuration + +client { + enabled = true + + node_class = "{{ nomad_node_class | default('default') }}" + + # Resource reservations + reserved { + cpu = 500 + memory = 512 + } + + # Host volumes for persistent data + host_volume "docker-data" { + path = "/mnt/nas/docker" + read_only = false + } + + host_volume "media" { + path = "/mnt/nas/media" + read_only = false + } + + # Meta information + meta { + "rack" = "{{ rack | default('rack-1') }}" + "datacenter" = "{{ datacenter }}" + "legacy_name" = "{{ legacy_name | default('unknown') }}" + } + + # Network interface + network_interface = "{{ ansible_default_ipv4.interface | default('eth0') }}" +} diff --git a/ansible/roles/nomad/templates/nomad.hcl.j2 b/ansible/roles/nomad/templates/nomad.hcl.j2 new file mode 100644 index 0000000..557e26c --- /dev/null +++ b/ansible/roles/nomad/templates/nomad.hcl.j2 @@ -0,0 +1,53 @@ +# Nomad Configuration +# Managed by Ansible - Do not edit manually + +datacenter = "{{ datacenter }}" +data_dir = "{{ nomad_data_dir }}/data" +bind_addr = "0.0.0.0" + +advertise { + http = "{{ ansible_host }}" + rpc = "{{ ansible_host }}" + serf = "{{ ansible_host }}" +} + +# Enable ACLs (after bootstrap) +# acl { +# enabled = true +# } + +# Consul integration +consul { + address = "127.0.0.1:8500" + + server_service_name = "nomad" + client_service_name = "nomad-client" + + auto_advertise = true + server_auto_join = true + client_auto_join = true +} + +# Vault integration (after Vault setup) +# vault { +# enabled = true +# address = "http://127.0.0.1:8200" +# } + +telemetry { + collection_interval = "10s" + disable_hostname = true + prometheus_metrics = true + publish_allocation_metrics = true + publish_node_metrics = true +} + +plugin "docker" { + config { + allow_privileged = true + volumes { + enabled = true + } + extra_labels = ["job_name", "task_group_name", "task_name", "namespace", "node_name"] + } +} diff --git a/ansible/roles/nomad/templates/server.hcl.j2 b/ansible/roles/nomad/templates/server.hcl.j2 new file mode 100644 index 0000000..7686606 --- /dev/null +++ b/ansible/roles/nomad/templates/server.hcl.j2 @@ -0,0 +1,14 @@ +# Nomad Server Configuration + +server { + enabled = true + bootstrap_expect = {{ nomad_bootstrap_expect | default(1) }} + + # Raft settings + raft_protocol = 3 + + # Server join (for multi-server setup) + # server_join { + # retry_join = ["hf-pdocker-01.hobbs.farm"] + # } +} diff --git a/nomad/jobs/monitoring.nomad b/nomad/jobs/monitoring.nomad new file mode 100644 index 0000000..91a2ec0 --- /dev/null +++ b/nomad/jobs/monitoring.nomad @@ -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 + } + } + } +} diff --git a/nomad/jobs/traefik.nomad b/nomad/jobs/traefik.nomad new file mode 100644 index 0000000..9044d36 --- /dev/null +++ b/nomad/jobs/traefik.nomad @@ -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 + } + } +} diff --git a/terraform/environments/prod/main.tf b/terraform/environments/prod/main.tf new file mode 100644 index 0000000..ff85e8b --- /dev/null +++ b/terraform/environments/prod/main.tf @@ -0,0 +1,97 @@ +# Hobbs.farm Production Environment +# Terraform configuration for Nomad job management + +terraform { + required_version = ">= 1.5.0" + + required_providers { + nomad = { + source = "hashicorp/nomad" + version = "~> 2.0" + } + consul = { + source = "hashicorp/consul" + version = "~> 2.18" + } + } + + # Backend configuration for state storage + # Uncomment when ready to use remote state + # backend "consul" { + # address = "hf-pdocker-01.hobbs.farm:8500" + # scheme = "http" + # path = "terraform/hobbs-iac/prod" + # } +} + +# Provider configurations +provider "nomad" { + address = "http://hf-pdocker-01.hobbs.farm:4646" + # secret_id = var.nomad_token # Uncomment after ACL setup +} + +provider "consul" { + address = "hf-pdocker-01.hobbs.farm:8500" + datacenter = "hf-dc1" +} + +# Variables +variable "environment" { + default = "prod" +} + +variable "datacenter" { + default = "hf-dc1" +} + +# Deploy Nomad jobs +resource "nomad_job" "traefik" { + jobspec = file("${path.module}/../../nomad-jobs/traefik.nomad") + + hcl2 { + enabled = true + } +} + +resource "nomad_job" "monitoring" { + jobspec = file("${path.module}/../../nomad-jobs/monitoring.nomad") + + hcl2 { + enabled = true + } + + depends_on = [nomad_job.traefik] +} + +# Consul KV for configuration +resource "consul_keys" "config" { + datacenter = var.datacenter + + key { + path = "hobbs/config/environment" + value = var.environment + } + + key { + path = "hobbs/config/datacenter" + value = var.datacenter + } + + key { + path = "hobbs/config/domain" + value = "hobbs.farm" + } +} + +# Outputs +output "nomad_address" { + value = "http://hf-pdocker-01.hobbs.farm:4646" +} + +output "consul_address" { + value = "http://hf-pdocker-01.hobbs.farm:8500" +} + +output "traefik_job_id" { + value = nomad_job.traefik.id +}