¿Cómo solucionar el error 'UNREACHABLE!' en Ansible?

AnsibleBeginner
Practicar Ahora

Introducción

Ansible es una poderosa herramienta de automatización de infraestructura que simplifica la gestión de entornos de TI complejos. Sin embargo, los usuarios a menudo se encuentran con el error 'UNREACHABLE!', que puede interrumpir los flujos de trabajo de automatización. Este error generalmente ocurre cuando Ansible no puede establecer una conexión con los hosts de destino. En este laboratorio, aprenderá a identificar, solucionar problemas y prevenir el error 'UNREACHABLE!' en sus implementaciones de Ansible.

Al final de este laboratorio, comprenderá las causas comunes de los problemas de conectividad en Ansible y podrá implementar soluciones efectivas para garantizar que su automatización se ejecute sin problemas.

Configuración del Entorno Ansible

En este paso, configuraremos un entorno Ansible básico para trabajar. Instalaremos Ansible, configuraremos los archivos esenciales y nos aseguraremos de que todo esté listo para nuestra experimentación.

Instalación de Ansible

Primero, instalemos Ansible en la máquina virtual (VM) de LabEx utilizando los siguientes comandos:

sudo apt update
sudo apt install -y ansible

Esto instalará la última versión de Ansible disponible en los repositorios de Ubuntu. Una vez que la instalación esté completa, verifique la instalación comprobando la versión de Ansible:

ansible --version

Debería ver una salida similar a la siguiente, que muestra la versión de Ansible y los detalles de configuración:

ansible [core 2.12.x]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/labex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/labex/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.x (default, Apr 8 2022, 09:04:19) [GCC 11.2.0]
  jinja version = 3.0.3
  libyaml = True

Creación de un Directorio de Trabajo

Creemos un directorio dedicado para nuestro trabajo con Ansible:

mkdir -p ~/project/ansible-lab
cd ~/project/ansible-lab

Creación del Archivo de Configuración de Ansible

Ahora, creemos un archivo de configuración básico de Ansible en nuestro directorio del proyecto:

cat > ansible.cfg << 'EOF'
[defaults]
inventory = ./inventory
host_key_checking = False
remote_user = labex
EOF

Este archivo de configuración:

  • Especifica la ubicación de nuestro archivo de inventario
  • Deshabilita la verificación de la clave del host SSH (útil para entornos de laboratorio)
  • Establece el usuario remoto predeterminado en 'labex'

Creación del Archivo de Inventario

El archivo de inventario define los hosts que Ansible administrará. Creemos un archivo de inventario simple:

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

Este inventario contiene dos grupos:

  • local: Contiene solo localhost, que utiliza una conexión local
  • virtual: Contiene un host virtual que usaremos para demostrar el error 'UNREACHABLE!'

El virtual-host está configurado con una dirección IP (10.10.10.10) que no existe en nuestro entorno, lo que nos ayudará a generar el error 'UNREACHABLE!'.

Prueba de Ansible

Probemos nuestra configuración de Ansible ejecutando un comando ping simple contra el host local:

ansible local -m ping

Debería ver una respuesta exitosa como:

localhost | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Esto confirma que Ansible funciona correctamente para la conexión local. Ahora, intentemos hacer ping al host virtual, lo que debería fallar:

ansible virtual -m ping

Esto producirá el error 'UNREACHABLE!' porque el host no existe:

virtual-host | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.10.10 port 22: Connection timed out",
    "unreachable": true
}

Ahora ha configurado Ansible con éxito y ha creado un escenario donde ocurre el error 'UNREACHABLE!', que investigaremos en el siguiente paso.

Comprensión del Error 'UNREACHABLE!'

En el paso anterior, encontramos el error 'UNREACHABLE!' al intentar conectarnos a un host inexistente. Ahora, comprendamos el error con más detalle y exploremos las causas comunes.

Análisis del Mensaje de Error

Veamos el mensaje de error que recibimos:

virtual-host | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.10.10 port 22: Connection timed out",
    "unreachable": true
}

