From c89e8c565abb2caf4800949b97adae0226f2dd6e Mon Sep 17 00:00:00 2001 From: Kevin Midboe Date: Wed, 5 Aug 2026 20:50:58 +0200 Subject: [PATCH] play for installing traefik on lb servers --- group_vars/lb.yml | 23 +++++- plays/traefik.yml | 8 ++ roles/traefik/defaults/main.yml | 14 ++++ roles/traefik/handlers/main.yml | 9 +++ roles/traefik/tasks/install.yml | 77 +++++++++++++++++++ roles/traefik/tasks/main.yml | 1 + .../templates/dynamic-analytics.yml.j2 | 21 +++++ roles/traefik/templates/dynamic-hosts.yml.j2 | 20 +++++ roles/traefik/templates/traefik.service.j2 | 28 +++++++ roles/traefik/templates/traefik.yml.j2 | 38 +++++++++ 10 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 plays/traefik.yml create mode 100644 roles/traefik/defaults/main.yml create mode 100644 roles/traefik/handlers/main.yml create mode 100644 roles/traefik/tasks/install.yml create mode 100644 roles/traefik/tasks/main.yml create mode 100644 roles/traefik/templates/dynamic-analytics.yml.j2 create mode 100644 roles/traefik/templates/dynamic-hosts.yml.j2 create mode 100644 roles/traefik/templates/traefik.service.j2 create mode 100644 roles/traefik/templates/traefik.yml.j2 diff --git a/group_vars/lb.yml b/group_vars/lb.yml index bd66e2f..1297e00 100644 --- a/group_vars/lb.yml +++ b/group_vars/lb.yml @@ -1,6 +1,25 @@ --- +## REQUIRED +# DOMAIN "" +# ANALYTICS_IP "" +# WEB_IPS '["", ""]' + +# apt_packages: +# - nginx custom_firewall_ports: - 80 - 443 -install_nginx_exporter: true -description: "load balancer serving web servers for retailor.io" + # - 9100 # traefik exporter - internal only + # - 8080 # traefik dashboard - internal only +# install_nginx_exporter: true +description: "load balancer serving traefik webserver for retailor.io" + +traefik_image: "traefik:v3.1" +traefik_acme_email: webmaster@retailor.io +traefik_config_dir: "/etc/traefik" +traefik_dynamic_dir: "/etc/traefik/dynamic" + +traefik_web_port: 80 +traefik_websecure_port: 443 +traefik_dashboard_port: 8080 +traefik_dashboard_insecure: true diff --git a/plays/traefik.yml b/plays/traefik.yml new file mode 100644 index 0000000..b002eaf --- /dev/null +++ b/plays/traefik.yml @@ -0,0 +1,8 @@ +--- +- name: Install traefik using Docker + hosts: all + roles: + - role: roles/firewall + - role: roles/docker + - role: roles/env + - role: roles/traefik diff --git a/roles/traefik/defaults/main.yml b/roles/traefik/defaults/main.yml new file mode 100644 index 0000000..2eb03a1 --- /dev/null +++ b/roles/traefik/defaults/main.yml @@ -0,0 +1,14 @@ +traefik_config_dir: "/etc/traefik" +traefik_dynamic_dir: "/etc/traefik/dynamic" +traefik_web_port: 80 +traefik_websecure_port: 443 +traefik_dashboard_port: 8080 +traefik_metrics_port: 8100 +traefik_dashboard_insecure: true # ufw should only allow from tailscale or vnet + +domain: "{{ lookup('env', 'DOMAIN') | default('', true) }}" +# Single source of truth for static routing. Expected to be overridden +# from group_vars, -e, or an env-var lookup at the play level — see +# playbook.yml for the TRAEFIK_ROUTES example. +traefik_web_private_ips: "{{ lookup('env', 'WEB_IPS') | default('[]', true) | from_json }}" +traefik_analytics_private_ip: "{{ lookup('env', 'ANALYTICS_IP') | default('', true) }}" diff --git a/roles/traefik/handlers/main.yml b/roles/traefik/handlers/main.yml new file mode 100644 index 0000000..559310a --- /dev/null +++ b/roles/traefik/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: Reload systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart traefik + ansible.builtin.systemd: + name: traefik + state: restarted diff --git a/roles/traefik/tasks/install.yml b/roles/traefik/tasks/install.yml new file mode 100644 index 0000000..6fc4302 --- /dev/null +++ b/roles/traefik/tasks/install.yml @@ -0,0 +1,77 @@ +--- +- name: Fail fast if no routes were supplied + ansible.builtin.fail: + msg: >- + traefik_web_private_ips is empty. Set the WEB_IPS env var + (JSON list) before running, or override the variable directly, e.g. + -e traefik_web_private_ips='["10.0.1.11","10.0.1.12"]' + when: env_vars.WEB_IPS | length == 0 + +- name: Ensure Traefik config directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "{{ traefik_config_dir }}" + - "{{ traefik_dynamic_dir }}" + +- name: Create acme.json with correct permissions + ansible.builtin.file: + path: "{{ traefik_config_dir }}/acme.json" + state: touch + owner: root + group: root + mode: "0600" + modification_time: preserve + access_time: preserve + +- name: Deploy Traefik static config + ansible.builtin.template: + src: traefik.yml.j2 + dest: "{{ traefik_config_dir }}/traefik.yml" + mode: "0644" + notify: Restart traefik + +- name: Deploy dynamic downstream routes file + ansible.builtin.template: + src: dynamic-hosts.yml.j2 + dest: "{{ traefik_dynamic_dir }}/hosts.yml" + mode: "0644" + notify: Restart traefik + +- name: Deploy dynamic analytics file + ansible.builtin.template: + src: dynamic-analytics.yml.j2 + dest: "{{ traefik_dynamic_dir }}/analytics.yml" + mode: "0644" + notify: Restart traefik + +- name: Pull traefik image + community.docker.docker_image: + name: "{{ traefik_image }}" + source: pull + notify: Restart traefik + +- name: Deploy traefik systemd unit + ansible.builtin.template: + src: traefik.service.j2 + dest: /etc/systemd/system/traefik.service + owner: root + group: root + mode: "0644" + notify: Reload systemd + +- name: Enable and start traefik service + ansible.builtin.systemd: + name: traefik + enabled: true + state: started + daemon_reload: true + +- name: Remove dangling/old traefik images + community.docker.docker_prune: + images: true + images_filters: + dangling: true + diff --git a/roles/traefik/tasks/main.yml b/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..e7a272f --- /dev/null +++ b/roles/traefik/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: install.yml diff --git a/roles/traefik/templates/dynamic-analytics.yml.j2 b/roles/traefik/templates/dynamic-analytics.yml.j2 new file mode 100644 index 0000000..df83184 --- /dev/null +++ b/roles/traefik/templates/dynamic-analytics.yml.j2 @@ -0,0 +1,21 @@ +## Managed by Ansible — do not edit by hand. +## Source: traefik_analytics_private_ip (group_vars/lb.yml) +http: + routers: + analytics: + rule: "Host(`analytics.{{ env_vars.DOMAIN }}`)" + entryPoints: + - websecure + service: analytics-svc + tls: + certResolver: letsencrypt + + services: + analytics-svc: + loadBalancer: + servers: + - url: "http://{{ env_vars.ANALYTICS_IP }}:3000" + healthCheck: + path: /api/heartbeat + interval: "10s" + timeout: "3s" diff --git a/roles/traefik/templates/dynamic-hosts.yml.j2 b/roles/traefik/templates/dynamic-hosts.yml.j2 new file mode 100644 index 0000000..eda70ee --- /dev/null +++ b/roles/traefik/templates/dynamic-hosts.yml.j2 @@ -0,0 +1,20 @@ +## Managed by Ansible — do not edit by hand. +## Source: traefik_web_private_ips (group_vars/lb.yml) +http: + routers: + retailor-web: + rule: "Host(`app.{{ env_vars.DOMAIN }}`)" + service: retailor-web-svc + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + services: + retailor-web-svc: + loadBalancer: + servers: +{% for web_ip in env_vars.WEB_IPS %} + - url: "http://{{ web_ip }}:80" +{% endfor %} + diff --git a/roles/traefik/templates/traefik.service.j2 b/roles/traefik/templates/traefik.service.j2 new file mode 100644 index 0000000..914f99f --- /dev/null +++ b/roles/traefik/templates/traefik.service.j2 @@ -0,0 +1,28 @@ +[Unit] +Description=Traefik reverse proxy +After=docker.service network-online.target +Requires=docker.service +Wants=network-online.target + +[Service] +TimeoutStartSec=0 +Restart=always +RestartSec=5 + +# Clean up any stale container from a previous run/crash +ExecStartPre=-/usr/bin/docker rm -f traefik + +ExecStart=/usr/bin/docker run --rm \ + --name traefik \ + --network host \ + --security-opt no-new-privileges:true \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + -v {{ traefik_config_dir }}/traefik.yml:/etc/traefik/traefik.yml:ro \ + -v {{ traefik_dynamic_dir }}:/etc/traefik/dynamic:ro \ + -v {{ traefik_config_dir }}/acme.json:/etc/traefik/acme.json \ + {{ traefik_image }} + +ExecStop=/usr/bin/docker stop traefik + +[Install] +WantedBy=multi-user.target diff --git a/roles/traefik/templates/traefik.yml.j2 b/roles/traefik/templates/traefik.yml.j2 new file mode 100644 index 0000000..e0d1ebf --- /dev/null +++ b/roles/traefik/templates/traefik.yml.j2 @@ -0,0 +1,38 @@ +entryPoints: + web: + address: ":{{ traefik_web_port }}" + websecure: + address: ":{{ traefik_websecure_port }}" + traefik: + address: ":{{ traefik_dashboard_port }}" + metrics: + address: ":{{ traefik_metrics_port }}" + +providers: + file: + directory: "{{ traefik_dynamic_dir }}" + watch: true + +api: + dashboard: true + insecure: {{ traefik_dashboard_insecure | lower }} + +log: + level: INFO + +accessLog: {} + +certificatesResolvers: + letsencrypt: + acme: + email: "{{ traefik_acme_email }}" + storage: /etc/traefik/acme.json + httpChallenge: + entryPoint: web + +metrics: + prometheus: + entryPoint: metrics + addEntryPointsLabels: true + addServicesLabels: true + addRoutersLabels: true