Найти в Дзене
Infra as Code по-русски

Релизы без простоя: как Ansible делает обновления безопаснее

Эта статья показывает, как Ansible помогает выпускать обновления быстро и без остановок сервиса. Мы разберём простыми словами 6 приёмов, которые снижают риски, делают релизы предсказуемыми и экономят время команды: быстрые разовые операции, мягкая обработка ошибок, ожидание готовности сервисов, фоновые «долгие» задачи, поэтапные раскатки и однократные шаги «для всей компании». Когда продукт растёт, важно обновлять его без простоя и без «неожиданностей». Ansible — это инструмент, который превращает «человеческие инструкции» в точные и повторяемые шаги. Ниже — базовые приёмы, которые дают предсказуемость и контроль. Идея. Когда разовая команда уместна: «жив ли сервер?», «поставить один пакет», «перезапустить сервис». Когда нужен плейбук: всё, что повторяется; всё, что важно не забыть; всё, что нужно показать коллеге или прогнать в CI. Примеры разовых команд: # Проверить связь со всеми серверами ansible all -m ping # Перезапустить nginx на группе app ansible app -m service -a 'name=nginx
Оглавление

Анотация

Эта статья показывает, как Ansible помогает выпускать обновления быстро и без остановок сервиса. Мы разберём простыми словами 6 приёмов, которые снижают риски, делают релизы предсказуемыми и экономят время команды: быстрые разовые операции, мягкая обработка ошибок, ожидание готовности сервисов, фоновые «долгие» задачи, поэтапные раскатки и однократные шаги «для всей компании».

Зачем это бизнесу

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

1) Разовые команды и плейбуки: когда что использовать

Идея.

  • Разовая команда (ad-hoc) — быстро проверить гипотезу или сделать маленькую правку на всех серверах.
  • Плейбук — записанный сценарий. Его можно запускать много раз и быть уверенным в результате.

Когда разовая команда уместна: «жив ли сервер?», «поставить один пакет», «перезапустить сервис».

Когда нужен плейбук: всё, что повторяется; всё, что важно не забыть; всё, что нужно показать коллеге или прогнать в CI.

Примеры разовых команд:

# Проверить связь со всеми серверами
ansible all -m ping
# Перезапустить nginx на группе app
ansible app -m service -a 'name=nginx state=restarted' --become
# Установить пакет htop на базах
ansible db -m package -a 'name=htop state=present' --become

Итог. Разовые команды — «быстрый термометр». Плейбуки — «правила лечения».

2) Мягкая обработка ошибок: block / rescue / always

Идея. Делать изменения смело, но с «планом Б»: если шаг не удался, вернуть всё как было и зафиксировать статус.

Как это выглядит:

-2

Зачем бизнесу. Ошибка не превращается в простой: есть автоматический откат и понятный отчёт.

Что за блоки и когда они срабатывают — простым текстом

  • block — это основная часть. Здесь перечисляем шаги, которые хотим выполнить. Они идут по порядку и выполняются, пока не случится ошибка.
  • rescue — это план отката. Он запускается только если любой шаг внутри block завершился с ошибкой и эта ошибка не была проигнорирована (то есть без ignore_errors: true). В rescue мы возвращаем систему в рабочее состояние и фиксируем, что пошло не так.
  • always — это действия, которые нужно сделать в любом случае: уборка временных файлов, сбор статусов, логирование. always выполняется всегда после block и/или rescue (если хост доступен).

Когда и зачем это использовать

  • Рисковые изменения: правка конфигов, переключение версий/фич-флагов — нужен предсказуемый откат при сбое.
  • «Транзакции» из нескольких шагов: сделать → проверить → включить. Если проверка не прошла — вернуть всё как было.
  • Чистка «хвостов»: временные файлы, локи, режимы обслуживания — убрать независимо от исхода.
  • Понятный отчёт о результате: в always собрать статусы/логи и показать, чем всё кончилось.
  • Снижение простоя: ошибки не валят весь плей — быстро откатываемся и продолжаем.
  • Стандартизация релизов: единый шаблон «план А / план Б / уборка» во всех плейбуках.

