更新两店菜单

AnsibleBeginner
立即练习

介绍

Juniper & Oat 的两家咖啡店需要使用同一套获准饮品价格,同时保留各分店的身份、营业时间和本地特色。你将用一个 Ansible 模板和 playbook 更新两个通过真实 SSH 管理的目标,然后修复一次刻意制造的菜单漂移。

你需要掌握基本终端导航,并有阅读小型 Ansible inventory 和 playbook 的经验。实验会在使用时解释主机变量、JSON 渲染、安全预览以及受管内容与本地内容的区别。预备好的工具链和网站让你专注于自动化,而不是软件安装。

区分共享价格与分店信息

在本步骤中,你将识别两家咖啡店共享什么、哪些信息必须不同,然后为一个可复用模板声明获准数据。

Juniper & Oat 有两家社区咖啡店。它们采用相同的日常饮品和价格,但各自有自己的营业时间、特色和本地公告。你的任务是在不替换这些本地信息的情况下更新共享菜单。预备主机 web1 和 web2 是两个通过 SSH 管理、共享训练 VM 内核的容器,并非独立 VM。Controller 运行 Ansible,每个目标运行自己的真实菜单服务。

打开 Menu Preview,选择 First branch,阅读菜单、营业时间和本地特色。使用浏览器的返回按钮回到分店列表,再选择 Second branch。两店都显示旧的 Flat white 价格;Park Café 的营业时间也不正确。各页面的本地特色和公告已经正确。Visit 打开同一分店的到店信息,Download menu 下载该分店当前真实的 CSV。这些是页面链接,不是 LabEx 标签页。

切换到 Controller 标签页。终端起始目录是项目目录;如果切错目录,下面这个明确的命令也能让你回到这里:

cd /home/labex/project

inventory 中的 web1 和 web2 指向不同的预备 SSH 目标。使用 cat 查看 inventory 和获准的分店输入;cat 只读取文件,不改变内容:

cat inventory.ini
cat host_vars/web1.yml host_vars/web2.yml

Harbor Café 应在 07:00–16:00 营业,Park Café 应在 08:00–18:00 营业。host_vars 目录为名称匹配的 inventory 主机提供变量。保留这些输入和预备 SSH 设置,不要修改。这种差异是有意安排,并非需要消除的错误。

获准的共享菜单作为场景数据提供。在编写变量前先阅读:

cat approved-menu.json

其中列出 Espresso 3.20、Flat white 4.20 和 Tea 3.00。价格保存为带引号的字符串,以便在格式化后仍保留两位小数。menu_items 将是两个主机共享的 YAML 列表,每个对象包含名称和价格。

预备的 group_vars 和 templates 目录为空。名为 group_vars/all.yml 的文件为所有 inventory 主机提供变量。下面的 here-document 会写入一个完整文件:> 替换这个确切的目标文件,带引号的 YAML 分隔符阻止 shell 展开其中内容。结束分隔符必须独占一行并从行首开始。如果内容输入有误,重新执行完整代码块即可;它只替换你的变量文件。

cat > group_vars/all.yml <<'YAML'
menu_items:
  - name: Espresso
    price: "3.20"
  - name: Flat white
    price: "4.20"
  - name: Tea
    price: "3.00"
YAML

Jinja 模板将解析后的值插入受管文件。双花括号用于求表达式的值,管道符用于应用过滤器。这里的 to_nice_json 将整个对象序列化为有效、易读的 JSON,包括非 ASCII 字符,避免手工为每个插入值加引号。两店使用的是同一个模板:branch_name 和 opening_hours 来自各自的主机输入,menu_items 则为共享数据。

cat > templates/menu.json.j2 <<'JINJA'
{{ {'branch': branch_name, 'hours': opening_hours, 'items': menu_items} | to_nice_json }}
JINJA

运行 Ansible 前,读取两个作者源文件,发现缺失的引号、错误缩进或分隔符:

cat group_vars/all.yml templates/menu.json.j2

网站此时还没有改变。你只是声明了期望数据,尚未应用。下一步 Ansible 会在各主机的变量上下文中渲染这份源文件。

向两家咖啡店应用同一个 Playbook

在本步骤中,你将向两个真实 SSH 目标预览并部署同一份声明,然后使用实际菜单和下载。

留在 Controller 的 /home/labex/project 中。play 选择主机,tasks 声明受管状态。become: true 请求在预备目标上进行获准的提权,使文件可以归 root 所有;这不会更改 LabEx 登录账户。file 模块声明目录,template 模块渲染共享源文件,并只替换 menu.json。明确的归属和带引号的权限让目录遍历与公开菜单读取保持可预期:目录为 0755,菜单为 0644。

本地文件 local-special.txt、local-notice.txt 和私密的 staff-notes.txt 不在你的菜单声明管理范围内。不要替换目录内容或修改这些文件。保留它们的字节、root 归属和原权限:特色与公告为 0644,私人便签为 0600。服务在每次请求时读取 menu.json,因此只更新菜单数据不需要人为加入重启或 handler。

编写完整 playbook。YAML 列表项使用 -,通过空格缩进嵌套各模块的参数。src 下的路径指向控制节点上的源文件,dest 位于每个受管目标。

