--- # playbooks/docker/deploy_traefik_kop.yml # # Purpose: # Deploy the traefik-kop Swarm service, which bridges Swarm service labels # to Traefik routing via Redis. Once deployed, any Swarm service labelled # with traefik.enable=true will have its routes published automatically. # # Architecture: # Swarm services → traefik-kop → Redis (10.0.0.151:6379) → Traefik (heimdall) # traefik-kop reads Docker service state on the Swarm manager and writes # routing rules to Redis. Traefik's redis provider picks them up in real time. # # Pre-requisites: # - Swarm must be active and swarm-manager-1 (10.0.0.211) must be reachable # - Redis on Heimdall (10.0.0.151:6379) must be running # - community.docker collection installed: ansible-galaxy collection install community.docker # # Usage: # ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_traefik_kop.yml # # Dry-run (no changes to Swarm): # ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_traefik_kop.yml --check # # Tear down: # ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_traefik_kop.yml \ # -e "stack_state=absent" # # Labelling Swarm services for auto-discovery: # After this deploys, Swarm services only need these labels (under deploy.labels): # # deploy: # labels: # - "traefik.enable=true" # - "traefik.http.routers..rule=Host(`.castaldifamily.com`)" # - "traefik.http.routers..entrypoints=websecure" # - "traefik.http.routers..tls.certresolver=cloudflare" # - "traefik.http.services..loadbalancer.server.port=" # # NOTE: Use deploy.labels (not top-level labels) for Swarm services. # Top-level labels apply to the container image; deploy.labels apply # to the Swarm service — which is what traefik-kop reads. - name: Deploy traefik-kop Swarm stack hosts: swarm_managers become: false gather_facts: false vars: traefik_kop_stack_state: "{{ stack_state | default('present') }}" vars_files: - ../../group_vars/all.yml tasks: # -------------------------------------------------- # STEP 1: Assert Swarm is active and reachable # -------------------------------------------------- - name: Verify target is an active Swarm manager ansible.builtin.command: > docker info --format '{{ "{{" }}.Swarm.LocalNodeState{{ "}}" }}|{{ "{{" }}.Swarm.ControlAvailable{{ "}}" }}' register: _swarm_info changed_when: false when: inventory_hostname == groups['swarm_managers'][0] - name: Assert Swarm manager pre-conditions ansible.builtin.assert: that: - _swarm_info.stdout is search('active') - _swarm_info.stdout is search('true') fail_msg: >- {{ inventory_hostname }} must be an active Swarm manager. Current state: {{ _swarm_info.stdout | default('unknown') }} when: inventory_hostname == groups['swarm_managers'][0] # -------------------------------------------------- # STEP 2: Ensure proxy-net overlay network exists # WHY: The traefik-kop stack declares proxy-net as an external overlay. # Future Swarm services join this network to be discoverable by kop. # This network is separate from the bridge of the same name on Heimdall. # WHY attachable: allows standalone containers to join for debugging. # -------------------------------------------------- - name: Ensure proxy-net overlay network exists on Swarm community.docker.docker_network: name: "{{ edge_routing.swarm.proxy_network }}" driver: overlay attachable: true state: present when: inventory_hostname == groups['swarm_managers'][0] tags: [network] # -------------------------------------------------- # STEP 3: Verify Redis is reachable from manager # WHY: Fail fast before deploying — if kop can't reach Redis, the # container will start but immediately fail to publish routes. # -------------------------------------------------- - name: Verify Redis on Heimdall is reachable from Swarm manager ansible.builtin.wait_for: host: "{{ edge_routing.edge_host.ip }}" port: 6379 timeout: 10 state: started when: inventory_hostname == groups['swarm_managers'][0] tags: [preflight] # -------------------------------------------------- # STEP 4: Deploy traefik-kop stack # WHY swarm_stack_deploy role: handles template render, compose validation, # docker stack deploy idempotently, and external network pre-checks. # -------------------------------------------------- - name: Deploy traefik-kop stack ansible.builtin.include_role: name: swarm_stack_deploy vars: stack_name: "traefik-kop" stack_compose_src: "{{ playbook_dir }}/../../templates/stacks/traefik-kop.stack.yml" stack_state: "{{ traefik_kop_stack_state }}" stack_required_external_networks: - "{{ edge_routing.swarm.proxy_network }}" stack_required_directories: [] when: inventory_hostname == groups['swarm_managers'][0] tags: [deploy] # -------------------------------------------------- # STEP 5: Verify the service is running # -------------------------------------------------- - name: Wait for traefik-kop service to converge ansible.builtin.command: > docker service ls --filter name=traefik-kop_traefik-kop --format '{{ "{{" }}.Replicas{{ "}}" }}' register: _kop_replicas retries: 6 delay: 5 until: _kop_replicas.stdout is search('1/1') changed_when: false when: - inventory_hostname == groups['swarm_managers'][0] - traefik_kop_stack_state == 'present' - not ansible_check_mode tags: [verify] - name: Report deployment result ansible.builtin.debug: msg: - "================================================" - "traefik-kop deployment complete." - "================================================" - "Stack : traefik-kop" - "Manager : {{ inventory_hostname }} ({{ ansible_host | default('') }})" - "Redis : {{ edge_routing.integration.redis_addr }}" - "Bind IP : {{ edge_routing.swarm.bind_ip }}" - "Network : {{ edge_routing.swarm.proxy_network }} (overlay)" - "------------------------------------------------" - "To verify routes in Redis, run on Heimdall:" - " docker exec redis redis-cli keys 'traefik/*'" - "================================================" when: inventory_hostname == groups['swarm_managers'][0] tags: [always]