Типичные подводные камни

  • ignore_errors внутри block: ошибка «проглочена» → rescue не запустится. Если нужен контролируемый фейл — используйте failed_when.
  • Неверное место для notify: перезапуск до проверки включает плохой конфиг. Ставьте notify только после успешной валидации/переключения.
  • Ожидание «на глаз»: sleep вместо проверки состояния. Используйте retries + until в самом блоке.
  • Нет материала для отката: не сделали бэкап/кандидатный файл — rescue нечем откатывать. Готовьте «подушку» заранее.
  • always ломается: тяжёлые или хрупкие задачи в always могут сами упасть и скрыть первопричину. Держите always коротким и устойчивым.
  • Недоступный хост: rescue не сработает при UNREACHABLE. Сначала восстанавливаем доступ, затем — откат.
  • Неедиомпотентные шаги: повторный запуск усугубляет состояние. Делайте копии/темп-файлы и проверяйте перед заменой.
  • Потерянные переменные: в rescue обращаются к данным, которых нет из-за раннего сбоя. Используйте | default(...) и готовьте альтернативный путь.
  • Рассыпанные условия/права: забыли общий when/become на уровне block — часть задач не совпадёт по контексту. Ставьте их на сам block.

3) Ждать готовность сервисов без «слепого» сна: retries + until

Идея. Не зашивать фиксированные паузы. Вместо этого — проверять реальное состояние: порт открылся? страница отвечает 200?

-3

Плюс. Релизы идут быстрее и стабильнее: не ждём лишнего, но и не бежим раньше времени.

Что за блоки и когда они срабатывают — простым текстом

  • until — это условие «готовности». Задача будет повторяться, пока выражение не станет истинным (или пока не закончатся попытки).
  • retries — сколько раз пробовать; delay — пауза между попытками в секундах.
  • Обычно мы сохраняем результат задачи через register и проверяем его поля в until (например, код ответа HTTP или факт существования файла).
  • Если указать только retries/delay без until, повторов не будет — задача выполнится один раз.
  • Если после всех попыток условие так и не стало истинным, задача помечается как failed.

Частые кейсы для until:

  • HTTP-проверка: result.status == 200 (модуль uri).
  • Открытие порта: result.state == "started" или result.elapsed < ... (модуль wait_for).
  • Команда завершилась успешно: result.rc == 0 (модули command/shell).
  • Файл появился: result.stat.exists (модуль stat).

Типичные ошибки:

  • «result is undefined» — забыли register.
  • Сравнение разных типов (строка vs число). Приводите типы: result.status | int == 200.
  • Повторная задача имеет побочные эффекты (каждый ретрай что-то меняет). Держите проверку читающей, а не «изменяющей».
  • Слишком большие ожидания или бесконечные «зависания». Планируйте верхнюю границу: общее_время ≈ retries × delay.

4) Долгие задачи — в фоне: async / poll и «fire-and-forget»

Идея. Некоторые шаги занимают минуты (миграция БД, копирование больших файлов). Их можно запустить в фоне, а сам релиз продолжать.

-4

Плюс. Команда не «застревает» на одном шаге. Менеджеру видно: релиз идёт, статус контролируем.

