介绍
Courtyard Sessions 的网站已经在两个后端上正常运行。新版节目单已准备好,但同时更新两个节点可能让访客无站可用。你将用 Ansible 检查运行状态,再逐台发布新包。
你需要具备基本终端导航能力,并练习过小型 Ansible inventory、变量和 file/copy 任务。网关、SSH 访问和发布包均已准备好。本实验会在使用前解释执行位置、委派、批次、版本链接和完整 HTTP 内容检查。时长是作者估计,包含阅读和普通错误修正。
看清每个任务在哪里执行
在这一步,你将制作一个只读检查 playbook,显示每个后端正在运行的版本,以及实际参与流量分配的节点列表。
Courtyard Sessions 的访客网站已经可用。打开 Website Tab,阅读 **Good things, around the corner.**,然后点击 The program。页面会滚动到两个节目,首个是 Coffee in the courtyard。点击 Download the program (CSV) 并打开下载文件:表头为 time,session,place,后面是页面展示的两个节目。CSV 是用逗号分隔字段的纯文本表格。更新期间,这份真实的访客下载必须保持可用。
网关将短 HTTP 请求分配给两个后端。将某个后端移出活动流量池,会停止向它分配新的网关请求;将它加入后,它便重新参与轮换。这个准备好的小型网关不负责排空长连接,也不模拟完整的云端发布平台。两个后端都是通过 SSH 管理的容器,共享训练 VM 的内核。控制端是运行 Ansible 和网关的机器。
切换到 Controller Tab。起始目录为 /home/labex/project。pwd 打印当前目录,cat 打印文件内容:
pwd
cat inventory.yml
cat group_vars/all.yml
inventory 使用兼容 YAML 的结构化数据。website 组包含 web1 和 web2,分别保存实际的 ansible_host 地址和准备好的 SSH 连接信息。control 组包含使用本地连接的 controller。请使用项目 ansible.cfg 选定的 inventory.yml;镜像初始生成的 inventory.ini 不是本课程的 inventory。
组变量提供了 backend_port(8090)、仅供控制端使用的 pool_api、允许管理的 release_root、本地 package_root 和版本标识。baseline_release 为 courtyard-preview,approved_release 为 courtyard-weekend。它们是目录名称,不是 Ansible 或软件的版本号。HTTP 端口标识某个地址上监听的服务。/identity 返回 JSON,以结构化文本标识后端实际运行的版本。/pool 返回控制端当前的活动节点列表。
如果命令显示“No such file or directory”,用 ls 检查拼写,必要时通过 cd /home/labex/project 返回工作目录。读取命令可以安全重复执行。请保留所有提供的包文件、连接数据、原始版本文件和私密的组织者备注。
默认情况下,Ansible 通过 SSH 在每个选定的后端上执行任务。在后端上,127.0.0.1 指的是该后端自身。delegate_to: controller 则让这一个任务在控制端执行,而控制端自己的 127.0.0.1 上运行着流量池 API。委派不会改变 inventory_hostname:它仍然表示当前正在管理的后端。因此,控制端请求可以引用正确的后端。
创建 inspect.yml。cat > filename 会写入文件,并替换同名现有文件。<<'YAML' 开始一个 here-document:请一直粘贴到独占一行的结束标记 YAML。带引号的分隔符让粘贴的文本保持原样;Ansible 在运行 playbook 时处理表达式。缩进使用空格。{{ variable }} 插入 Ansible 变量。gather_facts: false 跳过无关机器事实的收集。
uri 模块发出真实 HTTP 请求;默认方法 GET 用于读取资源。register 将实际结果保存到指定名称下。对于 JSON 响应,.json 保存解析后的字段。debug 打印这些已保存的观察结果,不会创建版本或流量池状态。
cat > inspect.yml <<'YAML'
- name: Inspect the running website without changing it
hosts: website
gather_facts: false
tasks:
- name: Read this backend on its own loopback address
ansible.builtin.uri:
url: "http://127.0.0.1:{{ backend_port }}/identity"
register: backend
- name: Read the pool on the controller
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
delegate_to: controller
register: pool
- name: Show the actual release and active pool
ansible.builtin.debug:
msg:
node: "{{ inventory_hostname }}"
release: "{{ backend.json.release }}"
active: "{{ pool.json.active }}"
YAML
运行 playbook。ansible-playbook 读取任务,并将其应用到 inventory 中的主机:
ansible-playbook inspect.yml
每个后端都应显示 release: courtyard-preview,而 active 应包含 web1 和 web2。输出格式和地址可能不同。注意,第一个 uri 在后端执行,而流量池读取会显示类似 web1 -> controller 的委派执行位置。这两个任务都是只读的。
如果 YAML 缩进有误,重新复制完整代码块来修正文件,然后重试。这个 playbook 不应复制文件或改变流量池。你现在有了一个实用的观察工具,发布后可以再次运行它。
逐台发布后端版本
在这一步,你将通过 SSH 交付获准的网站包,验证实际访客内容,并在处理下一台前让当前后端重新提供服务。
留在 Controller Tab 的 /home/labex/project 目录。列出发布包,再读取获准节目单。ls 用于列出目录条目:
ls packages
cat packages/courtyard-weekend/schedule.csv
获准节目单包含三个节目:Bread and stories、Acoustic afternoon 和 Print a postcard。仅首页正常还不够,访客需要与之对应的完整下载。courtyard-incomplete 包留待下一实验使用;这里选择 courtyard-weekend,现有的 release_id 变量已经如此设置。
playbook 使用 serial: 1,每个批次处理一台主机。在这个批次内,它将当前节点移出流量池、复制发布包、切换活动版本链接、检查两项业务资源,再将节点加回。之后才开始下一批。order: sorted 让示例从 web1 开始。any_errors_fatal: true 显式传播未处理的失败,以停止整个 play。在这里的一台主机批次中,这台主机失败就意味着整个批次失败。这个设置不会撤销已经完成的任务。下一实验会加入恢复机制。
流量池 API 接受发往 /pool 的 HTTP POST,请求体为 JSON:node 指定受管节点,enabled 为布尔值 false 时移除,为 true 时加入。body_format: json 正确序列化这个请求体。流量池任务在控制端执行,文件任务则在选定的 SSH 后端执行。
file 搭配 state: directory 创建目录;模式 '0755' 允许所有者写入,并允许所有人读取和进入目录。copy 把选定的控制端发布包发送到后端。src 末尾的 / 表示复制目录内容,并用 '0644' 模式生成可读文件。file 搭配 state: link 创建或更新符号链接:current 指向一个版本目录。准备好的 HTTP 服务每次请求都通过这个链接读取真实文件。切换链接即可改变活动网站,无需重启服务,原始版本目录则保持完整。
对于在控制端执行的 HTTP 探测,hostvars[inventory_hostname].ansible_host 显式取得受管后端的地址。委派时,不限定来源的连接变量可能指向被委派的主机。return_content: true 将实际响应正文保存为 .content。
assert 要求 that 表达式成立。lookup('file', ...) 在控制端读取预期包文件,~ 拼接路径字符串。rstrip=False 保留结尾换行,因此比较的是完整正文,而非去掉尾部空白的片段。加回节点前,完整候选首页和完整获准 CSV 都必须匹配。404 表示服务器已响应,但该资源缺失;连接错误表示无法到达服务。两者都不能视为健康版本。
写入完整发布流程。here-document 采用与检查工具相同的文件写入方法:
cat > rollout.yml <<'YAML'
- name: Release one website backend at a time
hosts: website
order: sorted
gather_facts: false
serial: 1
any_errors_fatal: true
tasks:
- name: Drain this backend from the pool
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: false
delegate_to: controller
- name: Create this release directory
ansible.builtin.file:
path: "{{ release_root }}/releases/{{ release_id }}"
state: directory
mode: '0755'
- name: Copy the selected package over SSH
ansible.builtin.copy:
src: "{{ package_root }}/{{ release_id }}/"
dest: "{{ release_root }}/releases/{{ release_id }}/"
mode: '0644'
- name: Activate the selected release
ansible.builtin.file:
src: "{{ release_root }}/releases/{{ release_id }}"
dest: "{{ release_root }}/current"
state: link
- name: Read the real candidate homepage
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/"
return_content: true
delegate_to: controller
register: homepage
- name: Require the complete candidate homepage
ansible.builtin.assert:
that: homepage.content == lookup('file', package_root ~ '/' ~ release_id ~ '/index.html', rstrip=False)
- name: Read the real candidate download
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/schedule.csv"
return_content: true
delegate_to: controller
register: download
- name: Require the complete approved program
ansible.builtin.assert:
that: download.content == lookup('file', package_root ~ '/' ~ approved_release ~ '/schedule.csv', rstrip=False)
- name: Rejoin only after both business checks
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: true
delegate_to: controller
YAML
先检查 YAML 和任务结构,不执行发布。--syntax-check 验证语法,但不能证明业务结果正确:
ansible-playbook rollout.yml --syntax-check
如果检查报告拼写或缩进错误,重新复制完整文件并重试。随后执行真实发布:
ansible-playbook rollout.yml
web1 的任务应先完成,之后才处理 web2。两台主机最终都应为 failed=0、unreachable=0。移出和加回流量池的操作请求可能显示 ok;HTTP 是否成功及其实际效果,比汇总中的 changed 计数更重要。如果普通编辑错误中断运行,修正完整文件后,用同一个获准包重新运行。重复这个流程仍会执行移除、检查和加回操作,并非完全不做操作的部署。
用原先的工具检查运行状态:
ansible-playbook inspect.yml
两个节点现在都应报告 courtyard-weekend,且都在活动流量池中。切换到 Website Tab 并重新加载页面,标题应为 A little room for a good afternoon.。点击 The program,核对三个节目,再次下载 CSV 并打开新文件,确认三行数据完整。已有下载时,浏览器可能在新文件名中加上数字。Plan a visit 仍应通向花园入口的无障碍访问信息。
更新后的节目单包含全部三个获准节目,真实 CSV 下载链接就在列表旁。

这些结果证明,在准备好的短请求环境中,两个节点的网站更新正常完成;它们不能证明任意流量或应用在生产环境中都能零停机发布。
总结
你制作了只读检查工具,并用 Ansible 逐台发布真实网站及其完整节目单下载。委派的流量池操作与真实内容检查,让每次发布判断都对应到当前受管后端。



