介绍
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!' 错误可能由于多种原因而发生:
- 网络问题:主机可能位于防火墙后面,或者可能存在网络连接问题。
- 主机信息不正确:inventory 中的主机名或 IP 地址可能错误。
- SSH 配置:SSH 可能未在目标主机上正确配置。
- 身份验证问题:SSH 密钥或密码可能不正确。
- 主机不可用:主机可能已关闭或无法访问。
创建测试 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!' 错误,方法是:
- 测试直接 SSH 连接
- 设置 SSH 密钥以进行无密码身份验证
- 使用正确的连接参数配置 Ansible inventory
- 使用自定义 SSH 配置
- 使用一个全面的 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!' 错误。你已经:
设置了一个基本的 Ansible 环境,并亲身体验了 'UNREACHABLE!' 错误
分析了错误消息,并了解了连接问题的常见原因
使用了各种故障排除技术来诊断连接问题
实施了解决方案来修复错误,包括:
- 配置 SSH 密钥以进行无密码身份验证
- 设置正确的 inventory 文件
- 使用 SSH 配置选项
应用了最佳实践来防止未来的 'UNREACHABLE!' 错误,例如:
- 创建结构化的 inventory 组织
- 实施重试逻辑
- 制定错误处理策略
- 使用动态 inventory 脚本来检查主机可达性
这些技能将帮助你维护可靠的 Ansible 部署,并快速解决出现的任何连接问题。通过了解 'UNREACHABLE!' 错误的根本原因并实施适当的预防措施,你可以确保你的基础设施自动化运行平稳高效。


