Sanitized mirror from private repository - 2026-04-19 09:48:50 UTC
This commit is contained in:
411
ansible/automation/playbooks/container_dependency_map.yml
Normal file
411
ansible/automation/playbooks/container_dependency_map.yml
Normal file
@@ -0,0 +1,411 @@
|
||||
---
|
||||
- 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
|
||||
Reference in New Issue
Block a user