cat > menus.yml <<'YAML'
- name: Keep both branch menus current
  hosts: all
  gather_facts: false
  become: true
  tasks:
    - name: Keep the menu directory available
      ansible.builtin.file:
        path: /srv/branch-menu
        state: directory
        owner: root
        group: root
        mode: "0755"

    - name: Render the shared menu with each branch's inputs
      ansible.builtin.template:
        src: templates/menu.json.j2
        dest: /srv/branch-menu/menu.json
        owner: root
        group: root
        mode: "0644"
YAML

首先使用 --syntax-check 验证 playbook 语法,而不应用变更:

ansible-playbook menus.yml --syntax-check

如果报告 YAML 错误,阅读所指文件和行号。将缩进与上面的完整代码块比较,重写该代码块,再执行语法检查。Ansible 语法错误不需要重置 VM。

--check 预测受支持模块的变更,--diff 显示拟议内容的前后差异。对于这些不含秘密的菜单文件,它很有用,但预览并非真实业务测试。一起使用这两个选项:

ansible-playbook menus.yml --check --diff

两个主机都应预测菜单变更。diff 应修正价格和 Park Café 的营业时间,同时保留不同的分店身份。目标文件尚未改变。切换到 Menu Preview,打开或刷新各分店,确认仍能看到旧值。再切回 Controller,不带预览选项应用同一份声明:

ansible-playbook menus.yml

各主机的汇总不应出现不可达或失败任务。changed 的确切数量可能不同,因为各主机需要修正的内容不同。用 ad hoc 命令读取实际受管数据:all 选择两个 inventory 主机,-m 指定模块,-a 传入模块参数。command 模块在各目标上运行 cat,而不是在控制节点上运行。

ansible all -m ansible.builtin.command -a 'cat /srv/branch-menu/menu.json'

两店都应包含获准的日常饮品和价格。Harbor Café 与 Park Café 必须保留各自的名称和营业时间。

现在切换到 Menu Preview,刷新每家分店。阅读三项价格、分店名称、营业时间、特色和本地公告。打开 Visit,再通过 Menu 返回。选择 Download menu 并打开 CSV:它必须包含该分店的三项获准饮品,每行都带有该分店名称。只看到首页成功打开还不够。

Harbor Café 的共享价格正确,同时保留自己的营业时间和本地特色。

将左侧共享价格与右侧本地特色进行比较。Park Café 使用同一份获准饮品列表,同时保留自己的营业时间和特色。

如果浏览器显示菜单缺失或无效的错误,回到 Controller,阅读命令输出,并将 JSON 模板与步骤 1 比较。修正控制节点上的源文件,再运行 playbook。如果目标不可达,读取 inventory.ini 并与原始输出比较;不要禁用 SSH 信任检查或更改平台账户。

回到 Controller。在输入不变时重复执行,应没有待应用的变更:

ansible-playbook menus.yml

查看两行汇总:

web1 ... changed=0 ... unreachable=0 ... failed=0 ...
web2 ... changed=0 ... unreachable=0 ... failed=0 ...

这就是幂等配置:期望文件已经匹配,Ansible 因此不再重写它们。这不代表普通请求或服务日志停止发生。

修复漂移并保留本地内容

在本步骤中,你将故意破坏一个受管菜单值,然后使用未改动的控制节点源文件将其恢复,同时保留本地内容。

留在 Controller。配置漂移是实际受管状态不再符合声明意图。分店获准的不同营业时间不是漂移;被手工改动的共享价格才是。

下面是仅针对 web1、范围明确的刻意故障。-b 为这个 ad hoc 任务请求 become。copy 的 content 仅替换指定菜单 JSON,不替换目录。它采用正确结构,但将 Espresso 价格写为未经批准的 0.01。这个临时文件是故意写错的;不要把其中价格复制到 group_vars。

ansible web1 -b -m ansible.builtin.copy -a 'dest=/srv/branch-menu/menu.json content={"branch":"Harbor Café","hours":"07:00–16:00","items":[{"name":"Espresso","price":"0.01"},{"name":"Flat white","price":"4.20"},{"name":"Tea","price":"3.00"}]} owner=root group=root mode=0644'

切换到 Menu Preview,刷新 First branch,观察错误的 Espresso 价格。第二家分店和两店的本地特色应不受影响。这是目标文件的真实变更,不是模拟的红色状态灯。

切回 Controller。不要编辑原始源文件。预览已有的期望状态:

ansible-playbook menus.yml --check --diff

应该只有 web1 预测变更。Web2 已符合获准状态。应用同一个 playbook,恢复受管菜单:

ansible-playbook menus.yml

切换到 Menu Preview,刷新两家分店。Espresso 应再次为 3.20。两店的本地特色、公告、营业时间和菜单下载都必须仍然正确。自动化只负责一个配置文件,不负责目录内的所有本地文件。

回到 Controller,最后再重复执行一次:

ansible-playbook menus.yml

两店汇总都应再次显示 changed=0、unreachable=0 和 failed=0。成功修复来自控制节点数据中的可复用意图与共享模板,而不是手工修一次目标。将来获准的共享饮品发生变化时,这个模板也应继续可用。

总结

你区分了共享价格、分店输入和本地内容,向两个 SSH 目标预览并应用同一个模板,使用了实际页面和 CSV 下载,并在保留合理分店差异的同时修复了真实配置漂移。无变更的重复执行确认声明的菜单已经收敛。