Остановите неудачное обновление

AnsibleBeginner
Практиковаться сейчас

Введение

У нового пакета 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 по-прежнему должна показывать информацию о доступности.

Восстановленный сайт продолжает предлагать два исходных мероприятия и полную загрузку.

Восстановленный сайт Courtyard Sessions с исходной программой

При обычной ошибке 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 завершилось успешно.