Введение
У Courtyard Sessions уже работает сайт на двух серверах. Новая программа готова, но одновременное обновление обоих узлов может оставить посетителей без доступного сайта. С помощью Ansible проверьте состояние и опубликуйте новый пакет по одному серверу за раз.
Нужны базовые навыки работы с каталогами в терминале и опыт использования небольшого инвентаря Ansible, переменных и задач file/copy. Шлюз, доступ по SSH и пакеты уже подготовлены. До первого применения лабораторная работа объясняет место выполнения задач, делегирование, партии узлов, ссылки на версии и полные проверки HTTP. Продолжительность оценена автором с учётом чтения и обычных исправлений.
Проверьте, где выполняется каждая задача
На этом шаге вы создадите playbook для наблюдения без изменения состояния: он покажет активную версию каждого сервера и фактический состав пула маршрутизации.
У Courtyard Sessions уже есть работающий сайт для посетителей. Откройте вкладку Website. Прочитайте Good things, around the corner. и нажмите The program. Страница прокрутится к двум мероприятиям, первое — Coffee in the courtyard. Нажмите Download the program (CSV) и откройте загруженный файл: после заголовка time,session,place идут два показанных мероприятия. CSV — текстовая таблица, в которой поля разделены запятыми. Во время обновления эта настоящая загрузка должна оставаться доступной.
Шлюз распределяет короткие HTTP-запросы между двумя серверами. Исключение сервера из активного пула прекращает передачу ему новых запросов шлюза; добавление возвращает его в чередование. Подготовленный небольшой шлюз не завершает длительные соединения перед отключением и не воспроизводит целую облачную платформу публикации. Оба сервера — контейнеры, управляемые по SSH и использующие ядро этой учебной VM. Контроллер — машина, на которой работают Ansible и шлюз.
Переключитесь на вкладку Controller. Начальный каталог — /home/labex/project. pwd показывает текущий каталог, а cat выводит файл:
pwd
cat inventory.yml
cat group_vars/all.yml
Инвентарь содержит структурированные данные, совместимые с YAML. В его группу website входят web1 и web2, каждый с реальным адресом ansible_host и подготовленными параметрами SSH. Группа control содержит controller с локальным подключением. Используйте inventory.yml, который выбран проектным ansible.cfg; исходный inventory.ini образа не является инвентарём курса.
Переменные группы задают 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 читает задачи и применяет их к инвентарю:
ansible-playbook inspect.yml
Каждый сервер должен показать release: courtyard-preview, а active должен включать web1 и web2. Форматирование и адреса могут отличаться. Заметьте: первый uri выполняется на сервере, а чтение пула показывает делегирование вроде web1 -> controller. Обе задачи только читают состояние.
Если отступы YAML неверны, снова скопируйте полный блок, исправив файл, и повторите запуск. Этот playbook никогда не должен копировать файлы или менять пул. Теперь у вас есть полезный инструмент для повторного наблюдения после публикации.
Публикуйте по одному серверу за раз
На этом шаге вы доставите утверждённый пакет сайта по SSH, проверите настоящее содержимое для посетителей и вернёте каждый сервер в обслуживание до начала следующего.
Оставайтесь во вкладке Controller, в /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 пула принимает HTTP POST к /pool с телом 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 и обновите страницу. Заголовок должен быть A little room for a good afternoon. Нажмите The program, проверьте три мероприятия, снова загрузите CSV и откройте новый файл, чтобы подтвердить три полные строки. При наличии предыдущей загрузки браузер может добавить номер к имени. Plan a visit по-прежнему должна вести к информации о входе без ступенек через сад.
Обновлённая программа содержит три утверждённых мероприятия; настоящая загрузка CSV находится рядом со списком.

Это демонстрирует функциональное обновление двух узлов в подготовленной среде коротких запросов. Оно не устанавливает гарантию публикации без перерывов для произвольного трафика или производственного приложения.
Резюме
Вы создали инструмент наблюдения без изменения состояния и выполнили последовательное развёртывание Ansible, доставив настоящий сайт и полную загрузку его программы. Делегированные изменения пула и реальные проверки связывают каждое решение о публикации с нужным управляемым сервером.