Что за блоки и когда они срабатывают — простым текстом

  • async — говорит Ansible, сколько максимум секунд можно выполнять задачу на удалённой машине. Задача стартует в фоне и может жить без открытого SSH-сеанса до указанного лимита.
  • poll — как часто опрашивать статус фоновой задачи.
    Если poll: 0 — режим
    «fire-and-forget»: запустили и сразу пошли дальше, не ждём результат.
    Если poll не указан — Ansible будет ждать завершения, опрашивая примерно
    раз в 10 секунд (блокирующее ожидание).
    Если poll: N — будет ждать и опрашивать каждые N секунд.
  • Результат фоновой задачи сохраняется в переменной, где есть ansible_job_id. Чтобы позже проверить статус, используйте модуль async_status и передайте туда этот jid.
  • Если задача выполняется дольше, чем указано в async, Ansible прервёт её на удалённом хосте. Ставьте разумный запас (например, 3600 секунд для часовых миграций).
  • Асинхронность задаётся на каждый хост отдельно. Параллелизм между хостами контролируется обычными механизмами (serial, forks, при желании — throttle на задаче).
  • Для очень долгих операций (часы/после перезагрузки) надёжнее запускать их под присмотром systemd или скрипта-демона и контролировать готовность отдельной проверкой (порт/HTTP/файл), а не держать гигантский async.

Типовые сценарии:

  • Долгая миграция БД, большая сборка/копирование — запускаем в фоне, продолжаем плей, позже собираем статусы.
  • Массовые операции на десятках хостов — не блокируемся на «медленных», плей движется дальше.

Частые ошибки:

  • Запустили poll: 0, но потеряли ansible_job_id — потом нечем проверять статус. Всегда делайте register и сохраняйте jid.
  • Поставили слишком маленький async — задача убивается на полпути. Дайте запас.
  • Ожидают «готовность» без явной проверки. Асинхронность — это про запуск, а готовность проверяйте отдельно (uri, wait_for, command + until).
  • Надеются на rescue для фоновой задачи. Если вы ушли дальше по плейбуку, rescue уже не «поймает» падение процесса в фоне. Делайте явную проверку статуса и ветвление.

5) Обновлять по очереди, без общей остановки: serial, strategy=free, max_fail_percentage

Идея. Обновлять не все сервера сразу, а по одному или небольшими группами. Если что-то пошло не так — остановиться вовремя.

-5

Плюс. Пользователи не замечают релиз: часть узлов всегда остаётся рабочей.

Что за блоки и когда они срабатывают — простым текстом

  • serial — задаёт, сколько хостов обновляем одновременно.

    Можно число (serial: 1 — по одному) или процент (serial: 20% — по 20% группы за раз).

    Работает
    пачками: Ansible берёт первую пачку, выполняет все задачи, затем переходит к следующей. Это и есть «роллинг».
  • strategy=free — говорит Ansible не ждать медленные хосты внутри текущей пачки.

    Быстрые хосты идут вперёд, медленные догоняют. Это ускоряет раскатку, но
    порядок между хостами не гарантируется.

    Важно: strategy=free
    не отменяет serial — размер пачки сохраняется, просто внутри пачки хосты не синхронизируются шаг-в-шаг.
  • max_fail_percentage — «красная кнопка безопасности».

    Если
    процент упавших хостов превысил порог (например, 20), Ansible останавливает плей.

    Помните про малые группы: при 2 хостах 50% — это уже
    1 хост.

Когда и зачем это использовать

  • Нужен роллинг без простоя → ставим маленький serial (часто 1), чтобы всегда оставалась рабочая часть кластера.
  • Хотим ускорить раскатку в пределах пачки → добавляем strategy=free.
  • Хотим остановить релиз при серии фейлов → задаём max_fail_percentage (обычно 10–30% в зависимости от риска).

Типичные подводные камни

  • Слишком большой serial может просадить доступность (слишком много узлов одновременно выводятся из строя).
  • С strategy=free шаги на разных хостах могут идти в разнобой — не рассчитывайте на строгую синхронизацию между хостами (например, «сначала у всех остановить, потом у всех запустить» — это не так).
  • max_fail_percentage в маленьких группах ведёт себя «ступеньками»: считайте в штуках, а не только в процентах.
  • Обработчики (handlers) запускаются после пачки. Планируйте это: перезапуск сервиса может происходить несколько раз — по завершении каждой пачки.

6) Общие действия — один раз: delegate_to, run_once

Идея. Если нужно создать общий артефакт (например, ключ или токен), делаем это один раз на «контрол-хосте», а потом распространяем.

