Введение
Juniper & Oat нужны одинаковые утверждённые цены на напитки в двух кафе без потери названия, часов работы и фирменного предложения каждого филиала. Вы обновите два настоящих хоста, управляемых через SSH, одним шаблоном и playbook Ansible, а затем исправите намеренно созданное отклонение меню.
Нужны базовая навигация в терминале и опыт чтения небольшого инвентаря и playbook Ansible. Переменные хостов, формирование 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
Имена web1 и web2 в инвентаре указывают на разные подготовленные SSH-хосты. Прочитайте инвентарь и утверждённые данные филиалов с помощью 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 предоставляет переменные одноимённому хосту инвентаря. Оставьте эти данные и подготовленные настройки 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 предоставляет переменные всем хостам инвентаря. Следующий here-document записывает целый файл: > заменяет именно этот путь, а разделитель YAML в кавычках не позволяет оболочке подставлять значения внутри содержимого. Закрывающий разделитель должен стоять один с начала строки. При опечатке повторите весь блок; он заменит только ваш файл переменных.
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 выбирает оба хоста инвентаря, -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: он должен содержать три утверждённых товара этого филиала с его собственным названием в каждой строке. Одной работающей главной страницы недостаточно.

Сравните общие цены слева с местным предложением справа. 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 запрашивает become для этой задачи ad hoc. content модуля copy заменяет ровно указанный 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-файлы и исправили реальное отклонение, сохранив допустимые различия филиалов. Повторный запуск без изменений подтвердил сходимость объявленных меню.



