Errores y depuración en Ansible 🔧
Aquí cubrimos lo que sale mal cuando algo sale mal (manejo de errores) y cómo encontrar el problema cuando un playbook revienta a las 3 AM (depuración).
📋 Contenido del capítulo
- Manejo de errores —
block/rescue/always,failed_when,ignore_errorsy patrones de tolerancia a fallos. - Depuración y troubleshooting —
--check,--diff, módulodebug, verbosity y técnicas de bisección.
Manejo de errores 🛡️
Cómo hacer que tus playbooks sean resistentes a fallos y reaccionen de forma inteligente.
El problema: playbooks frágiles
Cuando un playbook falla, Ansible se detiene inmediatamente. Eso puede ser catastrófico si estás a mitad de un despliegue en producción. Necesitas mecanismos para:
- Ignorar errores esperados
- Capturar fallos y ejecutar acciones de recuperación
- Controlar qué se considera un fallo
- Validar condiciones antes de continuar
🏥 La analogía: el plan de emergencia del hospital
Un hospital no cierra si falla la luz. Tiene un plan B:
- Intenta usar la electricidad normal (block)
- Si falla, activa el generador de emergencia (rescue)
- Siempre notifica al equipo de mantenimiento (always)
Ansible funciona exactamente igual con block, rescue y always.
ignore_errors: la tirita rápida
La forma más simple de manejar errores: decirle a Ansible "si falla, sigue adelante".
- name: Intentar detener un servicio que puede no existir
systemd:
name: servicio-opcional
state: stopped
ignore_errors: yes
⚠️ Cuándo usarlo y cuándo NO
✅ Usar para:
- Servicios que pueden no existir en todos los servidores
- Comprobaciones previas donde el fallo es esperado
- Limpieza de recursos que pueden no estar presentes
❌ NO usar para:
- Tareas críticas donde un fallo indica un problema real
- Sustituir una lógica de manejo de errores adecuada
- "Tapar" errores sin entenderlos
# ❌ MAL: Ignorar un error crítico sin más
- name: Instalar dependencia esencial
apt:
name: paquete-critico
state: present
ignore_errors: yes # Si esto falla, todo lo demás fallará también
# ✅ BIEN: Ignorar solo lo esperado
- name: Eliminar archivo temporal que puede no existir
file:
path: /tmp/deploy-lock.pid
state: absent
ignore_errors: yes
block, rescue y always: el trío de oro
Esta es la forma profesional de manejar errores en Ansible. Funciona igual que try, catch, finally en programación.
Estructura básica
- name: Despliegue con protección ante errores
block:
# === TRY: Lo que quieres hacer ===
- name: Descargar nueva versión de la app
get_url:
url: "https://releases.ejemplo.com/app-{{ version }}.tar.gz"
dest: /tmp/app.tar.gz
- name: Desplegar nueva versión
unarchive:
src: /tmp/app.tar.gz
dest: /opt/app/
remote_src: yes
- name: Reiniciar aplicación
systemd:
name: myapp
state: restarted
rescue:
# === CATCH: Si algo falla en el block ===
- name: Registrar el fallo
debug:
msg: "¡FALLO EN EL DESPLIEGUE! Iniciando rollback..."
- name: Restaurar versión anterior
copy:
src: /opt/app/backup/
dest: /opt/app/current/
remote_src: yes
- name: Reiniciar con versión anterior
systemd:
name: myapp
state: restarted
always:
# === FINALLY: Siempre se ejecuta ===
- name: Limpiar archivos temporales
file:
path: /tmp/app.tar.gz
state: absent
- name: Enviar notificación al equipo
debug:
msg: "Proceso de despliegue completado (éxito o rollback)"
Simulación del lanzamiento de un cohete
Ejemplo real: actualización de base de datos
- name: Migración de base de datos con protección
hosts: barcelona
become: true
tasks:
- name: Migración segura de base de datos
block:
- name: Crear backup antes de migrar
shell: |
mysqldump --all-databases > /backup/pre-migration-$(date +%Y%m%d).sql
register: backup_result
- name: Ejecutar migración
shell: mysql < /opt/migrations/v2.0.sql
register: migration_result
- name: Verificar integridad
shell: mysqlcheck --all-databases --check
register: check_result
rescue:
- name: La migración falló, restaurar backup
shell: mysql < /backup/pre-migration-*.sql
- name: Notificar al equipo de DB
debug:
msg: |
❌ Migración fallida.
Backup restaurado automáticamente.
Error: {{ ansible_failed_result.msg | default('Desconocido') }}
always:
- name: Registrar resultado en log
lineinfile:
path: /var/log/migrations.log
line: "{{ ansible_date_time.iso8601 }} - Migración v2.0 - {{ 'OK' if migration_result is defined and migration_result.rc == 0 else 'FALLIDA' }}"
create: yes
Nota:
changed_when(controlar cuándo Ansible reporta cambios) se cubre en el módulo 4, en la sección de cómo hacercommand/shellidempotentes. Aquí nos centramos enfailed_when, que es el complemento para controlar qué se considera un fallo.
failed_when: redefinir qué es un fallo
A veces Ansible piensa que algo falló cuando en realidad está todo bien, o viceversa. Con failed_when tú decides qué es un fallo.
# El comando grep devuelve rc=1 si no encuentra nada.
# Ansible lo interpreta como "error", pero NO lo es.
# ❌ Sin failed_when (Ansible cree que falló)
- name: Buscar errores en el log
shell: grep "ERROR" /var/log/app.log
register: log_errors
# Si no hay errores, grep devuelve rc=1 → Ansible dice "FAILED"
# ✅ Con failed_when (tú defines el fallo)
- name: Buscar errores en el log
shell: grep "ERROR" /var/log/app.log
register: log_errors
failed_when: log_errors.rc not in [0, 1]
# rc=0 → encontró errores (ok, queremos saberlo)
# rc=1 → no encontró errores (ok, mejor aún)
# rc=2+ → algo raro pasó con grep (ESTO sí es un error)
Más ejemplos prácticos
- name: Verificar que la API responde correctamente
uri:
url: "http://localhost:{{ app_port }}/health"
return_content: yes
register: health_check
failed_when: "'healthy' not in health_check.content"
- name: Ejecutar script de validación
shell: /opt/scripts/validate.sh
register: validation
failed_when:
- validation.rc != 0
- "'WARNING' not in validation.stdout"
# Falla SOLO si el rc no es 0 Y no hay un warning esperado
assert: validar antes de actuar
El módulo assert es como un guardia de seguridad en la puerta. Verifica condiciones antes de que el playbook haga algo peligroso.
- name: Validaciones previas al despliegue en producción
hosts: servers
become: true
tasks:
- name: Verificar requisitos mínimos del servidor
assert:
that:
- ansible_facts['memtotal_mb'] >= 4096
- ansible_facts['processor_vcpus'] >= 2
- ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 5368709120
fail_msg: |
❌ El servidor no cumple los requisitos mínimos:
- RAM: {{ ansible_facts['memtotal_mb'] }}MB (mínimo 4096MB)
- CPUs: {{ ansible_facts['processor_vcpus'] }} (mínimo 2)
success_msg: "✅ Servidor validado. Procediendo con el despliegue."
- name: Verificar que la versión es correcta
assert:
that:
- app_version is defined
- app_version is match('^[0-9]+\.[0-9]+\.[0-9]+$')
fail_msg: "La versión '{{ app_version | default('NO DEFINIDA') }}' no es válida. Formato esperado: X.Y.Z"
- name: Verificar conectividad con servicios externos
uri:
url: "https://api.ejemplo.com/status"
status_code: 200
register: api_status
- name: Confirmar que la API está operativa
assert:
that:
- api_status.status == 200
fail_msg: "La API externa no está disponible. Abortando despliegue."
any_errors_fatal: parar todo si uno falla
Cuando despliegas en múltiples servidores, a veces necesitas que si uno falla, se detengan todos. Es la diferencia entre un fallo parcial controlado y un desastre.
- name: Actualización crítica en cluster de contenedores
hosts: servers
any_errors_fatal: true # Si un servidor falla, TODOS paran
serial: 2 # Desplegar de 2 en 2
tasks:
- name: Descargar nueva imagen del contenedor
community.docker.docker_image:
name: "pabpereza/quotes:{{ app_version }}"
source: pull
force_source: true
- name: Recrear contenedor con la nueva versión
community.docker.docker_container:
name: quotes
image: "pabpereza/quotes:{{ app_version }}"
state: started
restart_policy: unless-stopped
recreate: true
ports:
- "8000:8000"
- name: Verificar salud post-actualización
uri:
url: "http://localhost:8000/health"
status_code: 200
retries: 3
delay: 5
¿Cuándo usarlo?
- Clusters donde la consistencia es crítica (todos deben tener la misma versión)
- Bases de datos en modo réplica (si el primario falla, no toques los secundarios)
- Balanceadores de carga donde necesitas al menos N servidores sanos
retries y delay: reintentos inteligentes
A veces un servicio necesita unos segundos para arrancar. En lugar de fallar, reintenta.
- name: Esperar a que la aplicación esté lista
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: health
retries: 10 # Intentar hasta 10 veces
delay: 6 # Esperar 6 segundos entre intentos
until: health.status == 200
- name: Esperar a que el puerto esté escuchando
wait_for:
port: "{{ app_port }}"
host: localhost
delay: 3
timeout: 60
state: started
Ejemplo: esperar convergencia de un cluster
- name: Esperar a que el cluster Elasticsearch esté verde
uri:
url: "http://localhost:9200/_cluster/health"
return_content: yes
register: cluster_health
retries: 30
delay: 10
until: "'green' in cluster_health.content"
Práctica completa: despliegue resiliente de contenedor 🚀
Vamos a combinar todo lo aprendido en un playbook de despliegue profesional de contenedores. La ventaja del enfoque Docker es que el "rollback" es simplemente volver a la imagen anterior: sin backups de ficheros, sin parar el proceso a mano.
- name: Despliegue Resiliente de Contenedor Web
hosts: servers
become: true
serial: "30%" # Desplegar al 30% de los servidores a la vez
any_errors_fatal: true
pre_tasks:
- name: Validar requisitos del servidor
assert:
that:
- ansible_facts['memtotal_mb'] >= 2048
- app_version is defined
fail_msg: "Servidor no cumple requisitos o falta app_version"
- name: Sacar servidor del balanceador
uri:
url: "http://{{ lb_host }}/api/servers/{{ inventory_hostname }}/disable"
method: POST
delegate_to: localhost
changed_when: false
tasks:
- name: Despliegue de la nueva versión del contenedor
block:
- name: Guardar imagen actual para rollback
community.docker.docker_container_info:
name: quotes
register: current_container
failed_when: false # Puede no existir aún
- name: Descargar nueva imagen
community.docker.docker_image:
name: "pabpereza/quotes:{{ app_version }}"
source: pull
force_source: true
- name: Recrear contenedor con la nueva versión
community.docker.docker_container:
name: quotes
image: "pabpereza/quotes:{{ app_version }}"
state: started
restart_policy: unless-stopped
recreate: true
ports:
- "8000:8000"
env:
POSTGRES_HOST: "{{ postgres_host }}"
POSTGRES_USER: "{{ postgres_user }}"
POSTGRES_PASSWORD: "{{ postgres_password }}"
POSTGRES_DB: "{{ postgres_db }}"
- name: Verificar salud de la aplicación
uri:
url: "http://localhost:8000/health"
status_code: 200
retries: 5
delay: 3
until: health_result.status == 200
register: health_result
rescue:
- name: ROLLBACK - Restaurar imagen anterior
community.docker.docker_container:
name: quotes
image: >-
{{ current_container.container.Config.Image
| default('pabpereza/quotes:stable') }}
state: started
restart_policy: unless-stopped
recreate: true
ports:
- "8000:8000"
when: current_container is defined
- name: ROLLBACK - Verificar que funciona
uri:
url: "http://localhost:8000/health"
status_code: 200
retries: 3
delay: 5
- name: ROLLBACK - Notificar el fallo
debug:
msg: |
❌ Despliegue de {{ app_version }} fallido.
Revertido a imagen anterior.
Error: {{ ansible_failed_result.msg | default('Desconocido') }}
always:
- name: Limpiar imágenes huérfanas
community.docker.docker_prune:
images: true
images_filters:
dangling: true
Depuración y troubleshooting 🔍
Cómo encontrar y solucionar problemas cuando tus playbooks no funcionan como esperabas.
La realidad: las cosas fallan
No importa lo bueno que seas, tus playbooks van a fallar. La diferencia entre un principiante y un profesional es la velocidad a la que diagnostican y resuelven el problema.
🕵️ La analogía: el detective
Depurar es como resolver un caso policial:
- Examinas la escena del crimen (los logs de error)
- Buscas pistas (verbose mode, debug)
- Interrogas a los sospechosos (variables, facts, conexión SSH)
- Reconstruyes los hechos (paso a paso)
Niveles de verbosidad (-v)
La primera herramienta de diagnóstico es subir el volumen de la salida de Ansible.
# Normal (solo resultados)
ansible-playbook site.yml
# Verbose (-v): Muestra el resultado de cada tarea
ansible-playbook site.yml -v
# Más verbose (-vv): Incluye detalles de conexión
ansible-playbook site.yml -vv
# Muy verbose (-vvv): Incluye comandos SSH completos
ansible-playbook site.yml -vvv
# Máximo verbose (-vvvv): Incluye debug de conexión SSH
ansible-playbook site.yml -vvvv
¿Qué nivel usar?
| Nivel | Cuándo usarlo |
|---|---|
-v | "¿Qué devolvió esta tarea?" |
-vv | "¿Se está conectando al host correcto?" |
-vvv | "¿Qué comando SSH está ejecutando?" |
-vvvv | "¿Por qué no se conecta por SSH?" |
Ejemplo práctico
# Tu playbook falla en una tarea de apt
$ ansible-playbook site.yml -v
TASK [Instalar Nginx] ***************
fatal: [target1]: FAILED! => {
"changed": false,
"msg": "No package matching 'ngnix' is available"
# ^^^^^^ ¡Typo! Es "nginx"
}
Con -v pudiste ver el mensaje de error completo que te reveló el problema.
El módulo debug: tu mejor amigo
El módulo debug es el equivalente a console.log() o print(). Te permite inspeccionar variables en cualquier punto del playbook.
Inspeccionar variables
- name: Ver el valor de una variable
debug:
var: my_variable
- name: Ver con formato personalizado
debug:
msg: "El puerto es {{ app_port }} y el host es {{ db_host }}"
- name: Ver tipo y contenido de una variable compleja
debug:
var: ansible_facts
verbosity: 2 # Solo se muestra con -vv o más
Inspeccionar el resultado de una tarea
- name: Ejecutar comando
shell: systemctl status nginx
register: nginx_status
ignore_errors: yes
- name: Ver TODO el resultado (estructura completa)
debug:
var: nginx_status
- name: Ver solo lo que necesitas
debug:
msg: |
RC: {{ nginx_status.rc }}
Stdout: {{ nginx_status.stdout_lines | join('\n') }}
Stderr: {{ nginx_status.stderr }}
Changed: {{ nginx_status.changed }}
Failed: {{ nginx_status.failed }}
Inspeccionar Facts del sistema
- name: Ver TODOS los facts (genera MUCHO output)
debug:
var: ansible_facts
- name: Ver facts específicos
debug:
msg: |
SO: {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
IP: {{ ansible_facts['default_ipv4']['address'] }}
RAM: {{ ansible_facts['memtotal_mb'] }}MB
Disco libre (/): {{ ansible_mounts | selectattr('mount','equalto','/') | map(attribute='size_available') | first | human_readable }}
Truco: debug condicional
# Solo muestra debug si la variable tiene un valor inesperado
- name: Alerta si hay poco disco
debug:
msg: "⚠️ ¡Poco espacio en disco! Solo {{ disk_free }}MB libres"
when: disk_free | int < 1024
Modo check (dry run) y diff
--check: simulación sin cambios
Ansible ejecuta el playbook pero no aplica ningún cambio. Es como un ensayo general.
ansible-playbook site.yml --check
Limitaciones:
- Tareas que dependen de resultados de tareas anteriores pueden fallar (porque la tarea anterior no se ejecutó realmente)
- Módulos
shellycommandse saltan por defecto
# Forzar que un comando se ejecute incluso en modo check
- name: Verificar versión de la app
shell: /opt/app/bin/app --version
register: app_version
check_mode: no # Se ejecuta incluso con --check
changed_when: false
--diff: ver qué cambia
Muestra las diferencias exactas que Ansible va a aplicar en archivos.
# Ver qué cambiará (sin aplicar)
ansible-playbook site.yml --check --diff
# Aplicar y ver los cambios
ansible-playbook site.yml --diff
Ejemplo de salida:
TASK [Copiar configuración de Nginx] ***
--- before: /etc/nginx/nginx.conf
+++ after: /etc/nginx/nginx.conf
@@ -1,3 +1,3 @@
server {
- listen 80;
+ listen 8080;
server_name example.com;
--step y --start-at-task: control manual
Ejecución paso a paso
ansible-playbook site.yml --step
Ansible te preguntará antes de cada tarea:
TASK [Instalar Nginx] ****
Perform task: TASK: Instalar Nginx (N)o/(y)es/(c)ontinue:
- y: Ejecutar esta tarea
- n: Saltar esta tarea
- c: Ejecutar esta y todas las siguientes sin preguntar
Empezar desde una tarea específica
# Saltar todo hasta la tarea "Configurar firewall"
ansible-playbook site.yml --start-at-task "Configurar firewall"
Es muy útil cuando el playbook falla a mitad de camino y quieres reiniciar desde donde falló sin repetir todo.
Listar tareas sin ejecutar
# Ver todas las tareas del playbook
ansible-playbook site.yml --list-tasks
# Ver solo las de un tag
ansible-playbook site.yml --list-tasks --tags config
Errores comunes y sus soluciones
Error 1: "Unreachable" - no se puede conectar
fatal: [target1]: UNREACHABLE! => {
"msg": "Failed to connect to the host via ssh"
}
Diagnóstico:
# 1. ¿Puedes hacer SSH manualmente?
ssh -p 55000 ansible@localhost
# 2. ¿El host es correcto?
ansible -i inventory.yml target1 -m ping -vvvv
# 3. ¿La clave SSH es correcta?
ssh -p 55000 ansible@localhost
# 4. ¿El puerto SSH es el estándar?
ssh -p 55000 ansible@localhost
Soluciones comunes:
# inventory.ini
target1 ansible_host=localhost ansible_port=55000 ansible_user=ansible ansible_ssh_pass=ansible
Error 2: "Permission denied" - falta sudo
fatal: [target1]: FAILED! => {
"msg": "Missing sudo password"
}
Solución:
# Opción 1: Pedir contraseña sudo
ansible-playbook site.yml --ask-become-pass
# Opción 2: Configurar sudo sin contraseña (en el servidor)
echo "deploy ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/deploy
Error 3: "Module failure" - el módulo no existe
fatal: [target1]: FAILED! => {
"msg": "The module custom_module was not found"
}
Diagnóstico:
# ¿El módulo existe?
ansible-doc -l | grep custom_module
# ¿Es de una collection?
ansible-galaxy collection list
# ¿Falta instalar la collection?
ansible-galaxy collection install community.general
Error 4: "Variable undefined" - variable no definida
fatal: [target1]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'app_port' is undefined"
}
Diagnóstico:
# ¿Dónde debería estar definida?
grep -r "app_port" group_vars/ host_vars/ inventory/
# ¿Qué variables ve Ansible para este host?
ansible -i inventory.yml target1 -m debug -a "var=hostvars[inventory_hostname]"
Error 5: Indentación YAML incorrecta
ERROR! Syntax Error while loading YAML.
mapping values are not allowed in this context
Diagnóstico:
# Validar sintaxis YAML
python -c "import yaml; yaml.safe_load(open('playbook.yml'))"
# Usar yamllint para más detalle
pip install yamllint
yamllint playbook.yml
# Verificar sintaxis del playbook
ansible-playbook --syntax-check playbook.yml
Causa más frecuente: mezclar tabs y espacios, o indentación incorrecta.
Error 6: "Vault password not provided"
ERROR! Attempting to decrypt but no vault secrets found
Solución:
# Proporcionar contraseña
ansible-playbook site.yml --ask-vault-pass
# O usar archivo de contraseña
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Herramientas de diagnóstico avanzadas
ansible-config dump: ver configuración efectiva
# Ver TODA la configuración activa
ansible-config dump
# Ver solo las que difieren del default
ansible-config dump --only-changed
# Ver de dónde viene cada configuración
ansible-config dump -v
ansible-inventory: inspeccionar el inventario
# Ver el inventario completo en JSON
ansible-inventory -i inventory.yml --list
# Ver el grafo de grupos
ansible-inventory -i inventory.yml --graph
# Ver variables de un host específico
ansible-inventory -i inventory.yml --host target1
Ejemplo de salida de --graph:
@all:
|--@servers:
| |--@madrid:
| | |--target1
| | |--target2
| |--@barcelona:
| | |--target3
|--@ungrouped:
ansible-console: shell interactiva
# Abrir consola interactiva contra el grupo servers
ansible-console -i inventory.yml servers --become
# Dentro de la consola, ejecutar módulos directamente
target1,target2> ping
target1,target2> shell uptime
target1,target2> setup filter=ansible_distribution
target1,target2> apt name=htop state=present
Es perfecto para explorar y probar módulos antes de escribirlos en un playbook.
Callback plugins: mejorar la salida
Los callback plugins cambian cómo Ansible muestra los resultados.
yaml - Salida legible
# ansible.cfg
[defaults]
stdout_callback = yaml
Antes (default):
ok: [target1] => {"ansible_facts": {"ansible_distribution": "Ubuntu"}}
Después (yaml):
ok: [target1] =>
ansible_facts:
ansible_distribution: Ubuntu
timer - Tiempo de ejecución
# ansible.cfg
[defaults]
callbacks_enabled = timer, profile_tasks
Añade el tiempo total al final y el tiempo de cada tarea:
TASK [Instalar paquetes] ****
ok: [target1]
--- 12.45s
Playbook run took 0 days, 0 hours, 2 minutes, 34 seconds
debug - Más detalles en errores
# ansible.cfg
[defaults]
stdout_callback = debug
Muestra stdout y stderr separados y formateados cuando una tarea falla.
Estrategia de depuración: el método sistemático
Cuando algo falla, sigue este proceso ordenado:
Checklist de depuración rápida
# 1. ¿La sintaxis es correcta?
ansible-playbook --syntax-check playbook.yml
# 2. ¿Los hosts son accesibles?
ansible -i inventory.yml all -m ping
# 3. ¿Las variables están definidas?
ansible-inventory -i inventory.yml --host target1
# 4. ¿Qué haría sin ejecutar?
ansible-playbook playbook.yml --check --diff -v
# 5. ¿Dónde exactamente falla?
ansible-playbook playbook.yml -vvv --start-at-task "Tarea problemática"
Práctica: debuggeando un playbook roto 🐛
A continuación tienes un playbook con varios errores intencionados. Tu misión es encontrarlos y arreglarlos usando las técnicas de este capítulo.
El playbook roto
- name: Desplegar contenedor web
hosts: servers
become: true
vars:
container_port: 8080
tasks:
- name: Desplegar Ngnix # 🐛 Error 1: ¿Ves algo raro en el nombre de la imagen?
community.docker.docker_container:
name: web
image: ngnix:latest
state: started
- name: Crear directorio de contenido
file:
path: "/srv/{{ app_name }}/html" # 🐛 Error 2: ¿Está definida app_name?
state: directory
- name: Verificar si el contenedor responde
shell: "curl -s http://localhost:{{ container_port }}"
register: health
# 🐛 Error 3: ¿Qué pasa si el contenedor no está listo aún?
- name: Copiar configuración de Nginx
copy:
src: ./files/nginx.conf
dest: /tmp/nginx.conf
mode: '0644' # 🐛 Error 4: Indentación con tabs
- name: Reiniciar contenedor
command: docker restart web # 🐛 Error 5: ¿command o docker_container?
Proceso de diagnóstico
# Paso 1: Verificar sintaxis
ansible-playbook broken.yml --syntax-check
# → Detecta Error 4 (tabs vs espacios)
# Paso 2: Ejecutar en dry-run con verbosidad
ansible-playbook broken.yml --check -v
# → Detecta Error 1 (imagen "ngnix" no existe)
# → Detecta Error 2 (app_name undefined)
# Paso 3: Añadir debug tasks para investigar Error 3
# Añadir failed_when: false para que curl no falle si el contenedor no está listo
# Paso 4: Corregir Error 5 usando el módulo docker_container con recreate: true
El playbook corregido
- name: Desplegar contenedor web
hosts: servers
become: true
vars:
container_port: 8080
app_name: notastack # ✅ Fix 2: Variable definida
tasks:
- name: Desplegar Nginx
community.docker.docker_container:
name: web
image: nginx:latest # ✅ Fix 1: Nombre correcto
state: started
ports:
- "{{ container_port }}:80"
- name: Crear directorio de contenido
file:
path: "/srv/{{ app_name }}/html"
state: directory
- name: Verificar si el contenedor responde
uri:
url: "http://localhost:{{ container_port }}"
status_code: 200
register: health
failed_when: false # ✅ Fix 3: No fallar si no está listo aún
changed_when: false
- name: Copiar configuración de Nginx
copy:
src: ./files/nginx.conf
dest: /tmp/nginx.conf
mode: '0644' # ✅ Fix 4: Espacios, no tabs
- name: Aplicar nueva configuración recreando el contenedor
community.docker.docker_container: # ✅ Fix 5: Módulo correcto
name: web
image: nginx:latest
state: started
recreate: true