El mensaje de error proporciona información valiosa:

  • UNREACHABLE! indica que Ansible no pudo establecer una conexión con el host
  • El campo msg nos dice por qué: "Failed to connect to the host via ssh" (Fallo al conectar con el host a través de ssh)
  • El error específico es "Connection timed out" (Tiempo de conexión agotado), lo que significa que Ansible intentó conectarse pero no recibió respuesta

Causas Comunes de los Errores 'UNREACHABLE!'

El error 'UNREACHABLE!' puede ocurrir por varias razones:

  1. Problemas de Red: El host podría estar detrás de un firewall, o podría haber problemas de conectividad de red.
  2. Información Incorrecta del Host: El nombre de host o la dirección IP en el inventario podrían ser incorrectos.
  3. Configuración de SSH: SSH podría no estar configurado correctamente en el host de destino.
  4. Problemas de Autenticación: La clave SSH o la contraseña podrían ser incorrectas.
  5. Indisponibilidad del Host: El host podría estar inactivo o inalcanzable.

Creación de un Playbook de Prueba

Creemos un playbook simple para demostrar aún más el error:

cat > test_playbook.yml << 'EOF'
---
- name: Test Connectivity
  hosts: all
  gather_facts: no
  tasks:
    - name: Ping the hosts
      ping:
EOF

Este playbook intenta hacer ping a todos los hosts definidos en nuestro inventario. Ejecutémoslo:

ansible-playbook test_playbook.yml

Debería ver una salida similar a:

PLAY [Test Connectivity] ************************************************

TASK [Ping the hosts] ***************************************************
ok: [localhost]
fatal: [virtual-host]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.10.10 port 22: Connection timed out", "unreachable": true}

PLAY RECAP *************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
virtual-host               : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0

El playbook tuvo éxito para localhost pero falló para virtual-host con el error 'UNREACHABLE!'.

Inspección de los Niveles de Verbose de Ansible

Ansible proporciona diferentes niveles de verbosidad para ayudar a diagnosticar problemas. Intentemos ejecutar el playbook con mayor verbosidad:

ansible-playbook test_playbook.yml -v

Para obtener una salida aún más detallada, use -vv o -vvv:

ansible-playbook test_playbook.yml -vvv

La opción -vvv proporciona la salida más detallada, mostrando los comandos SSH exactos que Ansible está intentando usar:

<virtual-host> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o User=labex -o ConnectTimeout=10 -o ControlPath=/home/labex/.ansible/cp/ansible-ssh-%h-%p-%r 10.10.10.10 '/bin/sh -c '"'"'echo ~labex && sleep 0'"'"''

Este nivel de detalle puede ser invaluable para solucionar problemas de conexión SSH.

Uso de la Opción --limit

Cuando se trabaja con un inventario grande, puede limitar a Ansible a ejecutar comandos contra hosts o grupos específicos utilizando la opción --limit:

ansible-playbook test_playbook.yml --limit localhost

Este comando solo ejecutará el playbook contra localhost, evitando el error 'UNREACHABLE!' de virtual-host.

Ahora que entendemos mejor el error 'UNREACHABLE!', pasemos a la solución de problemas y la corrección de estos problemas en el siguiente paso.

Solución de Problemas y Corrección de Errores 'UNREACHABLE!'

Ahora que entendemos qué causa los errores 'UNREACHABLE!', aprendamos a solucionar y corregirlos. Utilizaremos una variedad de enfoques para diagnosticar y resolver problemas de conectividad.

Corrección de Problemas de Inventario

Una de las causas más comunes de los errores 'UNREACHABLE!' es la información incorrecta del inventario. Corrijamos nuestro archivo de inventario:

cd ~/project/ansible-lab

Primero, actualicemos nuestro archivo de inventario para incluir un host válido. En este entorno de laboratorio, nos centraremos en usar localhost con diferentes métodos de conexión para demostrar técnicas de solución de problemas:

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[ssh_local]
local-ssh ansible_host=127.0.0.1 ansible_connection=ssh

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

