Inventory 只列授权主机,先用 --graph 确认 group 和 alias,不在清单中保存密码与私钥。
外观
外观
约 3535 字大约 12 分钟
AnsibleInventoryPlaybook幂等
2026-07-30
[Windows PowerShell] wsl ~:打开本机默认的 WSL2 Ubuntu;Inventory、Playbook 和 Ansible 命令都保存在这里。[WSL] ssh root@你的ECS公网IP:先确认 WSL 能正常连接自己的 ECS,再退出 SSH 会话并由 Ansible 发起后续连接;如果登录用户不是 root,同步修改 Inventory 中的远程用户。到目前为止,我们常常登录服务器后逐条执行命令。只有一台机器时还能勉强记住步骤;机器增多、任务重复或需要审计时,“我上次大概这样做的”就很危险。Ansible 把目标主机放进 Inventory,把期望步骤写进 Playbook,再用 changed、failed、HTTP 和业务标记判断结果。
关键提醒
自动化不是把一串 shell 命令换成 YAML。可维护的自动化要有明确目标、可重复执行、可解释的变更结果、失败门禁、健康证据和只清理自身资源的边界。
学完这一课,做到四件事:
ansible.builtin.ping。changed=0 的意义和限制。uri、assert 和容器状态组合服务健康检查。Inventory 与 Playbook 保存在控制端,模块通过 SSH 到目标主机执行。开始前先确认这两个位置,避免把本地路径、远端目录和运行结果混在一起。
实训包包含 ansible.cfg、inventory.ini、site.yml、health.yml、fault.yml、cleanup.yml、verify.sh 等文件(从智慧职教课程资源区下载 lab-17-starter.zip):
[WSL] mkdir -p ~/cloud-course/lab-17
[WSL] cd ~/cloud-course/lab-17
[WSL] cp -r /path/to/lab-17-starter/* .
[WSL] ssh root@你的ECS公网IP hostname最后一条只确认 SSH 连通性——hostname 应返回你的 ECS 主机名。
同一份 Playbook 会连续执行两次。第一次建立期望状态,第二次用于观察是否还有多余变更;随后用一次可恢复的页面漂移检查健康门禁和恢复路径。
本课角色架构包含控制节点与被管理节点:
| 角色 | 本课环境 | 核心职责 | 绝不应该暴露或保存的内容 |
|---|---|---|---|
| 控制端 | 个人 WSL;演示环境使用本地 Linux/macOS 控制节点 | 保存 Inventory 清单与 Playbook,发起 SSH 连接并汇总执行结果 | 绝不能把密钥与真实 IP 写入代码包 |
| 被管理端 | 云主机 B,登录目标 root@SYG680400 | 运行 Python 探针与模块,创建隔离目录与部署业务容器 | 被管理端不需要安装 Ansible 控制程序 |
演示机不是 Windows WSL 环境,因此本课截图中统一标记为 [CTRL];学生在 WSL 里敲相同命令时,路径换成各自的本地目录即可。两种控制节点都是通过 SSH 管理 ECS,但请注意不能把演示机的路径或主机名直接抄进个人报告。
观看时留意:控制端怎样通过 SSH 管理目标主机,目标主机为什么不需要常驻 Agent?
与本课的关系:本课控制端是 WSL,目标主机是 ECS;连接配置和密钥仍按课程安全规则处理。
[WSL] python3 --versionAnsible 的核心逻辑在控制端运行。本课基线版本要求 Python 3.10 或更高版本。Ubuntu 22.04 自带的 Python 3.10 可以直接使用;如果显示 3.9 或更早版本,先停止安装并请教师统一处理控制端环境,不要自行替换系统 Python。
[WSL] python3 -m venv "$HOME/.venvs/cloud-course-ansible"新建独立的 Python 虚拟环境,隔离全局环境。
[WSL] "$HOME/.venvs/cloud-course-ansible/bin/python" -m pip install \
"ansible-core==2.17.14"安装经课程验证过的 Ansible Core 指定版本。这个版本兼容 Python 3.10,适合常见的 Ubuntu 22.04 WSL 环境。较新的 Ansible Core 版本可能同时提高 Python 最低版本;在真实的生产环境里,应当把 Python、Ansible 和目标系统的兼容关系一起验证后再升级。
[WSL] source "$HOME/.venvs/cloud-course-ansible/bin/activate"激活虚拟环境。命令行提示符前会多出环境名称。
本课定义的 Inventory 主机清单格式:
[course_hosts]
cloud-b ansible_host=B ansible_user=root ansible_python_interpreter=/usr/bin/python3
[course_hosts:vars]
ansible_ssh_common_args='-o BatchMode=yes'关键配置项说明:
| 字段 | 属性含义 | 本课设定 |
|---|---|---|
course_hosts | 逻辑主机组 | 本课限定为包含单个目标机器 |
cloud-b | Inventory 内部识别的服务名 | 仅作标识,不是 IP 地址 |
ansible_host=B | 引用本地 SSH 配置文件里的 Config 别名 | 真实 IP 和私钥依然由 .ssh/config 统一托管 |
ansible_user=root | SSH 登录用户名 | 演示环境账号 |
ansible_python_interpreter | 远端 ECS 的 Python 解释器路径 | /usr/bin/python3 |
BatchMode=yes | 禁用交互式密码弹窗询问 | 一旦认证失败直接退出并抛错 |
Inventory 切忌直接写入明文密码、私钥或验证码。学生在个人 ECS 操作时,建议使用普通用户搭配 sudo 权限。
[WSL] ansible-inventory --graph检查 Inventory 能否解析,预期 course_hosts 下只有 cloud-b。
[WSL] ansible course_hosts -m ansible.builtin.ping -oAnsible 的 ping 模块通过 SSH 登录远端,验证远端 Python 是否可用,并返回 pong;它不是网络层 ICMP ping。
2026-07-28 的真实控制端到云主机 B 结果:
教师机本地控制端到 root@SYG680400 · 2026-07-28 真实连接
teacher@local:lab-17$ ansible --version | sed -n '1,4p'ansible [core 2.19.2]config file = .../lab-17/starter/ansible.cfgansible python module location = .../ansiblepython version = 3.12.13“确认 Ansible 版本”保留了完整命令和与它相邻的输出。判断时应直接引用输出中的字段或状态,不要把步骤名称当成结论。
这一步是“Inventory 只选择 cloud-b,SSH 与远端 Python 能运行 ping 模块并返回 pong”证据链中的一环。
教师机不是学生 WSL;ping 不是 ICMP,也不证明容器或 HTTP 服务健康

整页截图与上方分步卡片来自同一条记录;敏感字段已经遮盖,较长命令可能经过换行排版。
沿目标选择、连接、期望状态、重复执行、健康断言和清理检查组织一次 Ansible 自动化。
Inventory 只列授权主机,先用 --graph 确认 group 和 alias,不在清单中保存密码与私钥。
site.yml 只管理本课资源:
- name: Deploy the isolated lesson 17 health site
hosts: course_hosts
gather_facts: falsehosts 限制目标为课程组。gather_facts: false 跳过本课不需要的全量事实采集,减少输出和等待;需要操作系统事实时再按需开启。
观看时留意:一个 Play 怎样把目标主机、任务和模块组织成可重复执行的描述?
与本课的关系:Playbook 的价值还要通过第二次执行验证,观察是否出现不必要的 changed。
创建目录使用 file 模块:
- name: Ensure the course directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"目录已经存在且权限符合时,模块返回 ok,不重复创建目录。
发布页面使用 copy 模块:
- name: Publish the expected lesson page
ansible.builtin.copy:
dest: /opt/xpk-course-demos/lesson-17/site/index.html
mode: "0644"
content: |
...
<p>release=lesson17-v1</p>
<p>health=lesson17-health-ok</p>copy 会比较内容。目标缺失或漂移时写入并返回 changed;内容一致时返回 ok。
容器先 inspect,再按条件创建:
- name: Start the isolated course container
ansible.builtin.command:
argv:
- docker
- run
- --detach
- --name
- cloud-course-lab-17-web
- --publish
- 127.0.0.1:18017:80
- nginx:1.27-alpine
when: lesson17_container_inspect.rc != 0容器存在时跳过创建,避免第二次运行出现名称冲突。入口只绑定远端回环地址,不新增公网安全组。
安全边界
不要把 docker rm -f $(docker ps -aq)、pkill、全局防火墙修改或未知服务重启放进 Playbook。自动化会放大命令的速度,也会放大错误范围。
[WSL] ansible-playbook --syntax-check site.yml检查 YAML 和任务结构能否解析。语法通过只过了第一关,SSH、权限、镜像、端口和业务内容是否都可用还得接着验证。
[WSL] ansible-playbook site.yml第一次运行真实结果:
cloud-course-lab-17-web;127.0.0.1:18017;ok=7 changed=3 failed=0。[WSL] ansible-playbook site.yml不修改清单,立即执行第二次。真实结果中目录和页面为 ok,创建容器任务为 skipping,HTTP 继续通过,recap 为 ok=6 changed=0 failed=0 skipped=1。
云主机 B 隔离目录和容器 · 2026-07-28 真实对比
...$ ansible-playbook site.yml # 第一次TASK [Ensure the course directories exist] changed: [cloud-b]TASK [Publish the expected lesson page] changed: [cloud-b]TASK [Start the isolated course container] changed: [cloud-b]boundary: image=nginx:1.27-alpine ports=127.0.0.1:18017->80http_status=200 health_marker=lesson17-health-okPLAY RECAPcloud-b ok=7 changed=3 unreachable=0 failed=0 skipped=0输出保留了捕获时的故障现象。它用于缩小故障范围;原始记录没有单独保存这条命令的退出码,因此卡片不推测 exit code。
这一步是“首次执行 changed=3,第二次 changed=0 且容器任务 skipped,回环端口、镜像和健康标记保持预期”证据链中的一环。
只证明这份 Playbook 在捕获环境与相同输入下未报告第二次变更,不证明所有 command 任务或其他主机幂等

整页截图与上方分步卡片来自同一条记录;敏感字段已经遮盖,较长命令可能经过换行排版。
关键提醒
changed=0 是一次有边界的证据:它只说明这份 Playbook 在当时环境和相同输入下没有报告变更。若任务错误地写了 changed_when: false,数字好看也不意味着真的幂等。
完整可观测性还包括指标、日志、追踪和告警。本课先做最小健康链:
| 层次 | 证据 | 能发现什么 | 不能发现什么 |
|---|---|---|---|
| SSH/Python | ping pong | 控制端能运行远端模块 | HTTP 与业务内容 |
| 容器 | inspect status/image | 容器运行与镜像 | 页面是否正确 |
| HTTP | status=200 | Web 入口响应 | 版本和内容是否正确 |
| 发布标记 | release=lesson17-v1 | 当前页面版本 | 所有业务功能 |
| 健康标记 | lesson17-health-ok | 课程健康契约 | 长期性能和外部依赖 |
| Play recap | failed/changed/unreachable | 自动化任务结果 | 用户体验趋势 |
观看时留意:日志、指标和链路分别能回答什么问题,单独一类信号会遗漏什么?
与本课的关系:本课从 HTTP 状态、内容契约和自动化结果入门,不要求部署完整可观测性平台。
health.yml 先读取容器,再用 uri 请求完整路径,最后用 assert 检查发布和健康标记。
[WSL] ansible-playbook health.yml健康时返回 lesson17 health check passed,recap 为 failed=0 changed=0。
输入脱敏后的 inventory、task、recap、容器、HTTP 与业务标记结果,让助教判断连接、幂等或健康问题。
本实验的原始聊天仅保存在当前标签页,不会写入全局课程助教上下文。
fault.yml 只把课程页面替换为漂移版本:
[WSL] ansible-playbook fault.yml页面仍由 Nginx 返回 200,但内容变成 release=unknown-drift,缺少健康标记。
[WSL] ansible-playbook health.yml真实检查中 uri 任务为 ok,随后 assert 失败:
HTTP 200 returned, but the expected lesson17 marker or release is missing这说明网络、容器和 HTTP 仍通,但发布内容不符合契约。
[WSL] ansible-playbook site.yml重新应用期望状态。copy 发现内容漂移并恢复页面,recap 为 changed=1 failed=0;容器任务继续 skipping,没有重建服务。
[WSL] ansible-playbook health.yml同一路径再次检查,容器 running、HTTP 200、release 与健康标记全部通过。
命令和相邻输出按原始捕获顺序拆开显示。切换步骤时,先看命令,再从输出中找判断依据。
...$ ansible-playbook fault.ymlTASK [Replace only the course page with a drifted version]changed: [cloud-b]PLAY RECAP cloud-b ok=1 changed=1 failed=0输出保留了捕获时的故障现象。它用于缩小故障范围;原始记录没有单独保存这条命令的退出码,因此卡片不推测 exit code。
这一步是“HTTP 200 内容缺少发布/健康标记时 assert 失败;重新应用 site.yml 只恢复页面,随后容器、HTTP、release 和 marker 通过”证据链中的一环。
只是主动健康检查和恢复闭环,不代表已部署指标、日志、追踪或持续告警平台

整页截图与上方分步卡片来自同一条记录;敏感字段已经遮盖,较长命令可能经过换行排版。
四步记录保留了故障注入、健康检查失败、重新应用期望状态和同路复测。Playbook 输出中的 failed=1 属于受控任务结果;卡片没有原始 Shell 退出码,因此不会额外猜测 exit code。
控制端到云主机 B · Ansible 2.19.2 · 2026-07-28
teacher@local:lab-17$ ansible-playbook health.ymlTASK [Request the complete local service path] ok: [cloud-b]TASK [Require the expected health marker] FAILED!HTTP 200 returned, but the expected lesson17 marker or release is missingPLAY RECAP cloud-b ok=2 changed=0 failed=1health_exit=2HTTP 请求成功,但发布标记或健康标记缺失,断言使 Playbook 返回失败
HTTP 请求成功,但发布标记或健康标记缺失,断言使 Playbook 返回失败
只覆盖课程页面内容契约,不是完整的指标、日志和追踪系统

整页截图与上方分步卡片来自同一条记录;敏感字段已经遮盖,较长命令可能经过换行排版。
Request the complete local service path 为 ok,说明 HTTP 请求已经返回;紧接着的断言失败,说明响应内容缺少课程发布标记或健康标记。两行放在一起,才能看出这是“200 但内容不对”,而不是 SSH 或 Web 连接失败。
控制端到云主机 B · Ansible 2.19.2 · 2026-07-28
teacher@local:lab-17$ ansible-playbook site.ymlTASK [Publish the expected lesson page] changed: [cloud-b]TASK [Start the isolated course container] skipping: [cloud-b]PLAY RECAP cloud-b ok=6 changed=1 failed=0 skipped=1Playbook 只把漂移页面恢复为期望内容,正常容器没有被重建
Playbook 只把漂移页面恢复为期望内容,正常容器没有被重建
changed=1 只描述本次受 Ansible 管理的任务,不代表主机上没有其他变化

整页截图与上方分步卡片来自同一条记录;敏感字段已经遮盖,较长命令可能经过换行排版。
页面任务为 changed,容器任务为 skipping,recap 只有一次变更。这说明本次恢复修改了漂移文件,没有重建正常容器;它只描述 Playbook 管理范围内的变化。
控制端到云主机 B · Ansible 2.19.2 · 2026-07-28
teacher@local:lab-17$ ansible-playbook health.ymllesson17 health check passedstatus=running image=nginx:1.27-alpinehttp_status=200 release=lesson17-v1 health=lesson17-health-okPLAY RECAP cloud-b ok=4 changed=0 failed=0容器、HTTP、发布版本和健康标记在恢复后同时满足课程契约
容器、HTTP、发布版本和健康标记在恢复后同时满足课程契约
只证明捕获时的本地回环服务,不证明公网路径或长期稳定性

整页截图与上方分步卡片来自同一条记录;敏感字段已经遮盖,较长命令可能经过换行排版。
最后一张图沿原来的 health.yml 路径复测。running、http_status=200、release=lesson17-v1 和 lesson17-health-ok 分别覆盖容器、协议、版本与内容契约;这仍不是公网路径或长期监控结论。
| 结果 | 含义 | 下一步 |
|---|---|---|
ok | 任务成功且未报告变更 | 继续核对业务证据 |
changed | 任务成功并报告状态改变 | 确认改变符合计划 |
failed | 已连接目标,但模块或断言失败 | 保留任务和错误详情 |
unreachable | SSH、认证、路由或目标不可达 | 回到控制端到远端链路 |
skipped | 条件不满足或显式跳过 | 检查条件是否符合预期 |
rescued | block 失败后进入 rescue | 仍需确认最终状态 |
ignored | 失败被忽略 | 警惕把真实故障掩盖 |
不要给关键健康任务加 ignore_errors: true 来让 recap 变绿。失败门禁的价值就在于阻止错误结果被批准。
[WSL] bash verify.sh脚本检查课程文件、Inventory、Playbook 语法、Ansible ping 和 health.yml。远端不可达或健康断言失败时返回非 0。
[WSL] ansible-playbook cleanup.yml只删除 cloud-course-lab-17-web 和 /opt/xpk-course-demos/lesson-17。
[WSL] ansible course_hosts -m ansible.builtin.shell \
-a "docker ps -a --filter name=^/cloud-course-lab-17-web$; ss -lnt 'sport = :18017'"人工检查容器不存在,18017 没有监听。不要把这条 shell 模块扩展成全局清理。
完整步骤见实训 17:Ansible 幂等部署与健康检查。
提交文件名:
班级_学号_姓名_第17次课_Ansible与可观测性报告.docx只提交一个 Word 到智慧职教“第17次课”。不提交 SSH 私钥、实际 Inventory、完整地址、Ansible 临时目录、完整日志或其他业务数据。
检查目标边界、连接、两次执行、健康契约、漂移恢复、清理和凭据保护。
用 6 道题检查 Inventory、ping、幂等、健康契约、故障恢复和安全清理。
ansible.builtin.ping 验证 SSH 与远端 Python,不等于 ICMP 或业务健康。changed=0 是幂等证据,但仍受任务写法和环境边界限制。下一次课将把 Compose、备份恢复、自动健康检查和故障自动修复流程整合为课程综合项目。
进入综合项目前,整理一份最小交接清单:
第 18 次课不会再单独讲这些工具,而是检查它们能否被另一位同学按交接说明复现。
以下资料在 2026-07-28 核对:
助教会读取当前课程页面和结构化学习记录,但不会读取正文实验框里的原始聊天。