Files
homelab-optimized/ansible/automation/playbooks/container_dependency_map.yml
Gitea Mirror Bot b2aa602dac
Some checks failed
Documentation / Deploy to GitHub Pages (push) Has been cancelled
Documentation / Build Docusaurus (push) Has been cancelled
Sanitized mirror from private repository - 2026-04-05 12:03:50 UTC
2026-04-05 12:03:50 +00:00

412 lines
16 KiB
YAML

---
- name: Container Dependency Mapping and Orchestration
hosts: all
gather_facts: yes
vars:
dependency_timestamp: "{{ ansible_date_time.iso8601 }}"
dependency_report_dir: "/tmp/dependency_reports"
restart_timeout: 300
health_check_retries: 5
health_check_delay: 10
tasks:
- name: Create dependency reports directory
file:
path: "{{ dependency_report_dir }}"
state: directory
mode: '0755'
delegate_to: localhost
run_once: true
- name: Check if Docker is available
shell: command -v docker >/dev/null 2>&1
register: docker_available
changed_when: false
ignore_errors: yes
- name: Skip Docker tasks if not available
set_fact:
skip_docker: "{{ docker_available.rc != 0 }}"
- name: Get all running containers
shell: |
docker ps --format "{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "No containers"
register: running_containers
changed_when: false
when: not skip_docker
- name: Get all containers (including stopped)
shell: |
docker ps -a --format "{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "No containers"
register: all_containers
changed_when: false
when: not skip_docker
- name: Analyze Docker Compose dependencies
shell: |
echo "=== DOCKER COMPOSE DEPENDENCY ANALYSIS ==="
# Find all docker-compose files
compose_files=$(find /opt /home -name "docker-compose*.yml" -o -name "compose*.yml" 2>/dev/null | head -20)
if [ -z "$compose_files" ]; then
echo "No Docker Compose files found"
exit 0
fi
echo "Found Docker Compose files:"
echo "$compose_files"
echo ""
# Analyze dependencies in each compose file
for compose_file in $compose_files; do
if [ -f "$compose_file" ]; then
echo "=== Analyzing: $compose_file ==="
# Extract service names
services=$(grep -E "^ [a-zA-Z0-9_-]+:" "$compose_file" | sed 's/://g' | sed 's/^ //' | sort)
echo "Services: $(echo $services | tr '\n' ' ')"
# Look for depends_on relationships
echo "Dependencies found:"
grep -A 5 -B 1 "depends_on:" "$compose_file" 2>/dev/null || echo " No explicit depends_on found"
# Look for network dependencies
echo "Networks:"
grep -E "networks:|external_links:" "$compose_file" 2>/dev/null | head -5 || echo " Default networks"
# Look for volume dependencies
echo "Shared volumes:"
grep -E "volumes_from:|volumes:" "$compose_file" 2>/dev/null | head -5 || echo " No shared volumes"
echo ""
fi
done
register: compose_analysis
changed_when: false
when: not skip_docker
- name: Analyze container network connections
shell: |
if ! command -v docker >/dev/null 2>&1; then
echo "Docker not available"
exit 0
fi
echo "=== CONTAINER NETWORK ANALYSIS ==="
# Get all Docker networks
echo "Docker Networks:"
docker network ls --format "table {{.Name}}\t{{.Driver}}\t{{.Scope}}" 2>/dev/null || echo "No networks found"
echo ""
# Analyze each network
networks=$(docker network ls --format "{{.Name}}" 2>/dev/null | grep -v "bridge\|host\|none")
for network in $networks; do
echo "=== Network: $network ==="
containers_in_network=$(docker network inspect "$network" --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null)
if [ -n "$containers_in_network" ]; then
echo "Connected containers: $containers_in_network"
else
echo "No containers connected"
fi
echo ""
done
# Check for port conflicts
echo "=== PORT USAGE ANALYSIS ==="
docker ps --format "{{.Names}}\t{{.Ports}}" 2>/dev/null | grep -E ":[0-9]+->" | while read line; do
container=$(echo "$line" | cut -f1)
ports=$(echo "$line" | cut -f2 | grep -oE "[0-9]+:" | sed 's/://' | sort -n)
if [ -n "$ports" ]; then
echo "$container: $(echo $ports | tr '\n' ' ')"
fi
done
register: network_analysis
changed_when: false
when: not skip_docker
- name: Detect service health endpoints
shell: |
if ! command -v docker >/dev/null 2>&1; then
echo "Docker not available"
exit 0
fi
echo "=== HEALTH ENDPOINT DETECTION ==="
# Common health check patterns
health_patterns="/health /healthz /ping /status /api/health /health/ready /health/live"
# Get containers with exposed ports
docker ps --format "{{.Names}}\t{{.Ports}}" 2>/dev/null | grep -E ":[0-9]+->" | while read line; do
container=$(echo "$line" | cut -f1)
ports=$(echo "$line" | cut -f2 | grep -oE "0\.0\.0\.0:[0-9]+" | cut -d: -f2)
echo "Container: $container"
for port in $ports; do
echo " Port $port:"
for pattern in $health_patterns; do
# Test HTTP health endpoint
if curl -s -f -m 2 "http://localhost:$port$pattern" >/dev/null 2>&1; then
echo " ✅ http://localhost:$port$pattern"
break
elif curl -s -f -m 2 "https://localhost:$port$pattern" >/dev/null 2>&1; then
echo " ✅ https://localhost:$port$pattern"
break
fi
done
done
echo ""
done
register: health_endpoints
changed_when: false
when: not skip_docker
ignore_errors: yes
- name: Analyze container resource dependencies
shell: |
if ! command -v docker >/dev/null 2>&1; then
echo "Docker not available"
exit 0
fi
echo "=== RESOURCE DEPENDENCY ANALYSIS ==="
# Check for containers that might be databases or core services
echo "Potential Core Services (databases, caches, etc.):"
docker ps --format "{{.Names}}\t{{.Image}}" 2>/dev/null | grep -iE "(postgres|mysql|mariadb|redis|mongo|elasticsearch|rabbitmq|kafka)" || echo "No obvious database containers found"
echo ""
# Check for reverse proxies and load balancers
echo "Potential Reverse Proxies/Load Balancers:"
docker ps --format "{{.Names}}\t{{.Image}}" 2>/dev/null | grep -iE "(nginx|apache|traefik|haproxy|caddy)" || echo "No obvious proxy containers found"
echo ""
# Check for monitoring services
echo "Monitoring Services:"
docker ps --format "{{.Names}}\t{{.Image}}" 2>/dev/null | grep -iE "(prometheus|grafana|influxdb|telegraf|node-exporter)" || echo "No obvious monitoring containers found"
echo ""
# Analyze container restart policies
echo "Container Restart Policies:"
docker ps -a --format "{{.Names}}" 2>/dev/null | while read container; do
if [ -n "$container" ]; then
policy=$(docker inspect "$container" --format '{{.HostConfig.RestartPolicy.Name}}' 2>/dev/null)
echo "$container: $policy"
fi
done
register: resource_analysis
changed_when: false
when: not skip_docker
- name: Create dependency map
set_fact:
dependency_map:
timestamp: "{{ dependency_timestamp }}"
hostname: "{{ inventory_hostname }}"
docker_available: "{{ not skip_docker }}"
containers:
running: "{{ running_containers.stdout_lines | default([]) | length }}"
total: "{{ all_containers.stdout_lines | default([]) | length }}"
analysis:
compose_files: "{{ compose_analysis.stdout | default('Docker not available') }}"
network_topology: "{{ network_analysis.stdout | default('Docker not available') }}"
health_endpoints: "{{ health_endpoints.stdout | default('Docker not available') }}"
resource_dependencies: "{{ resource_analysis.stdout | default('Docker not available') }}"
- name: Display dependency analysis
debug:
msg: |
==========================================
🔗 DEPENDENCY ANALYSIS - {{ inventory_hostname }}
==========================================
📊 CONTAINER SUMMARY:
- Running Containers: {{ dependency_map.containers.running }}
- Total Containers: {{ dependency_map.containers.total }}
- Docker Available: {{ dependency_map.docker_available }}
🐳 COMPOSE FILE ANALYSIS:
{{ dependency_map.analysis.compose_files }}
🌐 NETWORK TOPOLOGY:
{{ dependency_map.analysis.network_topology }}
🏥 HEALTH ENDPOINTS:
{{ dependency_map.analysis.health_endpoints }}
📦 RESOURCE DEPENDENCIES:
{{ dependency_map.analysis.resource_dependencies }}
==========================================
- name: Generate dependency report
copy:
content: |
{
"timestamp": "{{ dependency_map.timestamp }}",
"hostname": "{{ dependency_map.hostname }}",
"docker_available": {{ dependency_map.docker_available | lower }},
"container_summary": {
"running": {{ dependency_map.containers.running }},
"total": {{ dependency_map.containers.total }}
},
"analysis": {
"compose_files": {{ dependency_map.analysis.compose_files | to_json }},
"network_topology": {{ dependency_map.analysis.network_topology | to_json }},
"health_endpoints": {{ dependency_map.analysis.health_endpoints | to_json }},
"resource_dependencies": {{ dependency_map.analysis.resource_dependencies | to_json }}
},
"recommendations": [
{% if dependency_map.containers.running > 20 %}
"Consider implementing container orchestration for {{ dependency_map.containers.running }} containers",
{% endif %}
{% if 'No explicit depends_on found' in dependency_map.analysis.compose_files %}
"Add explicit depends_on relationships to Docker Compose files",
{% endif %}
{% if 'No obvious database containers found' not in dependency_map.analysis.resource_dependencies %}
"Ensure database containers have proper backup and recovery procedures",
{% endif %}
"Regular dependency mapping recommended for infrastructure changes"
]
}
dest: "{{ dependency_report_dir }}/{{ inventory_hostname }}_dependencies_{{ ansible_date_time.epoch }}.json"
delegate_to: localhost
- name: Orchestrated container restart (when service_name is provided)
block:
- name: Validate service name parameter
fail:
msg: "service_name parameter is required for restart operations"
when: service_name is not defined
- name: Check if service exists
shell: |
if command -v docker >/dev/null 2>&1; then
docker ps -a --format "{{.Names}}" | grep -x "{{ service_name }}" || echo "not_found"
else
echo "docker_not_available"
fi
register: service_exists
changed_when: false
- name: Fail if service not found
fail:
msg: "Service '{{ service_name }}' not found on {{ inventory_hostname }}"
when: service_exists.stdout == "not_found"
- name: Get service dependencies (from compose file)
shell: |
# Find compose file containing this service
compose_file=""
for file in $(find /opt /home -name "docker-compose*.yml" -o -name "compose*.yml" 2>/dev/null); do
if grep -q "^ {{ service_name }}:" "$file" 2>/dev/null; then
compose_file="$file"
break
fi
done
if [ -n "$compose_file" ]; then
echo "Found in: $compose_file"
# Extract dependencies
awk '/^ {{ service_name }}:/,/^ [a-zA-Z]/ {
if (/depends_on:/) {
getline
while (/^ - /) {
gsub(/^ - /, "")
print $0
getline
}
}
}' "$compose_file" 2>/dev/null || echo "no_dependencies"
else
echo "no_compose_file"
fi
register: service_dependencies
changed_when: false
- name: Stop dependent services first
shell: |
if [ "{{ service_dependencies.stdout }}" != "no_dependencies" ] && [ "{{ service_dependencies.stdout }}" != "no_compose_file" ]; then
echo "Stopping dependent services..."
# This would need to be implemented based on your specific dependency chain
echo "Dependencies found: {{ service_dependencies.stdout }}"
fi
register: stop_dependents
when: cascade_restart | default(false) | bool
- name: Restart the target service
shell: |
echo "Restarting {{ service_name }}..."
docker restart "{{ service_name }}"
# Wait for container to be running
timeout {{ restart_timeout }} bash -c '
while [ "$(docker inspect {{ service_name }} --format "{{.State.Status}}" 2>/dev/null)" != "running" ]; do
sleep 2
done
'
register: restart_result
- name: Verify service health
shell: |
# Wait a moment for service to initialize
sleep {{ health_check_delay }}
# Check if container is running
if [ "$(docker inspect {{ service_name }} --format '{{.State.Status}}' 2>/dev/null)" = "running" ]; then
echo "✅ Container is running"
# Try to find and test health endpoint
ports=$(docker port {{ service_name }} 2>/dev/null | grep -oE "[0-9]+$" | head -1)
if [ -n "$ports" ]; then
for endpoint in /health /healthz /ping /status; do
if curl -s -f -m 5 "http://localhost:$ports$endpoint" >/dev/null 2>&1; then
echo "✅ Health endpoint responding: http://localhost:$ports$endpoint"
exit 0
fi
done
echo "⚠️ No health endpoint found, but container is running"
else
echo "⚠️ No exposed ports found, but container is running"
fi
else
echo "❌ Container is not running"
exit 1
fi
register: health_check
retries: "{{ health_check_retries }}"
delay: "{{ health_check_delay }}"
- name: Restart dependent services
shell: |
if [ "{{ service_dependencies.stdout }}" != "no_dependencies" ] && [ "{{ service_dependencies.stdout }}" != "no_compose_file" ]; then
echo "Restarting dependent services..."
# This would need to be implemented based on your specific dependency chain
echo "Would restart dependencies: {{ service_dependencies.stdout }}"
fi
when: cascade_restart | default(false) | bool
when: service_name is defined and not skip_docker
- name: Summary message
debug:
msg: |
🔗 Dependency analysis complete for {{ inventory_hostname }}
📄 Report saved to: {{ dependency_report_dir }}/{{ inventory_hostname }}_dependencies_{{ ansible_date_time.epoch }}.json
{% if service_name is defined %}
🔄 Service restart summary:
- Target service: {{ service_name }}
- Restart result: {{ restart_result.rc | default('N/A') }}
- Health check: {{ 'PASSED' if health_check.rc == 0 else 'FAILED' }}
{% endif %}
💡 Use -e service_name=<container_name> to restart specific services
💡 Use -e cascade_restart=true to restart dependent services