--- # Container Dependency Orchestrator # Smart restart ordering with dependency management across hosts # Run with: ansible-playbook -i hosts.ini playbooks/container_dependency_orchestrator.yml - name: Container Dependency Orchestration hosts: all gather_facts: yes vars: # Define service dependency tiers (restart order) dependency_tiers: tier_1_infrastructure: - "postgres" - "mariadb" - "mysql" - "redis" - "memcached" - "mongo" tier_2_core_services: - "authentik-server" - "authentik-worker" - "gitea" - "portainer" - "nginx-proxy-manager" tier_3_applications: - "plex" - "sonarr" - "radarr" - "lidarr" - "bazarr" - "prowlarr" - "jellyseerr" - "immich-server" - "paperlessngx" tier_4_monitoring: - "prometheus" - "grafana" - "alertmanager" - "node_exporter" - "snmp_exporter" tier_5_utilities: - "watchtower" - "syncthing" - "ntfy" # Cross-host dependencies cross_host_dependencies: - service: "immich-server" depends_on: - host: "atlantis" service: "postgres" - service: "gitea" depends_on: - host: "calypso" service: "postgres" tasks: - name: Gather container information docker_host_info: containers: yes register: docker_info when: ansible_facts['os_family'] != "Synology" - name: Get Synology container info via docker command shell: docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" register: synology_containers when: ansible_facts['os_family'] == "Synology" become: yes - name: Parse container information set_fact: running_containers: "{{ docker_info.containers | selectattr('State', 'equalto', 'running') | map(attribute='Names') | map('first') | list if docker_info is defined else [] }}" stopped_containers: "{{ docker_info.containers | rejectattr('State', 'equalto', 'running') | map(attribute='Names') | map('first') | list if docker_info is defined else [] }}" - name: Categorize containers by dependency tier set_fact: tier_containers: tier_1: "{{ running_containers | select('match', '.*(' + (dependency_tiers.tier_1_infrastructure | join('|')) + ').*') | list }}" tier_2: "{{ running_containers | select('match', '.*(' + (dependency_tiers.tier_2_core_services | join('|')) + ').*') | list }}" tier_3: "{{ running_containers | select('match', '.*(' + (dependency_tiers.tier_3_applications | join('|')) + ').*') | list }}" tier_4: "{{ running_containers | select('match', '.*(' + (dependency_tiers.tier_4_monitoring | join('|')) + ').*') | list }}" tier_5: "{{ running_containers | select('match', '.*(' + (dependency_tiers.tier_5_utilities | join('|')) + ').*') | list }}" - name: Display container categorization debug: msg: | Container Dependency Analysis for {{ inventory_hostname }}: Tier 1 (Infrastructure): {{ tier_containers.tier_1 | length }} containers {{ tier_containers.tier_1 | join(', ') }} Tier 2 (Core Services): {{ tier_containers.tier_2 | length }} containers {{ tier_containers.tier_2 | join(', ') }} Tier 3 (Applications): {{ tier_containers.tier_3 | length }} containers {{ tier_containers.tier_3 | join(', ') }} Tier 4 (Monitoring): {{ tier_containers.tier_4 | length }} containers {{ tier_containers.tier_4 | join(', ') }} Tier 5 (Utilities): {{ tier_containers.tier_5 | length }} containers {{ tier_containers.tier_5 | join(', ') }} - name: Check container health status shell: docker inspect {{ item }} --format='{{.State.Health.Status}}' 2>/dev/null || echo "no-healthcheck" register: health_checks loop: "{{ running_containers }}" become: yes failed_when: false - name: Identify unhealthy containers set_fact: unhealthy_containers: "{{ health_checks.results | selectattr('stdout', 'equalto', 'unhealthy') | map(attribute='item') | list }}" healthy_containers: "{{ health_checks.results | selectattr('stdout', 'in', ['healthy', 'no-healthcheck']) | map(attribute='item') | list }}" - name: Display health status debug: msg: | Container Health Status for {{ inventory_hostname }}: - Healthy/No Check: {{ healthy_containers | length }} - Unhealthy: {{ unhealthy_containers | length }} {% if unhealthy_containers %} Unhealthy Containers: {% for container in unhealthy_containers %} - {{ container }} {% endfor %} {% endif %} - name: Restart unhealthy containers (Tier 1 first) docker_container: name: "{{ item }}" state: started restart: yes loop: "{{ tier_containers.tier_1 | intersect(unhealthy_containers) }}" when: - restart_unhealthy | default(false) | bool - unhealthy_containers | length > 0 become: yes - name: Wait for Tier 1 containers to be healthy shell: | for i in {1..30}; do status=$(docker inspect {{ item }} --format='{{.State.Health.Status}}' 2>/dev/null || echo "no-healthcheck") if [[ "$status" == "healthy" || "$status" == "no-healthcheck" ]]; then echo "Container {{ item }} is ready" exit 0 fi sleep 10 done echo "Container {{ item }} failed to become healthy" exit 1 loop: "{{ tier_containers.tier_1 | intersect(unhealthy_containers) }}" when: - restart_unhealthy | default(false) | bool - unhealthy_containers | length > 0 become: yes - name: Restart unhealthy containers (Tier 2) docker_container: name: "{{ item }}" state: started restart: yes loop: "{{ tier_containers.tier_2 | intersect(unhealthy_containers) }}" when: - restart_unhealthy | default(false) | bool - unhealthy_containers | length > 0 become: yes - name: Generate dependency report copy: content: | # Container Dependency Report - {{ inventory_hostname }} Generated: {{ ansible_date_time.iso8601 }} ## Container Summary - Total Running: {{ running_containers | length }} - Total Stopped: {{ stopped_containers | length }} - Healthy: {{ healthy_containers | length }} - Unhealthy: {{ unhealthy_containers | length }} ## Dependency Tiers ### Tier 1 - Infrastructure ({{ tier_containers.tier_1 | length }}) {% for container in tier_containers.tier_1 %} - {{ container }} {% endfor %} ### Tier 2 - Core Services ({{ tier_containers.tier_2 | length }}) {% for container in tier_containers.tier_2 %} - {{ container }} {% endfor %} ### Tier 3 - Applications ({{ tier_containers.tier_3 | length }}) {% for container in tier_containers.tier_3 %} - {{ container }} {% endfor %} ### Tier 4 - Monitoring ({{ tier_containers.tier_4 | length }}) {% for container in tier_containers.tier_4 %} - {{ container }} {% endfor %} ### Tier 5 - Utilities ({{ tier_containers.tier_5 | length }}) {% for container in tier_containers.tier_5 %} - {{ container }} {% endfor %} {% if unhealthy_containers %} ## Unhealthy Containers {% for container in unhealthy_containers %} - {{ container }} {% endfor %} {% endif %} {% if stopped_containers %} ## Stopped Containers {% for container in stopped_containers %} - {{ container }} {% endfor %} {% endif %} dest: "/tmp/container_dependency_{{ inventory_hostname }}_{{ ansible_date_time.epoch }}.md" delegate_to: localhost - name: Display report location debug: msg: "Dependency report saved to: /tmp/container_dependency_{{ inventory_hostname }}_{{ ansible_date_time.epoch }}.md"