Последовательное обновление серверов Ansible: serial и проверка готовности

Playbook перезапускает приложение на всех хостах группы. Даже если каждая машина возвращается за несколько секунд, одновременный restart оставляет сервис без работающих экземпляров. Serial позволяет проходить play партиями: закончить весь набор задач на первой части хостов и только затем перейти к следующей.

Но размер партии отвечает лишь за количество одновременно обрабатываемых машин. Для безопасного продолжения нужно ещё подтвердить, что обновлённый экземпляр обслуживает нужную версию. Запущенный процесс и готовый сервис — разные наблюдения.

Определим условия учебного стенда

Предположим, на хостах app уже установлен systemd-сервис demo-app. Он запускается из /opt/demo-app/current, а release-каталог /opt/demo-app/releases/2026-10-29.1 заранее доставлен на каждый сервер. Локальный HTTP endpoint /health отвечает JSON со status=ok и version=2026-10-29.1. Перед использованием адаптируйте эти пути и контракт ответа к своему приложению.

- name: Switch application one host at a time
  hosts: app
  become: true
  gather_facts: false
  serial: 1
  any_errors_fatal: true
  vars:
    release_version: "2026-10-29.1"
  tasks:
    - name: Check installed release directory
      ansible.builtin.stat:
        path: "/opt/demo-app/releases/{{ release_version }}"
      register: release_dir
    - name: Require a prepared release
      ansible.builtin.assert:
        that:
          - release_dir.stat.isdir | default(false)
    - name: Switch current symlink
      ansible.builtin.file:
        src: "/opt/demo-app/releases/{{ release_version }}"
        dest: /opt/demo-app/current
        state: link
      notify: Restart application
    - name: Apply the pending restart before health check
      ansible.builtin.meta: flush_handlers
    - name: Wait for the expected version
      ansible.builtin.uri:
        url: http://127.0.0.1:8080/health
        return_content: true
        status_code: 200
      register: health
      until: >
        (health.status | default(0)) == 200 and
        (health.json.status | default('')) == 'ok' and
        (health.json.version | default('')) == release_version
      retries: 12
      delay: 5
  handlers:
    - name: Restart application
      ansible.builtin.systemd_service:
        name: demo-app
        state: restarted

После переключения ссылки handler выполняется до HTTP-проверки. Без flush_handlers отложенный restart мог бы произойти позже, а readiness успела бы увидеть старый процесс. Проверка версии не позволяет принять старый успешный /health за готовность нового релиза.

Почему failure должен останавливать следующие партии

В примере any_errors_fatal требует прекратить play после неустранённой ошибки. Если первый экземпляр не прошёл readiness, продолжать переключение остальных опасно: можно последовательно потерять все рабочие серверы. Не добавляйте ignore_errors к этой проверке только ради завершения playbook.

Размер партии serial выбирают по ёмкости оставшихся экземпляров. Значение 1 удобно для первого стенда, но даже оно не гарантирует доступность: на двух серверах один оставшийся должен выдержать весь трафик. Forks ограничивает число работников Ansible и не заменяет такую границу партии.

Что происходит с трафиком

Playbook проверяет локальный процесс на управляемом хосте: uri выполняется там, поэтому 127.0.0.1 относится к серверу приложения. Это не проверка внешнего пути через балансировщик. В рабочем процессе часто нужно сначала вывести сервер из распределения трафика и дождаться завершения активных запросов, затем обновить, проверить и вернуть.

Команды отключения и включения зависят от конкретного балансировщика, поэтому их нельзя заменить универсальным echo. Разместите эти реальные действия внутри той же партии. Возвращать экземпляр следует только после успешной readiness; при ошибке сохраните его отключённым до решения оператора.

Повторный запуск и откат

Когда ссылка уже указывает на нужную версию, file не должен уведомлять handler повторно, но readiness всё равно проверяется. Если приложение уже остановлено, одного отсутствующего уведомления недостаточно для восстановления: добавьте отдельную задачу state: started, если это часть принятого контракта роли.

Для отката потребуется существующий каталог предыдущего релиза и совместимость данных. Смена ссылки не отменяет миграцию базы. Прежде чем автоматизировать обратный переход, проверьте, может ли старая версия работать с текущей схемой и файлами.

На стенде намеренно верните неправильную version из первого хоста. Дальнейшие серверы не должны обновляться. Затем исправьте readiness и повторите запуск. Эта отрицательная проверка подтверждает главный смысл serial: следующий экземпляр получает обновление после проверенного результата предыдущего, а не просто после паузы.

Ещё по теме

Продолжить изучение

Хотите собрать настройку серверов в повторяемый процесс? Inventory, роли, шаблоны и обновление сервисов входят в программу «Ansible: автоматизация инфраструктуры».

Продолжить на практике

  • Ansible: автоматизация инфраструктуры ↗

    Настраивайте серверы воспроизводимо: inventory, playbooks, роли, шаблоны и секреты. Соберите автоматизацию для нескольких окружений, проверьте повторный запуск и обновляйте сервисы управляемо.