如何修复 Ansible 中的 'UNREACHABLE!' 错误

AnsibleBeginner
立即练习

介绍

Ansible 是一个强大的基础设施自动化工具,它简化了复杂 IT 环境的管理。然而,用户经常会遇到 'UNREACHABLE!' 错误,这会中断自动化工作流程。当 Ansible 无法与目标主机建立连接时,通常会发生此错误。在本实验中,你将学习如何在 Ansible 部署中识别、排除故障和防止 'UNREACHABLE!' 错误。

通过完成本实验,你将了解 Ansible 中连接问题的常见原因,并能够实施有效的解决方案,以确保你的自动化顺利运行。

设置 Ansible 环境

在这一步中,我们将设置一个基本的 Ansible 环境来使用。我们将安装 Ansible,配置必要的文件,并确保一切都准备好进行我们的实验。

安装 Ansible

首先,让我们使用以下命令在 LabEx VM 上安装 Ansible:

sudo apt update
sudo apt install -y ansible

这将安装 Ubuntu 存储库中可用的最新版本的 Ansible。安装完成后,通过检查 Ansible 版本来验证安装:

ansible --version

你应该看到类似于以下的输出,显示 Ansible 版本和配置详细信息:

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

创建工作目录

让我们为我们的 Ansible 工作创建一个专用目录:

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

创建 Ansible 配置文件

现在,让我们在我们的项目目录中创建一个基本的 Ansible 配置文件:

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

此配置文件:

  • 指定我们的 inventory 文件位置
  • 禁用 SSH 主机密钥检查(对实验环境很有用)
  • 将默认的远程用户设置为 'labex'

创建 inventory 文件

inventory 文件定义了 Ansible 将管理的主机。让我们创建一个简单的 inventory 文件:

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

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

此 inventory 包含两个组:

  • local:仅包含 localhost,它使用本地连接
  • virtual:包含一个虚拟主机,我们将使用它来演示 'UNREACHABLE!' 错误

virtual-host 配置了一个 IP 地址(10.10.10.10),该地址在我们的环境中不存在,这将帮助我们生成 'UNREACHABLE!' 错误。

测试 Ansible

让我们通过对本地主机运行一个简单的 ping 命令来测试我们的 Ansible 设置:

ansible local -m ping

你应该看到一个成功的响应,例如:

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

这确认了 Ansible 正在正确地工作,用于本地连接。现在,让我们尝试 ping 虚拟主机,这应该会失败:

ansible virtual -m ping

这将产生 'UNREACHABLE!' 错误,因为该主机不存在:

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
}

你现在已经成功设置了 Ansible,并创建了一个发生 'UNREACHABLE!' 错误的情景,我们将在下一步中进行调查。

理解 'UNREACHABLE!' 错误

在之前的步骤中,当尝试连接到不存在的主机时,我们遇到了 'UNREACHABLE!' 错误。现在,让我们更详细地了解这个错误,并探索其常见原因。

分析错误消息

让我们看看我们收到的错误消息:

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
}

错误消息提供了有价值的信息:

  • UNREACHABLE! 表明 Ansible 无法与主机建立连接
  • msg 字段告诉我们原因:“Failed to connect to the host via ssh”(无法通过 ssh 连接到主机)
  • 具体的错误是“Connection timed out”(连接超时),这意味着 Ansible 尝试连接但没有收到响应

'UNREACHABLE!' 错误的常见原因

'UNREACHABLE!' 错误可能由于多种原因而发生:

  1. 网络问题:主机可能位于防火墙后面,或者可能存在网络连接问题。
  2. 主机信息不正确:inventory 中的主机名或 IP 地址可能错误。
  3. SSH 配置:SSH 可能未在目标主机上正确配置。
  4. 身份验证问题:SSH 密钥或密码可能不正确。
  5. 主机不可用:主机可能已关闭或无法访问。

创建测试 Playbook

让我们创建一个简单的 playbook 来进一步演示该错误:

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

此 playbook 尝试 ping 我们 inventory 中定义的所有主机。让我们运行它:

ansible-playbook test_playbook.yml

你应该看到类似于以下的输出:

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

该 playbook 在 localhost 上成功运行,但在 virtual-host 上失败,并出现了 'UNREACHABLE!' 错误。

检查 Ansible 详细程度

Ansible 提供了不同的详细程度级别来帮助诊断问题。让我们尝试使用增加的详细程度运行 playbook:

ansible-playbook test_playbook.yml -v

为了获得更详细的输出,请使用 -vv-vvv

ansible-playbook test_playbook.yml -vvv

-vvv 选项提供了最详细的输出,显示了 Ansible 尝试使用的确切 SSH 命令:

<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'"'"''

这种详细程度对于排除 SSH 连接问题非常有价值。

使用 --limit 选项

当使用大型 inventory 时,你可以使用 --limit 选项将 Ansible 限制为仅对特定主机或组运行命令:

ansible-playbook test_playbook.yml --limit localhost

此命令将仅对 localhost 运行 playbook,从而避免来自 virtual-host 的 'UNREACHABLE!' 错误。

现在我们更好地理解了 'UNREACHABLE!' 错误,让我们在下一步中继续排除故障并解决这些问题。

排除故障并修复 'UNREACHABLE!' 错误

现在我们了解了导致 'UNREACHABLE!' 错误的原因,让我们学习如何排除故障并修复它们。我们将使用各种方法来诊断和解决连接问题。

修复 inventory 问题

'UNREACHABLE!' 错误最常见的原因之一是 inventory 信息不正确。让我们修复我们的 inventory 文件:

cd ~/project/ansible-lab

首先,让我们更新我们的 inventory 文件以包含一个有效的主机。在这个实验环境中,我们将重点使用 localhost 和不同的连接方法来演示故障排除技术:

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