-6

Плюс. Нет дублирования и «расхождений» между серверами.

Что за блоки и когда они срабатывают — простым текстом

  • delegate_to — попросить Ansible выполнить конкретную задачу на другом хосте, а не на том, к которому вы сейчас подключены.

    Частые случаи: сделать действие на
    localhost (машина, где запущен Ansible), на бастионе, на балансировщике (lb), на DNS-сервере и т. п. Пример: «для каждого app-сервера вывести его из балансировщика» — сам шаг выполняется на хосте lb.
  • run_once — выполнить задачу только один раз для всего текущего запуска (или «пачки» при serial).

    Удобно для «общих артефактов»: сгенерировать ключ, получить токен, скачать архив билда. Вместо сотни одинаковых действий — одно.
  • Вместе: run_once + delegate_to: localhost — «сделай один раз и сделай это на локальной машине». Классика: сгенерировать ключ/токен/архив, а потом разослать.
  • Важно понимать про роллинг (serial): run_once выполняется один раз на всю задачу, но привязывается к первой группе хостов в текущей пачке. Если хотите гарантированно один раз на весь плей, безопаснее делать run_once: true + delegate_to: localhost.
  • delegate_to не копирует переменные автоматически между хостами. Он просто говорит, где выполнить шаг. Если нужно «поделиться результатом» со всеми, либо сохраните артефакт (файл) и разошлите, либо используйте hostvars[...] для доступа к результату.

Когда и зачем это использовать

  • Один общий артефакт для всех: сгенерировать ключ/сертификат/токен один раз на localhost, затем разослать → run_once: true + delegate_to: localhost.
  • Действия на стороннем узле: вывести/ввести хост из балансировщика, обновить записи DNS, дернуть API CI/CD — выполняем задачу на lb, dns, bastion через delegate_to.
  • Сбор метаданных/билда: скачать архив релиза, подготовить шаблоны, упаковать конфиг один раз — потом распространяем.
  • Экономия времени и идемпотентность: вместо N одинаковых команд на каждом хосте — один подготовительный шаг и простая раздача файлов.
  • Безопасность: секрет (ключ/токен) создаём локально и контролируем его права; на сервера доставляем только необходимое.

Типичные подводные камни

  • Забыли delegate_to: «однократная» команда запускается на первом хосте группы, а не на локалке. Решение: всегда явно указывать delegate_to: localhost, если ожидаете локальное выполнение.
  • Только run_once без делегации: задача выполнится один раз, но на первом хосте текущей пачки (особенно заметно при serial). Добавляйте делегацию туда, где действительно нужно.
  • Повтор при роллинге: с serial «один раз» может случиться на первую пачку, а не на весь плей. Критичные «единственные» шаги ставьте до роллинга или делегируйте на localhost.
  • Потерянный результат: сделали run_once, но не сохранили вывод. Используйте register, а затем раздавайте файл или обращайтесь к переменной через hostvars[groups['all'][0]]….
  • Смешение контекстов: команда делегирована на lb, а переменные берутся с app-хоста. Проверяйте, откуда берёте пути/файлы (inventory_hostname vs делегируемый узел).
  • Права и секреты: ключи/токены лежат в /tmp без ограничений. Выдавайте строгие режимы (0600), чистите временные файлы.
  • Параллельность: несколько «однократных» задач без явного порядка могут конфликтовать. Кладите их подряд и избегайте лишнего параллелизма для подготовки артефактов.

Почему это работает (в двух словах)

Ansible выполняет шаги точно по сценарию. Мы управляем тем, когда и как они идут:

  • «План Б» при ошибках → меньше простоев.
  • Проверка готовности вместо «ожидания на глаз» → быстрее и надёжнее.
  • Фоновые операции → релиз не стоит на месте.
  • Роллинг-подход → сервис остаётся доступным.
  • Общие шаги один раз → меньше ручной работы и меньше ошибок.

CTA

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