Введение
Главная страница Riverlight Weekend готова, но посетители не могут открыть программу и страницу площадки. Оба сервиса-источника уже работают. С помощью Ansible подключите входной сервис к выбранным участникам инвентаря, перечитывайте конфигурацию только при изменениях и защитите рабочие маршруты от неподходящей замены.
Нужны базовая навигация в терминале и практика чтения небольшого инвентаря, переменных и playbook Ansible. Роли HTTP-сервисов, группы, переменные других узлов, JSON, запросы только для чтения и обработчики объясняются до применения. Все программы и сервисы мероприятия подготовлены. Длительность оценена автором с учётом чтения и обычных исправлений.
Подключите страницы программы и площадки
На этом шаге вы подключите сайт мероприятия к подготовленным источникам программы и площадки, используя участников инвентаря вместо фиксированных адресов в шаблоне.
У Riverlight Weekend работают главная страница и памятка посетителю. Ссылки на программу и площадку пока показывают сообщение о недоступности страницы. Входной сервис — небольшой подготовленный обратный HTTP-прокси: он пересылает запрос другому сервису и возвращает его настоящий ответ. Данные программы не копируются на главную страницу. Входной сервис работает на контроллере; два источника — контейнеры, разделяющие ядро этой учебной VM. Это не три независимые VM. Программы и сервисы готовы; вы управляете только подключениями входного сервиса.
Откройте вкладку Event Site. Главная страница должна показывать A little closer to the river. Нажмите Explore the program: сообщение This page is taking a break. — ожидаемый ответ HTTP 502, поскольку маршрут ещё не подключён. Вернитесь по Back to the weekend, затем откройте Visitor guide в навигации. Эта страница уже работает и должна оставаться доступной.
Перейдите во вкладку Controller, которая начинается в /home/labex/project. pwd выводит текущий каталог:
pwd
Если потеряете рабочий каталог, вернитесь командой cd /home/labex/project. Ошибка в имени файла вызывает «No such file or directory»: проверьте написание, при необходимости посмотрите содержимое каталога через ls templates и повторите. Сбрасывать VM не нужно.
cat выводит файл. Прочитайте одобренный выбор сервисов, инвентарь и данные каждого узла:
cat approved-services.txt
cat inventory.ini
cat host_vars/web1.yml
cat host_vars/web2.yml
Группы [program] и [venue] выбирают по одному источнику. web1 предоставляет текущую программу, а web2 — площадку. Файлы узлов содержат настоящие service_address и порты. Адреса могут различаться между VM. Некоторые узлы также предлагают архивный сервис; доступность ещё не делает его одобренной текущей программой.
Группа [gateway] содержит entry. ansible_connection=local означает, что Ansible управляет входным сервисом на контроллере без дополнительного SSH-перехода. /usr/bin/python3 — подготовленный локальный интерпретатор Python. HTTP-порт обозначает слушающий сервис по адресу; один узел может запускать несколько сервисов на разных портах.
До изменения входного сервиса прочитайте каждый выбранный источник напрямую. Подготовленные конфигурации curl содержат настоящие URL. --config читает параметры curl из файла; fail превращает ошибки HTTP в неуспех команды, silent скрывает индикатор прогресса, а show-error сохраняет полезные сообщения. В этих файлах нет паролей или токенов.
cat requests/web1.conf
curl --config requests/web1.conf
cat requests/web2.conf
curl --config requests/web2.conf
Ответы JSON идентифицируют program и venue, сайт riverlight и их редакции. JSON — структурированный текст с именованными полями. Успешный запрос доказывает прямую работу источника, но ещё не работу маршрутов сайта.
Теперь создайте шаблон маршрутов. Переменная groups в Ansible сопоставляет имена групп их участникам; [0] выбирает первого участника группы, в которой он один. hostvars получает его переменные. Следующий playbook называет этих участников program_host и venue_host. В выражениях Jinja оператор ~ соединяет значения как текст, включая числовые порты. | to_json формирует JSON-строку с корректными кавычками, избегая их небезопасной ручной сборки.
Команда shell использует > для замены только этого файла. <<'EOF' передаёт следующие строки в cat буквально; одинарные кавычки не дают shell интерпретировать скобки Jinja. Вставьте всё до завершающего EOF на отдельной строке. Если пропущенный разделитель оставил shell ждать продолжения, нажмите Ctrl+C и вставьте полную команду заново.
cat > templates/routes.json.j2 <<'EOF'
{
"program": {{ ('http://' ~ hostvars[program_host].service_address ~ ':' ~ hostvars[program_host].program_port) | to_json }},
"venue": {{ ('http://' ~ hostvars[venue_host].service_address ~ ':' ~ hostvars[venue_host].venue_port) | to_json }}
}
EOF
Напишите playbook для входного сервиса. gather_facts: false отключает сбор ненужных сведений о машине. vars определяет выражения, вычисляемые по текущему инвентарю. template создаёт файл, устанавливает режим 0644 (владелец может писать, читать могут все) и уведомляет обработчик только при изменении файла маршрутов. Обработчик — задача, запускаемая после уведомления, обычно в конце play.
Входной сервис хранит маршруты в памяти, поэтому одной замены файла недостаточно. Подготовленный помощник отправляет настоящий сигнал SIGHUP только существующему процессу этого входного сервиса и ждёт подтверждения. command обработчика использует argv — список отдельных аргументов. playbook_dir обозначает каталог playbook и ограничивает управляемый путь входного сервиса этим проектом. Ни sudo, ни перезапуск сервиса не нужны.
cat > connect.yml <<'EOF'
- name: Connect the event services
hosts: gateway
gather_facts: false
vars:
program_host: "{{ groups['program'][0] }}"
venue_host: "{{ groups['venue'][0] }}"
tasks:
- name: Write the inventory-derived routes
ansible.builtin.template:
src: templates/routes.json.j2
dest: "{{ playbook_dir }}/entry/routes.json"
mode: '0644'
notify: Reload entry
handlers:
- name: Reload entry
ansible.builtin.command:
argv:
- python3
- /opt/labex/service-links/reload.py
- "{{ playbook_dir }}/entry"
EOF
Сначала проверьте структуру YAML, затем просмотрите прогноз изменения файла. --syntax-check не применяет задачи. --check прогнозирует поддерживаемые изменения; --diff показывает прежний и предлагаемый текст. Просмотр не отправляет сигнал шлюзу и не доказывает работу готового сайта.
ansible-playbook connect.yml --syntax-check
ansible-playbook connect.yml --check --diff
Разница должна заменить неподключённые маршруты 127.0.0.1:9 адресами и портами текущих источников. Примените playbook обычным образом:
ansible-playbook connect.yml
Ожидаются изменение шаблона и RUNNING HANDLER [Reload entry]. Вернитесь в Event Site, откройте Program и прочитайте три текущих пункта: Paper and ink, Acoustic afternoon и Shared supper. Нажмите Download the schedule, чтобы скачать schedule.csv: время, названия и места должны совпадать с таблицей. Через Visit откройте Riverside Yard, затем Visitor guide. Главная страница и памятка должны продолжать работать.
Подключённая программа должна показывать эти три пункта, а Download the schedule — находиться сразу под таблицей:

Вернитесь в Controller. Настоящий файл можно также сохранить и прочитать в VM. В curl параметр -f вызывает неуспех при HTTP-ошибке, -sS скрывает прогресс, но показывает ошибки, а -o задаёт выходной файл:
curl -fsS http://127.0.0.1:8081/program/schedule.csv -o schedule.csv
cat schedule.csv
Ожидаемое содержимое:
time,session,place
10:30,Paper and ink,Making table
13:00,Acoustic afternoon,River stage
16:00,Shared supper,Long table
127.0.0.1 означает этот контроллер, где входной сервис слушает порт 8081. Общедоступный адрес в браузере перенаправляет к тому же сервису.
Повторите тот же playbook:
ansible-playbook connect.yml
Итог должен показать changed=0 и failed=0 без запуска обработчика. Это сходимость: текущая декларация уже соответствует файлу. Работоспособность результата подтверждают настоящие страницы и скачивание, а не один итог выполнения.
Защитите рабочие маршруты до замены
На этом шаге вы добавите настоящие проверки HTTP-идентичности до записи маршрутов, затем отклоните доступный архивный сервис, сохранив рабочий сайт.
Продолжайте в Controller, в /home/labex/project. HTTP 200 означает успешный ответ сервера, но не подтверждает тип сервиса, мероприятие или редакцию. Текущие программа и площадка идентифицируются через /identity. В подготовленном group_vars/all.yml записаны одобренные сайт и редакции:
cat group_vars/all.yml
Добавьте предварительную проверку, то есть проверку до изменения. assert останавливает play при ложном условии. Первая проверка требует ровно одного участника в каждой группе сервисов. Каждая задача uri отправляет настоящий HTTP GET с контроллера. return_content: true сохраняет тело, а JSON-ответ также доступен через .json. register сохраняет результат задачи под именем переменной для следующих задач. Последние проверки сравнивают настоящую идентичность с одобренными значениями.
HTTP-задачи намеренно используют check_mode: false: запросы только для чтения должны работать и при предварительном просмотре. Иначе обычная задача uri может пропускаться в режиме проверки. Настройка относится только к чтению и не разрешает применять шаблон или перечитывать конфигурацию при просмотре. Тайм-аут в три секунды не позволяет отсутствующему источнику надолго задержать упражнение.
Замените playbook следующей полной версией с защитой. Сохраните шаблон и расположите все проверки идентичности перед задачей шаблона:
cat > connect.yml <<'EOF'
- name: Connect the event services
hosts: gateway
gather_facts: false
vars:
program_host: "{{ groups['program'][0] }}"
venue_host: "{{ groups['venue'][0] }}"
tasks:
- name: Require one member for each service
ansible.builtin.assert:
that:
- groups['program'] | length == 1
- groups['venue'] | length == 1
- name: Read the selected program identity
ansible.builtin.uri:
url: "http://{{ hostvars[program_host].service_address }}:{{ hostvars[program_host].program_port }}/identity"
return_content: true
timeout: 3
check_mode: false
register: program_probe
- name: Read the selected venue identity
ansible.builtin.uri:
url: "http://{{ hostvars[venue_host].service_address }}:{{ hostvars[venue_host].venue_port }}/identity"
return_content: true
timeout: 3
check_mode: false
register: venue_probe
- name: Check the actual service identities
ansible.builtin.assert:
that:
- program_probe.json.service == 'program'
- program_probe.json.site == event_site
- program_probe.json.edition == program_edition
- venue_probe.json.service == 'venue'
- venue_probe.json.site == event_site
- venue_probe.json.edition == venue_edition
- name: Write the inventory-derived routes
ansible.builtin.template:
src: templates/routes.json.j2
dest: "{{ playbook_dir }}/entry/routes.json"
mode: '0644'
notify: Reload entry
handlers:
- name: Reload entry
ansible.builtin.command:
argv:
- python3
- /opt/labex/service-links/reload.py
- "{{ playbook_dir }}/entry"
EOF
ansible-playbook connect.yml --syntax-check
Сохраните отпечаток рабочего файла маршрутов. sha256sum вычисляет хеш содержимого, а > сохраняет результат отдельно для последующего сравнения:
sha256sum entry/routes.json > routes-before.sha256
Подготовленный альтернативный инвентарь выбирает архив как программу. Прочитайте его и намеренно попробуйте с -i, выбирающим инвентарь только для этой команды. Обычный файл инвентаря не изменяется:
cat inventory-wrong-service.ini
ansible-playbook -i inventory-wrong-service.ini connect.yml
Ожидается неуспех с ненулевым кодом и failed=1. Архив доступен и возвращает JSON, но его редакция не одобрена. Проверка должна завершиться неуспехом до Write the inventory-derived routes; итог должен показать changed=0 без запуска обработчика. Это отличается от отказа соединения, при котором uri вообще не получает ответ. Оба случая должны остановить запись.
Проверьте сохранённый отпечаток. -c поручает sha256sum сравнить текущий файл с записанным хешем:
sha256sum -c routes-before.sha256
Ожидается entry/routes.json: OK. В Event Site откройте Program и снова скачайте текущее расписание. Отклонённое предложение должно оставить программу доступной, а не просто сохранить какой-либо файл.
Вернитесь в Controller и используйте обычный инвентарь:
ansible-playbook connect.yml --check --diff
ansible-playbook connect.yml
Обе выбранные идентичности должны пройти проверку. Уже правильным маршрутам не нужны изменения: итог показывает changed=0, обработчик не запускается. При ошибке отступов YAML сравните полный блок, используйте пробелы вместо табуляции и перепишите connect.yml. Ошибка синтаксиса не является успешным отклонением источника предварительной проверкой.
Ваши границы — connect.yml, templates/routes.json.j2 и маршруты входного сервиса через этот playbook. Сохраните предоставленные инвентари, данные узлов, одобренные редакции, запросы curl, содержимое источников, заметки организаторов, главную страницу и памятку. Для перечитывания используйте /opt/labex/service-links/reload.py с каталогом входного сервиса этого проекта. Не фиксируйте текущие имена узлов или адреса в шаблоне и запросах, не копируйте страницы во входной сервис, не скрывайте ошибки, не перезапускайте источники и не изменяйте подготовленный шлюз. При изменении инвентаря подключение должны по-прежнему определять заданные участники и порты.
Резюме
Вы подключили настоящие сервисы программы и площадки маршрутами из инвентаря, проверили сайт и точный CSV, а обработчик перечитывал только изменённую конфигурацию. Затем вы отклонили доступный, но неодобренный источник до записи маршрутов, сохранив текущий сайт. Следующая независимая лабораторная меняет участников сервиса, оставляя старый источник и его данные.