我们添加了一个新的组 ssh_local,其中包含一个主机,该主机将尝试通过 SSH 连接到 localhost,而不是本地连接方法。

直接测试 SSH 连接

在使用 Ansible 之前,直接测试 SSH 连接始终是一个好主意:

ssh 127.0.0.1

你可能会被提示输入密码或看到关于主机密钥的消息。这是一个好兆头,因为它意味着 SSH 连接正在工作,但你可能需要为 Ansible 正确配置 SSH。

如果卡在密码提示符处,请按 Ctrl+C 退出。

设置 SSH 密钥以进行无密码身份验证

Ansible 通常使用 SSH 密钥进行身份验证。让我们设置对 localhost 的无密码 SSH 访问:

## 如果你没有 SSH 密钥,请生成一个
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa

## 将密钥添加到 authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

## 设置正确的权限
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

现在尝试通过 SSH 连接到 localhost:

ssh 127.0.0.1

你应该能够连接,而无需提示输入密码。键入 exit 返回到你的原始会话。

使用 SSH 连接测试 Ansible

现在,让我们使用 SSH 连接测试 Ansible 到 localhost:

ansible ssh_local -m ping

如果 SSH 设置正确,你应该看到成功的响应:

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

如果你仍然看到 'UNREACHABLE!' 错误,让我们向 inventory 文件添加更多连接参数:

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

再次尝试 ping 命令:

ansible ssh_local -m ping

将 Ansible 与自定义 SSH 配置一起使用

有时,你需要更复杂的 SSH 配置。让我们创建一个自定义 SSH 配置文件:

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

## 设置正确的权限
chmod 600 ~/.ssh/config

更新 inventory 以使用 SSH 配置条目:

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

[ssh_local]
local-ssh

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

再次测试连接:

ansible ssh_local -m ping

创建一个 Playbook 来测试所有连接

让我们创建一个全面的 playbook 来测试我们所有的连接:

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

运行 playbook:

ansible-playbook connection_test.yml

你应该看到对本地和 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

成功的输出确认我们已经修复了有效主机的 'UNREACHABLE!' 错误。唯一仍然无法访问的主机是 virtual-host,这是故意的,因为它不存在。

你现在已经成功地诊断并修复了 'UNREACHABLE!' 错误,方法是:

  1. 测试直接 SSH 连接
  2. 设置 SSH 密钥以进行无密码身份验证
  3. 使用正确的连接参数配置 Ansible inventory
  4. 使用自定义 SSH 配置
  5. 使用一个全面的 playbook 验证连接

实施最佳实践以防止 'UNREACHABLE!' 错误

现在我们已经修复了直接的 'UNREACHABLE!' 错误,让我们专注于未来的最佳实践,以防止它们再次发生。这包括适当的 inventory 管理、连接配置和错误处理技术。

创建一个强大的 inventory 结构

一个组织良好的 inventory 使故障排除更容易。让我们创建一个更有结构的 inventory 目录:

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

现在,让我们创建一个主要的 inventory 文件:

cat > inventory/hosts << 'EOF'
## 生产服务器
[production]
## prod-server ansible_host=prod.example.com

## 开发服务器
[development]
## dev-server ansible_host=dev.example.com

## 本地连接
[local]
localhost ansible_connection=local

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

接下来,让我们为 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

更新 Ansible 配置以使用新的 inventory 目录:

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

创建一个具有重试逻辑的连接测试 Playbook

Ansible 允许你重试失败的任务。让我们创建一个具有重试逻辑的 playbook:

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

使用我们新的 inventory 结构运行 playbook:

ansible-playbook connection_test_with_retry.yml

你应该看到显示到 localhost 和 local-ssh 的成功连接的输出。

优雅地处理 'UNREACHABLE!' 错误

让我们创建一个更高级的 playbook,该 playbook 优雅地处理 'UNREACHABLE!' 错误并生成报告:

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

运行报告 playbook:

ansible-playbook connection_report.yml

让我们检查报告:

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

你应该看到一个列出可访问和不可访问主机的报告。

使用 Ansible inventory 插件

Ansible 提供了 inventory 插件来动态管理主机。让我们创建一个简单的脚本来演示这一点:

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

测试动态 inventory 脚本:

./inventory_script.py

你应该看到 JSON 输出,显示主机被分类为可访问或不可访问。

让我们使用这个动态 inventory 运行一个 playbook:

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

这将仅尝试连接到脚本已确定为可访问的主机,从而帮助你完全避免 'UNREACHABLE!' 错误。

这些最佳实践为管理 Ansible 连接性和防止生产环境中的 'UNREACHABLE!' 错误提供了强大的框架。

总结

在这个实验中,你学习了如何在 Ansible 中识别、排除故障和防止 'UNREACHABLE!' 错误。你已经:

  1. 设置了一个基本的 Ansible 环境,并亲身体验了 'UNREACHABLE!' 错误

  2. 分析了错误消息,并了解了连接问题的常见原因

  3. 使用了各种故障排除技术来诊断连接问题

  4. 实施了解决方案来修复错误,包括:

    • 配置 SSH 密钥以进行无密码身份验证
    • 设置正确的 inventory 文件
    • 使用 SSH 配置选项
  5. 应用了最佳实践来防止未来的 'UNREACHABLE!' 错误,例如:

    • 创建结构化的 inventory 组织
    • 实施重试逻辑
    • 制定错误处理策略
    • 使用动态 inventory 脚本来检查主机可达性

这些技能将帮助你维护可靠的 Ansible 部署,并快速解决出现的任何连接问题。通过了解 'UNREACHABLE!' 错误的根本原因并实施适当的预防措施,你可以确保你的基础设施自动化运行平稳高效。