Hemos agregado un nuevo grupo ssh_local con un host que intentará conectarse a localhost a través de SSH en lugar del método de conexión local.

Prueba de Conectividad SSH Directamente

Antes de usar Ansible, siempre es una buena idea probar la conectividad SSH directamente:

ssh 127.0.0.1

Es posible que se le solicite una contraseña o vea un mensaje sobre la clave del host. Esta es una buena señal, ya que significa que la conectividad SSH está funcionando, pero es posible que deba configurar SSH correctamente para Ansible.

Presione Ctrl+C para salir si se atasca en el indicador de contraseña.

Configuración de Claves SSH para Autenticación sin Contraseña

Ansible normalmente usa claves SSH para la autenticación. Configuremos el acceso SSH sin contraseña a localhost:

## Generate an SSH key if you don't have one
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa

## Add the key to authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

## Set proper permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Ahora intente conectarse a localhost a través de SSH:

ssh 127.0.0.1

Debería poder conectarse sin que se le solicite una contraseña. Escriba exit para volver a su sesión original.

Prueba de Ansible con Conexión SSH

Ahora, probemos Ansible con la conexión SSH a localhost:

ansible ssh_local -m ping

Si la configuración de SSH es correcta, debería ver una respuesta exitosa:

local-ssh | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Si aún ve un error 'UNREACHABLE!', agreguemos más parámetros de conexión a nuestro archivo de inventario:

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[ssh_local]
local-ssh ansible_host=127.0.0.1 ansible_connection=ssh ansible_user=labex ansible_ssh_private_key_file=~/.ssh/id_rsa

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

Intente el comando ping de nuevo:

ansible ssh_local -m ping

Uso de Ansible con una Configuración SSH Personalizada

A veces, necesita configuraciones SSH más complejas. Creemos un archivo de configuración SSH personalizado:

mkdir -p ~/.ssh
cat > ~/.ssh/config << 'EOF'
Host local-ssh
    HostName 127.0.0.1
    User labex
    IdentityFile ~/.ssh/id_rsa
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null
EOF

## Set proper permissions
chmod 600 ~/.ssh/config

Actualice el inventario para usar la entrada de configuración SSH:

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[ssh_local]
local-ssh

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

Pruebe la conexión de nuevo:

ansible ssh_local -m ping

Creación de un Playbook para Probar Todas las Conexiones

Creemos un playbook completo para probar todas nuestras conexiones:

cat > connection_test.yml << 'EOF'
---
- name: Test Local Connection
  hosts: local
  gather_facts: no
  tasks:
    - name: Ping local
      ping:
      register: local_ping
    
    - name: Display local ping result
      debug:
        var: local_ping

- name: Test SSH Connection
  hosts: ssh_local
  gather_facts: no
  tasks:
    - name: Ping via SSH
      ping:
      register: ssh_ping
    
    - name: Display SSH ping result
      debug:
        var: ssh_ping
EOF

Ejecute el playbook:

ansible-playbook connection_test.yml

Debería ver conexiones exitosas tanto a los hosts local como SSH:

PLAY [Test Local Connection] ********************************************

TASK [Ping local] ******************************************************
ok: [localhost]

TASK [Display local ping result] ****************************************
ok: [localhost] => {
    "local_ping": {
        "changed": false,
        "ping": "pong"
    }
}

PLAY [Test SSH Connection] **********************************************

TASK [Ping via SSH] ****************************************************
ok: [local-ssh]

TASK [Display SSH ping result] *****************************************
ok: [local-ssh] => {
    "ssh_ping": {
        "changed": false,
        "ping": "pong"
    }
}

PLAY RECAP *************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
local-ssh                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

La salida exitosa confirma que hemos corregido los errores 'UNREACHABLE!' para nuestros hosts válidos. El único host que permanece inalcanzable es virtual-host, lo cual es intencional ya que no existe.

