412 lines
16 KiB
YAML
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
|