From 344e8dff6936fc6fe674eb64347b1c30d1c0e8ff Mon Sep 17 00:00:00 2001 From: Ryan Blundon Date: Sat, 7 Mar 2026 22:47:37 -0600 Subject: [PATCH] updated ansible for lightning-lane --- ansible/host_vars/lightning_lane/vars | 37 ++---- ansible/inventory.yml | 16 +-- ansible/playbooks/deploy_traefik.yml | 19 +++ ansible/roles/common/defaults/main.yml | 22 ++++ ansible/roles/common/files/unbound.conf | 4 - ansible/roles/common/handlers/main.yml | 8 ++ ansible/roles/common/main.yml | 22 ++++ ansible/roles/common/tasks/main.yml | 120 +++++------------- .../roles/common/templates/timesyncd.conf.j2 | 4 + ansible/roles/docker-host/handlers/main.yml | 8 ++ ansible/roles/docker-host/main.yml | 88 +++++++++++++ ansible/roles/traefik/defaults/main.yml | 41 ++++++ ansible/roles/traefik/handlers/main.yml | 8 ++ .../tasks/boilerplates/traefik/.gitkeep | 0 ansible/roles/traefik/tasks/main.yml | 75 +++++++++++ .../roles/traefik/templates/compose.yml.j2 | 36 ++++++ .../roles/traefik/templates/dynamic.yml.j2 | 34 +++++ ansible/roles/traefik/templates/env.j2 | 2 + .../roles/traefik/templates/traefik.yml.j2 | 68 ++++++++++ 19 files changed, 480 insertions(+), 132 deletions(-) create mode 100644 ansible/playbooks/deploy_traefik.yml create mode 100644 ansible/roles/common/defaults/main.yml delete mode 100644 ansible/roles/common/files/unbound.conf create mode 100644 ansible/roles/common/handlers/main.yml create mode 100644 ansible/roles/common/main.yml create mode 100644 ansible/roles/common/templates/timesyncd.conf.j2 create mode 100644 ansible/roles/docker-host/handlers/main.yml create mode 100644 ansible/roles/docker-host/main.yml create mode 100644 ansible/roles/traefik/defaults/main.yml create mode 100644 ansible/roles/traefik/handlers/main.yml create mode 100644 ansible/roles/traefik/tasks/boilerplates/traefik/.gitkeep create mode 100644 ansible/roles/traefik/tasks/main.yml create mode 100644 ansible/roles/traefik/templates/compose.yml.j2 create mode 100644 ansible/roles/traefik/templates/dynamic.yml.j2 create mode 100644 ansible/roles/traefik/templates/env.j2 create mode 100644 ansible/roles/traefik/templates/traefik.yml.j2 diff --git a/ansible/host_vars/lightning_lane/vars b/ansible/host_vars/lightning_lane/vars index 23a58ae..64cf1bf 100644 --- a/ansible/host_vars/lightning_lane/vars +++ b/ansible/host_vars/lightning_lane/vars @@ -1,33 +1,12 @@ --- # file: host_vars/lightning_lane/vars -# Traefik Load Balancer Server +# lightning-lane — Traefik Reverse Proxy / Load Balancer +# VM provisioned by Terraform (pre-pipeline bootstrap) -# Supported hypervisors: -# - Proxmox +# ─── Network ───────────────────────────────────────────────────────────────── +ip_address: 10.1.71.35 -platform: "proxmox" - -# This is the Proxmox node where all the VM templates are stored. (Templates are not global.) -proxmox_clone_node: "fantasyland" - -# VM Configuration -vm_os_distribution: "ubuntu" -vm_os_version: "24.04" -vm_size: "small" -vm_storage: "general" - -# Proxmox HA Configuration -ha_group: "pve02" -proxmox_host_target: "pve02" - -# Network Configuration -# IP address for the Traefik load balancer -ip_address: 10.1.71.24 -#vm_mac_address: 'BC:24:11:50:00:80' - -# Software Configuration -# Traefik will be installed and configured on this host - -# VM Template Configuration -#vm_clone_source: "{{ vm_os_distribution }}-{{ vm_os_version }}-{{ vm_size }}" -hostname: "{{ inventory_hostname }}.{{ base_domain }}" \ No newline at end of file +# ─── Application ───────────────────────────────────────────────────────────── +app_role: reverse_proxy +app_name: traefik +app_deployment: docker_compose diff --git a/ansible/inventory.yml b/ansible/inventory.yml index 48fc02c..e028425 100755 --- a/ansible/inventory.yml +++ b/ansible/inventory.yml @@ -1,7 +1,9 @@ # file: inventory.yml proxmox: hosts: - pve0[1:3]: + main-street-usa: + tomorrowland: + fantasyland: ntp_servers: hosts: @@ -27,8 +29,8 @@ prometheus_nodes: load_balancers: hosts: - lightning-lane: - ansible_host: 10.1.71.24 + lightning_lane: + ansible_host: 10.1.71.35 ansible_become: true # node_explorer: # hosts: @@ -55,10 +57,6 @@ n8n_server: hosts: tiki-room: -traefik_server: - hosts: - lightning_lane: - # dhcp_server: # hosts: # matchbox: @@ -67,10 +65,6 @@ backup_servers: hosts: timekeeper: -# freeipa: -# hosts: -# infra01: - # ipaserver: # hosts: # infra01.int.mk-labs.cloud: diff --git a/ansible/playbooks/deploy_traefik.yml b/ansible/playbooks/deploy_traefik.yml new file mode 100644 index 0000000..6e02988 --- /dev/null +++ b/ansible/playbooks/deploy_traefik.yml @@ -0,0 +1,19 @@ +--- +# ------------------------------------------------------------------------------ +# FILE: playbooks/deploy_traefik.yml +# DESCRIPTION: Deploys Traefik reverse proxy on lightning-lane. +# Installs Docker and configures Traefik with Cloudflare DNS-01 +# certificate resolution for *.local.mk-labs.cloud +# +# USAGE: +# ansible-playbook -i inventory.yml playbooks/deploy_traefik.yml +# ------------------------------------------------------------------------------ + +- name: Deploy Traefik reverse proxy + hosts: traefik_server + become: true + + roles: + - common + - docker-host + - traefik diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml new file mode 100644 index 0000000..6818447 --- /dev/null +++ b/ansible/roles/common/defaults/main.yml @@ -0,0 +1,22 @@ +--- +# ------------------------------------------------------------------------------ +# FILE: roles/common/defaults/main.yml +# ------------------------------------------------------------------------------ + +common_timezone: America/Chicago + +common_ntp_server: 10.1.71.33 # sundial + +common_packages: + - curl + - wget + - vim + - htop + - git + - jq + - unzip + - ca-certificates + - gnupg + - lsb-release + - net-tools + - dnsutils diff --git a/ansible/roles/common/files/unbound.conf b/ansible/roles/common/files/unbound.conf deleted file mode 100644 index 6d8f61d..0000000 --- a/ansible/roles/common/files/unbound.conf +++ /dev/null @@ -1,4 +0,0 @@ -[Resolve] -DNS=127.0.0.1 -DNSSEC=yes -DNSStubListener=no \ No newline at end of file diff --git a/ansible/roles/common/handlers/main.yml b/ansible/roles/common/handlers/main.yml new file mode 100644 index 0000000..32d8244 --- /dev/null +++ b/ansible/roles/common/handlers/main.yml @@ -0,0 +1,8 @@ +# ------------------------------------------------------------------------------ +# FILE: roles/common/handlers/main.yml +# ------------------------------------------------------------------------------ + +- name: restart timesyncd + systemd: + name: systemd-timesyncd + state: restarted diff --git a/ansible/roles/common/main.yml b/ansible/roles/common/main.yml new file mode 100644 index 0000000..6818447 --- /dev/null +++ b/ansible/roles/common/main.yml @@ -0,0 +1,22 @@ +--- +# ------------------------------------------------------------------------------ +# FILE: roles/common/defaults/main.yml +# ------------------------------------------------------------------------------ + +common_timezone: America/Chicago + +common_ntp_server: 10.1.71.33 # sundial + +common_packages: + - curl + - wget + - vim + - htop + - git + - jq + - unzip + - ca-certificates + - gnupg + - lsb-release + - net-tools + - dnsutils diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml index ec96dd3..f50b49e 100644 --- a/ansible/roles/common/tasks/main.yml +++ b/ansible/roles/common/tasks/main.yml @@ -1,101 +1,45 @@ # ------------------------------------------------------------------------------ # FILE: roles/common/tasks/main.yml +# DESCRIPTION: Baseline configuration applied to all managed hosts. +# Handles hostname, timezone, core packages, NTP, and +# ansible user setup. # ------------------------------------------------------------------------------ -- name: Disable swap - command: swapoff -a - changed_when: false -- name: Persist swap off by commenting out swap in fstab - replace: - path: /etc/fstab - regexp: '^(\s*)([^#\n]+\s+swap\s+.*)$' - replace: '#\2' - backup: yes +- name: Set hostname + hostname: + name: "{{ inventory_hostname | replace('_', '-') }}" -- name: Load required kernel modules - modprobe: - name: "{{ item }}" - state: present - loop: - - overlay - - br_netfilter +- name: Set timezone + timezone: + name: "{{ common_timezone }}" -- name: Persist kernel modules on boot - copy: - dest: /etc/modules-load.d/k8s.conf - content: | - overlay - br_netfilter +- name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 -- name: Configure required sysctl params for Kubernetes - copy: - dest: /etc/sysctl.d/k8s.conf - content: | - net.bridge.bridge-nf-call-iptables = 1 - net.bridge.bridge-nf-call-ip6tables = 1 - net.ipv4.ip_forward = 1 - -- name: Apply sysctl params without reboot - command: sysctl --system - changed_when: false - -- name: Set SELinux to permissive mode - selinux: - policy: targeted - state: permissive - -- name: Install DNF plugins core for managing repositories - dnf: - name: dnf-plugins-core +- name: Install base utility packages + apt: + name: "{{ common_packages }}" state: present -- name: Add Docker CE repository - command: dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo - args: - creates: /etc/yum.repos.d/docker-ce.repo +- name: Configure NTP to use sundial + template: + src: timesyncd.conf.j2 + dest: /etc/systemd/timesyncd.conf + mode: '0644' + notify: restart timesyncd -- name: Install containerd - dnf: - name: containerd.io - state: present - -- name: Create containerd config directory - file: - path: /etc/containerd - state: directory - -- name: Generate default containerd config and enable SystemdCgroup - shell: | - containerd config default > /etc/containerd/config.toml - sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml - args: - creates: /etc/containerd/config.toml - -- name: Restart and enable containerd service +- name: Ensure systemd-timesyncd is enabled and running systemd: - name: containerd - state: restarted + name: systemd-timesyncd + state: started enabled: yes - daemon_reload: yes -- name: Add Kubernetes repository - yum_repository: - name: kubernetes - description: Kubernetes - baseurl: https://pkgs.k8s.io/core:/stable:/v1.33/rpm/ - gpgkey: https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key - gpgcheck: yes - -- name: Install Kubernetes packages - dnf: - name: - - kubelet - - kubeadm - - kubectl - state: present - disable_excludes: kubernetes - -- name: Enable the kubelet service - systemd: - name: kubelet - enabled: yes \ No newline at end of file +- name: Ensure ansible user has sudo without password + lineinfile: + path: /etc/sudoers.d/{{ ansible_user }} + line: "{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL" + create: yes + mode: '0440' + validate: 'visudo -cf %s' diff --git a/ansible/roles/common/templates/timesyncd.conf.j2 b/ansible/roles/common/templates/timesyncd.conf.j2 new file mode 100644 index 0000000..72ab749 --- /dev/null +++ b/ansible/roles/common/templates/timesyncd.conf.j2 @@ -0,0 +1,4 @@ +# Managed by Ansible — do not edit manually +[Time] +NTP={{ common_ntp_server }} +FallbackNTP=ntp.ubuntu.com diff --git a/ansible/roles/docker-host/handlers/main.yml b/ansible/roles/docker-host/handlers/main.yml new file mode 100644 index 0000000..8943022 --- /dev/null +++ b/ansible/roles/docker-host/handlers/main.yml @@ -0,0 +1,8 @@ +# ------------------------------------------------------------------------------ +# FILE: roles/docker-host/handlers/main.yml +# ------------------------------------------------------------------------------ + +- name: restart docker + systemd: + name: docker + state: restarted diff --git a/ansible/roles/docker-host/main.yml b/ansible/roles/docker-host/main.yml new file mode 100644 index 0000000..d18375e --- /dev/null +++ b/ansible/roles/docker-host/main.yml @@ -0,0 +1,88 @@ +# ------------------------------------------------------------------------------ +# FILE: roles/docker-host/tasks/main.yml +# DESCRIPTION: Prepares an Ubuntu VM to run Docker Compose workloads. +# Installs Docker CE, configures the daemon, and ensures +# the ansible user (wed) can manage containers. +# ------------------------------------------------------------------------------ + +- name: Install prerequisite packages + apt: + name: + - ca-certificates + - curl + - gnupg + - lsb-release + state: present + update_cache: yes + +- name: Create keyrings directory + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' + +- name: Add Docker GPG key + get_url: + url: https://download.docker.com/linux/ubuntu/gpg + dest: /etc/apt/keyrings/docker.asc + mode: '0644' + +- name: Add Docker repository + apt_repository: + repo: >- + deb [arch={{ ansible_architecture | replace('x86_64', 'amd64') }} + signed-by=/etc/apt/keyrings/docker.asc] + https://download.docker.com/linux/ubuntu + {{ ansible_distribution_release }} stable + state: present + filename: docker + +- name: Install Docker CE and Compose plugin + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-compose-plugin + state: present + update_cache: yes + +- name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + +- name: Add ansible user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + +- name: Configure Docker daemon + copy: + dest: /etc/docker/daemon.json + content: | + { + "log-driver": "json-file", + "log-opts": { + "max-size": "10m", + "max-file": "3" + }, + "default-address-pools": [ + { + "base": "172.17.0.0/16", + "size": 24 + } + ] + } + mode: '0644' + notify: restart docker + +- name: Create Docker Compose project directory + file: + path: /opt/docker + state: directory + owner: "{{ ansible_user }}" + group: docker + mode: '0775' diff --git a/ansible/roles/traefik/defaults/main.yml b/ansible/roles/traefik/defaults/main.yml new file mode 100644 index 0000000..4ce12e6 --- /dev/null +++ b/ansible/roles/traefik/defaults/main.yml @@ -0,0 +1,41 @@ +--- +# ------------------------------------------------------------------------------ +# FILE: roles/traefik/defaults/main.yml +# ------------------------------------------------------------------------------ + +# Traefik version (matches boilerplate) +traefik_version: "v3.6.8" + +# Directory structure on the host +traefik_base_dir: /opt/docker/traefik +traefik_config_dir: "{{ traefik_base_dir }}/config" +traefik_certs_dir: "{{ traefik_base_dir }}/certs" +traefik_logs_dir: "{{ traefik_base_dir }}/logs" + +# Network +traefik_network: proxy +traefik_http_port: 80 +traefik_https_port: 443 +traefik_dashboard_port: 8080 +traefik_dashboard_enabled: true + +# TLS / ACME +traefik_tls_enabled: true +traefik_tls_certresolver: cloudflare +traefik_tls_acme_email: "" # Set in vault +traefik_tls_redirect: true + +# Domain +traefik_domain: "local.mk-labs.cloud" + +# Cloudflare API token — set in vault +traefik_cloudflare_api_token: "" + +# Security headers middleware +traefik_security_headers: true + +# Access log +traefik_accesslog_enabled: false + +# Prometheus metrics +traefik_prometheus_enabled: false diff --git a/ansible/roles/traefik/handlers/main.yml b/ansible/roles/traefik/handlers/main.yml new file mode 100644 index 0000000..cc9292c --- /dev/null +++ b/ansible/roles/traefik/handlers/main.yml @@ -0,0 +1,8 @@ +# ------------------------------------------------------------------------------ +# FILE: roles/traefik/handlers/main.yml +# ------------------------------------------------------------------------------ + +- name: restart traefik + community.docker.docker_compose_v2: + project_src: "{{ traefik_base_dir }}" + state: restarted diff --git a/ansible/roles/traefik/tasks/boilerplates/traefik/.gitkeep b/ansible/roles/traefik/tasks/boilerplates/traefik/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ansible/roles/traefik/tasks/main.yml b/ansible/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..ac220fb --- /dev/null +++ b/ansible/roles/traefik/tasks/main.yml @@ -0,0 +1,75 @@ +# ------------------------------------------------------------------------------ +# FILE: roles/traefik/tasks/main.yml +# DESCRIPTION: Deploys Traefik as a Docker Compose stack with Cloudflare +# DNS-01 certificate resolution for *.local.mk-labs.cloud +# ------------------------------------------------------------------------------ + +- name: Create Traefik directory structure + file: + path: "{{ item }}" + state: directory + owner: "{{ ansible_user }}" + group: docker + mode: '0775' + loop: + - "{{ traefik_base_dir }}" + - "{{ traefik_config_dir }}" + - "{{ traefik_config_dir }}/dynamic" + - "{{ traefik_certs_dir }}" + - "{{ traefik_logs_dir }}" + +- name: Create acme.json for certificate storage + file: + path: "{{ traefik_certs_dir }}/acme.json" + state: touch + owner: root + group: root + mode: '0600' + modification_time: preserve + access_time: preserve + +- name: Deploy Traefik static configuration + template: + src: traefik.yml.j2 + dest: "{{ traefik_config_dir }}/traefik.yml" + owner: "{{ ansible_user }}" + group: docker + mode: '0644' + notify: restart traefik + +- name: Deploy dynamic configuration + template: + src: dynamic.yml.j2 + dest: "{{ traefik_config_dir }}/dynamic/default.yml" + owner: "{{ ansible_user }}" + group: docker + mode: '0644' + notify: restart traefik + +- name: Deploy Docker Compose file + template: + src: compose.yml.j2 + dest: "{{ traefik_base_dir }}/compose.yml" + owner: "{{ ansible_user }}" + group: docker + mode: '0644' + notify: restart traefik + +- name: Deploy environment file + template: + src: env.j2 + dest: "{{ traefik_base_dir }}/.env" + owner: "{{ ansible_user }}" + group: docker + mode: '0600' + notify: restart traefik + +- name: Create Traefik Docker network + docker_network: + name: "{{ traefik_network }}" + state: present + +- name: Start Traefik + community.docker.docker_compose_v2: + project_src: "{{ traefik_base_dir }}" + state: present diff --git a/ansible/roles/traefik/templates/compose.yml.j2 b/ansible/roles/traefik/templates/compose.yml.j2 new file mode 100644 index 0000000..d86330e --- /dev/null +++ b/ansible/roles/traefik/templates/compose.yml.j2 @@ -0,0 +1,36 @@ +# ------------------------------------------------------------------------------ +# Traefik Docker Compose +# Managed by Ansible — do not edit manually +# ------------------------------------------------------------------------------ + +services: + traefik: + image: traefik:{{ traefik_version }} + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + ports: + - "{{ traefik_http_port }}:80" + - "{{ traefik_https_port }}:443" +{% if traefik_dashboard_enabled %} + - "{{ traefik_dashboard_port }}:8080" +{% endif %} + env_file: + - .env + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - {{ traefik_config_dir }}/traefik.yml:/etc/traefik/traefik.yml:ro + - {{ traefik_config_dir }}/dynamic:/etc/traefik/dynamic:ro + - {{ traefik_certs_dir }}/acme.json:/certs/acme.json +{% if traefik_accesslog_enabled %} + - {{ traefik_logs_dir }}:/logs +{% endif %} + networks: + - {{ traefik_network }} + labels: + - "traefik.enable=true" + +networks: + {{ traefik_network }}: + external: true diff --git a/ansible/roles/traefik/templates/dynamic.yml.j2 b/ansible/roles/traefik/templates/dynamic.yml.j2 new file mode 100644 index 0000000..f2a2a4e --- /dev/null +++ b/ansible/roles/traefik/templates/dynamic.yml.j2 @@ -0,0 +1,34 @@ +# ------------------------------------------------------------------------------ +# Traefik Dynamic Configuration +# Managed by Ansible — do not edit manually +# ------------------------------------------------------------------------------ + +http: +{% if traefik_security_headers %} + middlewares: + security-headers: + headers: + browserXssFilter: true + contentTypeNosniff: true + frameDeny: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 31536000 + customFrameOptionsValue: "SAMEORIGIN" + customResponseHeaders: + X-Robots-Tag: "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex" + server: "" + X-Forwarded-Proto: "https" +{% endif %} + +tls: + options: + default: + minVersion: VersionTLS12 + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 diff --git a/ansible/roles/traefik/templates/env.j2 b/ansible/roles/traefik/templates/env.j2 new file mode 100644 index 0000000..e7e1075 --- /dev/null +++ b/ansible/roles/traefik/templates/env.j2 @@ -0,0 +1,2 @@ +# Managed by Ansible — do not edit manually +CF_DNS_API_TOKEN={{ traefik_cloudflare_api_token }} diff --git a/ansible/roles/traefik/templates/traefik.yml.j2 b/ansible/roles/traefik/templates/traefik.yml.j2 new file mode 100644 index 0000000..c03ba24 --- /dev/null +++ b/ansible/roles/traefik/templates/traefik.yml.j2 @@ -0,0 +1,68 @@ +# ------------------------------------------------------------------------------ +# Traefik Static Configuration +# Managed by Ansible — do not edit manually +# ------------------------------------------------------------------------------ + +api: + dashboard: {{ traefik_dashboard_enabled | lower }} + insecure: {{ traefik_dashboard_enabled | lower }} + +entryPoints: + web: + address: ":{{ traefik_http_port }}" +{% if traefik_tls_enabled and traefik_tls_redirect %} + http: + redirections: + entryPoint: + to: websecure + scheme: https +{% endif %} + + websecure: + address: ":{{ traefik_https_port }}" +{% if traefik_tls_enabled %} + http: + tls: + certResolver: {{ traefik_tls_certresolver }} + domains: + - main: "{{ traefik_domain }}" + sans: + - "*.{{ traefik_domain }}" +{% endif %} + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + network: {{ traefik_network }} + file: + directory: /etc/traefik/dynamic + watch: true + +{% if traefik_tls_enabled %} +certificatesResolvers: + {{ traefik_tls_certresolver }}: + acme: + email: "{{ traefik_tls_acme_email }}" + storage: /certs/acme.json + dnsChallenge: + provider: {{ traefik_tls_certresolver }} + resolvers: + - "1.1.1.1:53" + - "8.8.8.8:53" +{% endif %} + +{% if traefik_accesslog_enabled %} +accessLog: + filePath: /logs/access.log +{% endif %} + +{% if traefik_prometheus_enabled %} +metrics: + prometheus: + addEntryPointsLabels: true + addServicesLabels: true +{% endif %} + +log: + level: INFO