Ahora ha diagnosticado y corregido con éxito los errores 'UNREACHABLE!' al:

  1. Probar la conectividad SSH directa
  2. Configurar claves SSH para la autenticación sin contraseña
  3. Configurar el inventario de Ansible con los parámetros de conexión adecuados
  4. Usar una configuración SSH personalizada
  5. Verificar la conectividad con un playbook completo

Implementación de las Mejores Prácticas para Prevenir Errores 'UNREACHABLE!'

Ahora que hemos solucionado los errores 'UNREACHABLE!' inmediatos, centrémonos en las mejores prácticas para prevenirlos en el futuro. Esto implica una gestión adecuada del inventario, configuraciones de conexión y técnicas de manejo de errores.

Creación de una Estructura de Inventario Robusta

Un inventario bien organizado facilita la solución de problemas. Creemos un directorio de inventario más estructurado:

cd ~/project/ansible-lab
mkdir -p inventory/{group_vars,host_vars}

Ahora, creemos un archivo de inventario principal:

cat > inventory/hosts << 'EOF'
## Production Servers
[production]
## prod-server ansible_host=prod.example.com

## Development Servers
[development]
## dev-server ansible_host=dev.example.com

## Local Connections
[local]
localhost ansible_connection=local

## SSH Connections
[ssh_local]
local-ssh ansible_host=127.0.0.1
EOF

A continuación, creemos variables de grupo para el grupo de conexión SSH:

cat > inventory/group_vars/ssh_local.yml << 'EOF'
---
ansible_connection: ssh
ansible_user: labex
ansible_ssh_private_key_file: ~/.ssh/id_rsa
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
EOF

Actualice la configuración de Ansible para usar el nuevo directorio de inventario:

cat > ansible.cfg << 'EOF'
[defaults]
inventory = ./inventory/hosts
host_key_checking = False
retry_files_enabled = True
retry_files_save_path = ~/.ansible/retry-files
timeout = 30
connect_timeout = 30
command_timeout = 30

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
control_path_dir = ~/.ansible/cp
EOF

Creación de un Playbook de Prueba de Conexión con Lógica de Reintento

Ansible le permite reintentar tareas fallidas. Creemos un playbook con lógica de reintento:

cat > connection_test_with_retry.yml << 'EOF'
---
- name: Test All Connections
  hosts: all
  gather_facts: no
  tasks:
    - name: Ping hosts
      ping:
      register: ping_result
      retries: 3
      delay: 5
      until: ping_result is not failed
      ignore_unreachable: yes
      
    - name: Display ping status
      debug:
        msg: "Connection to {{ inventory_hostname }} was successful"
      when: ping_result is success
      
    - name: Report unreachable hosts
      debug:
        msg: "Host {{ inventory_hostname }} is unreachable"
      when: ping_result is unreachable
EOF

Ejecute el playbook con nuestra nueva estructura de inventario:

ansible-playbook connection_test_with_retry.yml

Debería ver una salida que muestre conexiones exitosas a localhost y local-ssh.

Manejo de Errores 'UNREACHABLE!' con Elegancia

Creemos un playbook más avanzado que maneje los errores 'UNREACHABLE!' con elegancia y genere un informe:

cat > connection_report.yml << 'EOF'
---
- name: Test Connections and Generate Report
  hosts: all
  gather_facts: no
  tasks:
    - name: Try to connect to hosts
      ping:
      register: ping_result
      ignore_unreachable: yes
      
    - name: Create reachable hosts list
      set_fact:
        reachable_hosts: "{{ (reachable_hosts | default([])) + [inventory_hostname] }}"
      when: ping_result is success
      delegate_to: localhost
      delegate_facts: true
      
    - name: Create unreachable hosts list
      set_fact:
        unreachable_hosts: "{{ (unreachable_hosts | default([])) + [inventory_hostname] }}"
      when: ping_result is unreachable
      delegate_to: localhost
      delegate_facts: true

