Введение
У нового пакета Courtyard Sessions убедительная главная страница, но нет загрузки для посетителей. Эта независимая среда начинается с исходного сайта, работающего на обоих серверах. Улучшите подготовленный сценарий Ansible: при отказе кандидата он должен восстановить предыдущую пригодную версию и остановиться до обновления другого узла.
Сначала выполните работу «Обновляйте по одному хосту» либо получите равноценный опыт наблюдения, делегирования, последовательных партий, операций copy/file и полных HTTP-проверок. Здесь вы научитесь сохранять фактическую ссылку и восстанавливать состояние с block/rescue. Время оценено автором, а не измерено на начинающих.
Восстановите неудачную версию и остановите развёртывание
На этом шаге вы сохраните настоящую предыдущую версию, восстановите текущий узел после неудачной проверки и явно остановите следующую партию.
Откройте вкладку Website. Исходный заголовок — Good things, around the corner. Проверьте два исходных мероприятия через The program и Download the program (CSV). Шлюз и два SSH-сервера независимы от предыдущей лабораторной работы; её файлы или процессы не нужны.
Переключитесь на Controller, начинающуюся в /home/labex/project. Подготовленные inspect.yml и rollout.yml — инструменты из предыдущей работы. Прочитайте выбранный пакет и текущее состояние:
cat group_vars/all.yml
ls packages/courtyard-incomplete
ansible-playbook inspect.yml
release_id выбирает courtyard-incomplete. Главная страница и идентификатор присутствуют, но schedule.csv отсутствует. Оба настоящих сервера всё ещё сообщают courtyard-preview. Сохраните байты всех пакетов: добавление CSV к кандидату скрыло бы дефект вместо отработки восстановления. Пока не запускайте незащищённое развёртывание.
block объединяет задачи, которые могут завершиться ошибкой; его задачи rescue выполняются после обычного отказа задачи блока. До блока сохраните current с помощью stat, читающего метаданные файловой системы. follow: false читает саму ссылку, а не целевой каталог. Сохранённое previous.stat.lnk_source — фактическая предыдущая цель: восстановление возвращает реально работавшую версию, а не предполагаемый фиксированный каталог.
Восстановление должно вернуть ссылку, запросить главную страницу и полный CSV восстановленного сайта и сравнить их с предыдущим пакетом. Фильтр basename извлекает имя каталога версии из сохранённой цели, чтобы существующие сравнения с lookup('file', ...) читали соответствующий локальный пакет. Возвращать в пул можно только восстановленный исправный узел. Если восстановленный ресурс отсутствует или содержит неверные данные, эта задача rescue завершается ошибкой и узел остаётся исключённым; другой сервер продолжает обслуживать посетителей.
Успешное rescue снимает влияние исходной ошибки на решения Ansible об обработке ошибок. Поэтому само any_errors_fatal не останавливает дальнейшие партии после успешного восстановления. Явное fail в конце rescue сообщает об отклонении и останавливает следующую партию. Ненулевой код выхода предусмотрен, хотя предыдущий сайт снова работает. Недопустимый YAML и недоступные по SSH хосты не запускают этот механизм rescue; это не общая гарантия отката.
Замените rollout.yml следующей полной версией. cat > заменяет только этот учебный playbook; разделитель в кавычках сохраняет выражения Ansible. Соблюдайте показанные пробелы:
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: Remember the actual previous release link
ansible.builtin.stat:
path: "{{ release_root }}/current"
follow: false
register: previous
- name: Update this backend or recover it
block:
- 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
rescue:
- name: Restore the previous release link
ansible.builtin.file:
src: "{{ previous.stat.lnk_source }}"
dest: "{{ release_root }}/current"
state: link
- name: Read the restored homepage
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/"
return_content: true
delegate_to: controller
register: restored_home
- name: Require the complete previous homepage
ansible.builtin.assert:
that: restored_home.content == lookup('file', package_root ~ '/' ~ (previous.stat.lnk_source | basename) ~ '/index.html', rstrip=False)
- name: Read the restored download
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/schedule.csv"
return_content: true
delegate_to: controller
register: restored_download
- name: Require the complete previous program
ansible.builtin.assert:
that: restored_download.content == lookup('file', package_root ~ '/' ~ (previous.stat.lnk_source | basename) ~ '/schedule.csv', rstrip=False)
- name: Rejoin only after both restoration checks
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: true
delegate_to: controller
- name: Stop subsequent batches after recovery
ansible.builtin.fail:
msg: Candidate failed; the previous release is healthy again. Stop this rollout.
YAML
Проверьте синтаксис до изменения любого сервера:
ansible-playbook rollout.yml --syntax-check
Затем попытайтесь опубликовать неполную версию. Ожидается, что эта команда завершится ошибкой после восстановления сайта:
ansible-playbook rollout.yml
Немедленно прочитайте конечный статус. $? содержит код выхода предыдущей команды, а echo выводит его. Другая команда заменила бы сохранённое оболочкой значение:
echo $?
Ожидайте ненулевой статус, обычно 2. Проверка новой страницы проходит; загрузка кандидата возвращает HTTP 404; восстановленные страница и CSV проходят; затем Stop subsequent batches after recovery намеренно завершается ошибкой. HTTP 404 обозначает отсутствующую загрузку. SSH-соединение остаётся доступным для восстановления. web2 не должен приступать к задачам публикации.
Снова запустите инструмент наблюдения:
ansible-playbook inspect.yml
Оба сервера должны показывать courtyard-preview и оставаться активными. Переключитесь на Website, обновите страницу, откройте The program, снова загрузите CSV и убедитесь, что два исходных мероприятия доступны. Plan a visit по-прежнему должна показывать информацию о доступности.
Восстановленный сайт продолжает предлагать два исходных мероприятия и полную загрузку.

При обычной ошибке YAML или имени файла используйте ls, исправьте полный playbook и повторите проверку синтаксиса. Если предыдущее редактирование изменило ссылку до того, как восстановление заработало, сначала сохраните правильный playbook выше. Можно использовать уже изученное исправное развёртывание, чтобы вернуть известный исходный пакет, выбрав его и как версию, и как ожидаемую программу. Кавычки объединяют два присваивания в один аргумент --extra-vars:
ansible-playbook rollout.yml --extra-vars "release_id=courtyard-preview approved_release=courtyard-preview"
Эта команда восстановления должна завершиться успешно. Подтвердите оба узла через inspect.yml, затем повторите неполного кандидата исходной командой ansible-playbook rollout.yml, чтобы продемонстрировать требуемые отказ и восстановление. Не сбрасывайте VM и не переписывайте кандидата. Сохраняйте playbook пригодным для повторного использования: запросы пула должны использовать предоставленный pool_api, пути — release_root и package_root, а задачи — показанные прямые модули.
Резюме
Вы восстановили фактический предыдущий сайт после настоящей ошибки отсутствующей загрузки. Обе проверки восстановления управляют возвратом узла, а явная ошибка останавливает следующие партии Ansible, даже если rescue завершилось успешно.



