外观
项目 9:Ansible 批量巡检与主机管理
约 3941 字大约 13 分钟
AnsibleInventoryAd-HocSSH 免密
2026-05-31
项目目标
前面 8 节课的脚本主要面向单台服务器。本课引入 Ansible,完成控制端、受控端、Inventory 和 Ad-Hoc 命令练习,实现多主机批量巡检。
最终提交 ansible.cfg、inventory.ini、adhoc_check.md 和命令截图,能够批量采集主机名、运行时间、磁盘、内存和系统版本信息。
一、从脚本到 Ansible 的变化
Shell 脚本适合把一台机器初始化好,但当机器数量变成 5 台、20 台、100 台时,问题会变成:
| 单机脚本思路 | 多主机 Ansible 思路 |
|---|---|
| 登录一台服务器执行脚本 | 在控制端统一下发命令 |
| 服务器地址写在命令里 | 服务器写在 Inventory 中 |
| 手工复制脚本 | Ansible 通过 SSH 执行模块 |
| 输出散落在多个终端 | 控制端集中保存结果 |
Ansible 的基本结构很简单:
控制端不需要在受控节点上安装 Agent,它通过 SSH 连接目标主机,再临时传输和执行模块。这种"无 Agent"架构是 Ansible 区别于 Puppet、SaltStack 等工具的显著特征——受控端只需要 Python 和 SSH,没有任何常驻进程。
1.1 Ansible 的执行流程
Ansible 无代理执行模型演示
展示 ansible all -m ping 在底层经历了哪些步骤
💻控制端 (Ansible)
1. 解析 Inventory
2. 建立 SSH 连接
3. 生成临时 Python 代码
6. 解析返回的 JSON 结果
🖥️受控端 (无 Agent)
4. 接收并保存到
~/.ansible/tmp/⚙️ 5. 触发 Python 执行
{
"changed": false,
"ping": "pong"
}当你在控制端执行 ansible all -m ping 时,背后发生了以下步骤:
每一步都有可能失败。理解这个流程有助于定位 UNREACHABLE 和 MODULE FAILURE 这两类最常见的错误:前者是 SSH 连接没建立(网络或认证问题),后者是连接建立了但模块执行失败(Python 环境或权限问题)。
1.2 Ansible 模块的分类
Ansible 内置了数千个模块,但本课只需要了解几类核心模块:
| 类别 | 代表模块 | 用途 |
|---|---|---|
| 命令执行 | command、shell、raw | 在受控端执行命令 |
| 文件操作 | copy、file、template、fetch | 管理文件和目录 |
| 系统信息 | setup、debug | 采集 facts 和输出变量 |
| 包管理 | apt、yum、package | 安装/卸载软件包 |
| 服务管理 | service、systemd | 管理 systemd 服务 |
| 用户管理 | user、group | 管理用户和组 |
可以使用 ansible-doc -l 列出所有模块,用 ansible-doc <模块名> 查看详细用法和参数示例。这是比搜索引擎更准确的学习途径,因为它始终与你安装的 Ansible 版本匹配。
二、安装与项目目录
sudo apt update
sudo apt install -y ansible openssh-client sshpass
ansible --versionAnsible 对控制端的 Python 版本有要求——通常需要 Python 3.8 以上。如果系统 Python 版本过低,可以安装 python3.10 或更高版本,并在 ansible.cfg 中通过 interpreter_python 指定路径。
受控端的要求更宽松:只需要 Python 2.7 或 Python 3.5 以上,以及可用的 SSH 服务。如果受控端没有 Python(例如精简的容器镜像),可以使用 raw 模块先安装 Python,再使用其他模块。
建议建立单独目录:
mkdir -p ansible-check/{outputs,keys}
cd ansible-check2.1 复用第 3 课 Docker 受控节点
本课沿用第 3 课的靶机设计:用 Docker 创建两台 Ubuntu 服务器,分别映射到宿主机的 2221 和 2222 端口。为了让大家不用回到第 3 课翻文件,这里直接给出完整代码。
在当前 ansible-check/ 目录下创建 Dockerfile:
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
openssh-server sudo nginx rsync curl ca-certificates python3 \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -s /bin/bash deploy \
&& echo "deploy:deploy123" | chpasswd \
&& usermod -aG sudo deploy \
&& echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/deploy \
&& chmod 0440 /etc/sudoers.d/deploy
RUN mkdir -p /run/sshd \
&& sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication yes/' /etc/ssh/sshd_config \
&& sed -i 's/^#\?PubkeyAuthentication .*/PubkeyAuthentication yes/' /etc/ssh/sshd_config \
&& sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
EXPOSE 22 8081 8082
CMD ["/usr/sbin/sshd", "-D", "-e"]再创建 docker-compose.yml:
services:
web01:
build:
context: .
image: atsb-ubuntu-ssh-nginx:22.04
container_name: atsb-web01
ports:
- "2221:22"
- "8081:8081"
web02:
build:
context: .
image: atsb-ubuntu-ssh-nginx:22.04
container_name: atsb-web02
ports:
- "2222:22"
- "8082:8082"启动两台受控节点:
docker compose up -d --build
docker ps --filter "name=atsb-web"上面的 Dockerfile 已经把 python3 写进镜像安装列表。如果你已经用第 3 课旧文件启动了容器,没有重新构建镜像,就用下面这一条命令给两台容器补上 Python 3:
for c in atsb-web01 atsb-web02; do docker exec -u root "$c" bash -lc 'apt-get update && apt-get install -y python3'; done完成后,两台本地受控节点对应关系如下:
| 节点 | 容器名 | SSH 地址 | 登录用户 | 初始密码 |
|---|---|---|---|---|
web01 | atsb-web01 | 127.0.0.1:2221 | deploy | deploy123 |
web02 | atsb-web02 | 127.0.0.1:2222 | deploy | deploy123 |
本课目录结构:
ansible-check/
├── Dockerfile # Ubuntu SSH 靶机镜像
├── docker-compose.yml # 复用第 3 课的双服务器编排文件
├── ansible.cfg
├── inventory.ini
├── bootstrap_ansible_keys.sh # 自动把公钥写入两台容器
├── adhoc_check.md
├── keys/
└── outputs/三、配置 ansible.cfg
ansible.cfg 是 Ansible 控制端的本地配置文件。放在项目目录后,当前目录执行的 ansible 命令会优先读取它。Ansible 按以下优先级查找配置文件:
- 环境变量
ANSIBLE_CONFIG指定的路径 - 当前目录下的
ansible.cfg - 用户家目录下的
~/.ansible.cfg - 系统全局
/etc/ansible/ansible.cfg
这意味着每个项目都可以有自己的 ansible.cfg,不会互相干扰。课堂环境中务必在项目目录下创建配置文件,避免使用全局配置影响其他实验。
问题:Ansible 中用于保存受控主机清单的文件名在本课中叫什么?
配置重点:
| 配置项 | 作用 | 课堂建议值 |
|---|---|---|
inventory | 指定主机清单文件 | ./inventory.ini |
remote_user | 默认远程登录用户 | deploy |
host_key_checking | SSH 首次连接时的主机密钥确认 | False(课堂环境) |
timeout | SSH 连接超时时间(秒) | 10(本地 Docker 靶场) |
forks | 并行执行的任务数 | 5(受控端数量少时可调低) |
retry_files_enabled | 是否生成 .retry 文件 | False(课堂减少文件 clutter) |
gathering | 是否自动采集 facts | implicit(Playbook 中默认采集) |
host_key_checking = False 只适合课堂或测试环境。生产环境中应该保持开启,或通过 ssh-keyscan 预先收集主机密钥。
四、编写 Inventory 主机清单
Inventory 描述"有哪些机器、怎么连接"。它不仅是一个列表,更是一套灵活的机器组织系统。最小格式如下:
问题:Inventory 中用于指定目标主机 IP 或域名的变量名是什么?
字段说明:
| 字段 | 说明 |
|---|---|
web01 | Ansible 内部使用的主机别名 |
ansible_host | 真实 IP 或域名 |
ansible_port | SSH 端口 |
ansible_user | SSH 用户 |
ansible_ssh_private_key_file | 私钥路径 |
ansible_python_interpreter | 受控端 Python 路径 |
4.1 主机组与子组
当受控端超过 3 台时,建议按角色或功能分组。Inventory 支持层级组织:
[web]
web01 ansible_host=127.0.0.1 ansible_port=2221
web02 ansible_host=127.0.0.1 ansible_port=2222
[docker_lab:children]
web组的价值体现在以下场景:
ansible web -m ping——只对 Web 服务器执行ansible docker_lab -m command -a "python3 --version"——对本课 Docker 实验组执行- 在
group_vars/web.yml中定义 Web 组共享变量,避免在每个主机条目中重复
4.2 Inventory 变量优先级
Inventory 变量优先级演示
当同一个变量在不同层级被定义时,最终生效的是哪一个?
优先级 1 (最高)命令行参数 (-e)
优先级 2Playbook vars
优先级 3Inventory 主机行 (Host)
优先级 4 (最低)组变量 (group_vars/web)
Ansible 解析引擎
选择左侧定义的变量,点击解析
Ansible 中同一个变量可以在多处定义,优先级从高到低:
| 优先级 | 位置 |
|---|---|
| 最高 | 命令行 -e "var=value" |
| ↓ | Playbook 中的 vars |
| ↓ | host_vars/<主机名>.yml |
| ↓ | group_vars/<组名>.yml |
| ↓ | Inventory 文件中主机行的变量 |
| 最低 | Role 默认值 |
这个优先级体系意味着:如果 ansible_port 在 Inventory 行中写的是 2222,但在 group_vars/all.yml 中写的是 22,实际生效的是 Inventory 行的 2222(Inventory 行优先级高于 group_vars)。理解这一点可以避免"明明改了配置却不生效"的困惑。
连接失败优先查 Inventory
Ansible 连接失败时,最先检查 IP、端口、用户和私钥路径。大多数课堂故障都来自 Inventory 写错,而不是 Ansible 本身坏了。一个快速的调试方法是使用 ansible-inventory -i inventory.ini --list 查看 Ansible 最终解析出的完整主机变量,这比对着原始文件逐行检查高效得多。
五、SSH 免密与连通性测试
如果还没有密钥,可以生成一组:
ssh-keygen -t ed25519 -f keys/ansible_ed25519 -N ""ed25519 算法比 RSA 更短更安全,是现代 SSH 部署的首选。私钥权限必须是 600,否则 SSH 会拒绝使用。
在本地 Docker 实验环境中,两台容器的初始密码固定为 deploy123。为了节省课堂时间,可以用 sshpass 把这个明文密码写进脚本,自动把 keys/ansible_ed25519.pub 填入两台容器的 ~/.ssh/authorized_keys。把下面脚本保存为 bootstrap_ansible_keys.sh:
问题:本课脚本中用于把明文密码传给 ssh-copy-id 的命令是什么?
执行脚本:
chmod +x bootstrap_ansible_keys.sh
./bootstrap_ansible_keys.sh脚本执行后,先用原生命令确认能免密登录:
ssh -i keys/ansible_ed25519 -p 2221 deploy@127.0.0.1 hostname
ssh -i keys/ansible_ed25519 -p 2222 deploy@127.0.0.1 hostname再用 Ansible ping 模块测试:
ansible all -m ping成功输出通常包含:
{
"changed": false,
"ping": "pong"
}如果遇到 Permission denied,优先级最高的检查清单是:
authorized_keys文件权限是否为600~/.ssh/目录权限是否为700- 受控端 SSH 服务是否允许密钥认证(
sshd_config中PubkeyAuthentication yes) - 私钥路径在 Inventory 中是否写对
六、Ad-Hoc 批量巡检
Ad-Hoc 是"不写 Playbook,直接执行一次模块"的方式,适合临时巡检、批量查询和简单文件操作。它的语法格式是:
ansible <主机模式> -m <模块名> -a "<模块参数>"6.1 常用模块详解
| 模块 | 用途 | 示例 |
|---|---|---|
ping | 测试 Ansible 模块执行能力 | ansible all -m ping |
command | 执行普通命令,不经过 shell | ansible all -m command -a "hostname" |
shell | 执行需要管道、重定向的 shell 命令 | ansible all -m shell -a "df -h | grep /$" |
copy | 复制文件到受控节点 | ansible all -m copy -a "src=./file dest=/tmp/" |
fetch | 从受控节点拉取文件到控制端 | ansible all -m fetch -a "src=/etc/hostname dest=outputs/ flat=yes" |
file | 创建目录、修改权限、删除文件 | ansible all -m file -a "path=/opt/data state=directory mode=0755" |
setup | 采集系统 facts 信息 | ansible all -m setup |
apt | 管理 APT 软件包 | ansible all -m apt -a "name=htop state=present" --become |
service | 管理 systemd 服务 | ansible all -m service -a "name=nginx state=started" --become |
stat | 获取文件/目录状态信息 | ansible all -m stat -a "path=/etc/passwd" |
command 和 shell 的区别是初学者最容易混淆的点。command 不会启动 shell,因此不支持管道(|)、重定向(>)和变量展开($HOME)。如果需要这些功能,使用 shell 模块。但能用 command 时尽量用 command,因为不经过 shell 执行更安全,不受受控端 shell 配置的影响。
6.2 setup 模块与 Facts 体系
setup 模块返回的 facts 是 Ansible 最强大的数据源之一。执行一次 ansible all -m setup 可以看到数百个系统变量,包括:
| 变量路径 | 内容 | 典型用法 |
|---|---|---|
ansible_hostname | 主机名 | Playbook 中动态生成配置文件名 |
ansible_distribution | 发行版名称(Ubuntu/CentOS) | when: ansible_distribution == "Ubuntu" |
ansible_distribution_version | 发行版版本号 | 判断是否支持特定功能 |
ansible_processor_cores | CPU 核心数 | 动态调整应用线程数 |
ansible_memtotal_mb | 总内存(MB) | 容量规划和资源检查 |
ansible_default_ipv4.address | 主网卡 IPv4 地址 | 动态获取 IP 用于配置绑定 |
ansible_devices | 磁盘设备信息 | 磁盘巡检 |
Facts 可以在 Ad-Hoc 中过滤显示:
# 只看内存
ansible all -m setup -a "filter=ansible_memtotal_mb"
# 只看系统版本
ansible all -m setup -a "filter=ansible_distribution*"6.3 巡检命令实战
解锁巡检脚本,把命令输出保存到 outputs/,再整理到 adhoc_check.md。
问题:使用 Ansible 对所有主机执行 ping 模块的完整命令是什么?
建议的巡检清单(每一步保存输出):
# 1. 连通性
ansible all -m ping | tee outputs/01-ping.txt
# 2. 主机名
ansible all -m command -a "hostname" | tee outputs/02-hostname.txt
# 3. 运行时间
ansible all -m command -a "uptime" | tee outputs/03-uptime.txt
# 4. 磁盘使用
ansible all -m shell -a "df -h /" | tee outputs/04-disk.txt
# 5. 内存
ansible all -m shell -a "free -h" | tee outputs/05-memory.txt
# 6. 系统版本
ansible all -m command -a "cat /etc/os-release" | tee outputs/06-os.txt
# 7. 内核版本
ansible all -m command -a "uname -r" | tee outputs/07-kernel.txt
# 8. 最近登录记录
ansible all -m shell -a "last -n 5" | tee outputs/08-login.txt七、整理巡检记录
adhoc_check.md 可以按下面结构整理:
# Ansible 批量巡检记录
## 1. 主机连通性
- 命令:`ansible all -m ping`
- 结果:web01、web02 均返回 pong
## 2. 主机名
- 命令:`ansible all -m command -a "hostname"`
- 摘要:web01=atsb-web01,web02=atsb-web02
## 3. 系统资源
| 项目 | web01 | web02 |
| --- | --- | --- |
| 运行时间 | 3 days | 1 day |
| 磁盘使用率 | 45% | 32% |
| 可用内存 | 2.1G | 3.8G |
| 系统版本 | Ubuntu 22.04 | Ubuntu 24.04 |
| 内核版本 | 5.15.0 | 6.8.0 |Markdown 表格能让多主机的对比一目了然。整理的关键不是"把输出全部贴上去",而是提取关键数值,让阅读者在 30 秒内了解集群整体状态。
八、提交物清单
| 提交物 | 要求 |
|---|---|
ansible.cfg | 能指定本课 Inventory,命令在项目目录下可直接运行 |
inventory.ini | 至少配置 2 台受控节点或 2 个测试目标,包含主机组 |
adhoc_check.md | 整理 ping、主机名、运行时间、磁盘、内存、系统版本 |
| 命令截图 | 能证明多主机批量执行成功,每台机器返回独立结果 |
outputs/ 目录 | 保存各巡检命令的原始输出,便于复查 |
九、故障排查
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
UNREACHABLE | IP、端口、用户、私钥路径错误 | 先用原生 ssh 命令逐项验证,再用 ansible-inventory --list 检查最终变量 |
Permission denied | 公钥未写入或私钥权限过宽 | 检查 authorized_keys 内容,私钥权限 chmod 600,.ssh 目录权限 chmod 700 |
MODULE FAILURE | 受控节点 Python 环境异常 | 设置 ansible_python_interpreter=/usr/bin/python3,或用 raw 模块检查 Python 版本 |
| 输出太多看不清 | 没有保存巡检结果 | 使用 tee 或重定向保存到 outputs/,CHANGED 输出可用 grep -v CHANGED 过滤 |
ansible 命令报 No inventory was parsed | ansible.cfg 中的 inventory 路径不正确 | 确认路径是相对当前工作目录的,或用 -i inventory.ini 显式指定 |
| 部分主机成功、部分失败 | 不同主机 SSH 配置不同 | 检查 Inventory 中各主机是否都有 ansible_port、ansible_user 等必要变量 |
| facts 采集超时 | 受控端性能差或 Python 版本低 | 增大 timeout 值,或使用 gather_subset=min 只采集最小 facts 子集 |
十、进阶任务
探索任务 1:按主机组巡检
在 Inventory 中增加 [web]、[db] 两个组,分别执行 ansible web -m ping 和 ansible db -m ping,观察输出差异。为每个组定义 group_vars 目录,写入不同的 SSH 端口配置,验证子组是否会继承父组变量。
探索任务 2:批量文件分发
使用 copy 模块向所有受控节点的 /tmp/ 目录分发一个脚本文件,然后使用 command 或 shell 模块在受控端执行它。要求使用 --become 提权,观察 sudo 密码需求。
探索任务 3:自定义 Facts
在受控节点的 /etc/ansible/facts.d/ 目录下创建一个 .fact 文件(JSON 或 INI 格式),定义自定义变量。在控制端用 ansible all -m setup -a "filter=ansible_local" 查看自定义 facts 是否被采集到。思考自定义 facts 在运维场景中的应用(如标记机柜位置、服务角色等)。