- name: Generate Connection Report
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Display reachable hosts
      debug:
        msg: "Reachable hosts: {{ reachable_hosts | default([]) | join(', ') }}"
      
    - name: Display unreachable hosts
      debug:
        msg: "Unreachable hosts: {{ unreachable_hosts | default([]) | join(', ') }}"
      
    - name: Write report to file
      copy:
        content: |
          Connection Report
          -----------------
          Reachable hosts: {{ reachable_hosts | default([]) | join(', ') }}
          Unreachable hosts: {{ unreachable_hosts | default([]) | join(', ') }}
          
          Generated on: {{ ansible_date_time.iso8601 }}
        dest: ~/project/ansible-lab/connection_report.txt
      register: report
      
    - name: Show report location
      debug:
        msg: "Report saved to {{ report.dest }}"
EOF

Ejecute el playbook de informe:

ansible-playbook connection_report.yml

Comprobemos el informe:

cat ~/project/ansible-lab/connection_report.txt

Debería ver un informe que enumera los hosts alcanzables e inalcanzables.

Uso de Plugins de Inventario de Ansible

Ansible proporciona plugins de inventario para administrar hosts dinámicamente. Creemos un script simple para demostrar esto:

cat > inventory_script.py << 'EOF'
#!/usr/bin/env python3

import json
import socket

def is_host_reachable(host, port=22, timeout=1):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except:
        return False

## Define our hosts
hosts = {
    'localhost': '127.0.0.1',
    'local-ssh': '127.0.0.1',
    'virtual-host': '10.10.10.10'
}

## Check reachability and build inventory
inventory = {
    'all': {
        'hosts': list(hosts.keys())
    },
    'reachable': {
        'hosts': []
    },
    'unreachable': {
        'hosts': []
    },
    '_meta': {
        'hostvars': {}
    }
}

for hostname, ip in hosts.items():
    reachable = is_host_reachable(ip)
    group = 'reachable' if reachable else 'unreachable'
    inventory[group]['hosts'].append(hostname)
    
    inventory['_meta']['hostvars'][hostname] = {
        'ansible_host': ip,
        'reachability_checked': True,
        'is_reachable': reachable
    }

print(json.dumps(inventory, indent=2))
EOF

chmod +x inventory_script.py

Pruebe el script de inventario dinámico:

./inventory_script.py

Debería ver una salida JSON que muestra los hosts categorizados como alcanzables o inalcanzables.

Ejecutemos un playbook usando este inventario dinámico:

ansible-playbook -i ./inventory_script.py connection_test.yml --limit reachable

Esto solo intentará conectarse a los hosts que el script ha determinado que son alcanzables, lo que le ayudará a evitar los errores 'UNREACHABLE!' por completo.

Estas mejores prácticas proporcionan un marco robusto para administrar la conectividad de Ansible y prevenir los errores 'UNREACHABLE!' en entornos de producción.

Resumen

En este laboratorio, aprendió a identificar, solucionar problemas y prevenir el error 'UNREACHABLE!' en Ansible. Ha:

  1. Configurado un entorno básico de Ansible y encontró el error 'UNREACHABLE!' de primera mano.

  2. Analizado el mensaje de error y comprendido las causas comunes de los problemas de conectividad.

  3. Utilizado varias técnicas de solución de problemas para diagnosticar problemas de conexión.

  4. Implementado soluciones para corregir los errores, incluyendo:

    • Configuración de claves SSH para autenticación sin contraseña.
    • Configuración de archivos de inventario adecuados.
    • Uso de opciones de configuración SSH.
  5. Aplicado las mejores prácticas para prevenir futuros errores 'UNREACHABLE!', tales como:

    • Creación de una organización de inventario estructurada.
    • Implementación de lógica de reintento.
    • Desarrollo de estrategias de manejo de errores.
    • Uso de scripts de inventario dinámico para verificar la accesibilidad del host.

Estas habilidades le ayudarán a mantener implementaciones de Ansible confiables y a resolver rápidamente cualquier problema de conectividad que surja. Al comprender las causas subyacentes de los errores 'UNREACHABLE!' e implementar las medidas preventivas adecuadas, puede asegurarse de que la automatización de su infraestructura se ejecute de manera fluida y eficiente.