Антипаттерн: плейбук Ansible, который никто не запускал полгода
Полгода назад вы написали плейбук, который поднимает сервер с нуля: пакеты, конфиги, firewall. Он даже отработал один раз — на том самом сервере, который сейчас лежит. По плану восстановления нужно просто запустить ansible-playbook site.yml на новой машине. Но плейбук падает на третьей задаче, потому что репозиторий пакета переехал, а дальше — на пятнадцатой, потому что у модуля изменился обязательный параметр. Восстановление, которое должно было занять двадцать минут, превращается в отладку YAML в момент, когда каждая минута простоя считается деньгами.
Содержание
Почему плейбук «работал», а потом перестал
Ansible-плейбук — не программа, которую написали один раз и она навсегда осталась верной. Это снимок ваших представлений об инфраструктуре на момент написания: какие пакеты нужны, какие версии доступны, как называется сервис в systemd, какой синтаксис у модуля. Мир вокруг этого снимка продолжает меняться, а сам снимок — нет, если его не перезапускать. Три источника расхождения работают параллельно и незаметно:
- Дрейф конфигурации (config drift). Сервер живёт своей жизнью в обход плейбука: кто-то руками поставил пакет для отладки и забыл откатить, кто-то поменял параметр в конфиге через
nanoпо SSH «на скорую руку» (этот отдельный антипаттерн разобран в статье про правку конфигов прямо на проде), автообновления подняли версию пакета мимо плейбука. Каждое такое изменение — маленький шаг от состояния, которое описывает код, к состоянию, в котором сервер находится на самом деле. - Устаревание самого плейбука. У модулей Ansible со временем меняются параметры и поведение по умолчанию, у внешних инструментов, которые задачи вызывают (systemd, пакетные менеджеры, API провайдеров), — тоже. Синтаксис, который был нормой на момент написания, в новом окружении может давать предупреждение, а ещё позже — ошибку.
- Изменившиеся внешние зависимости. Плейбук тянет пакеты из репозиториев, скачивает архивы по прямым URL, обращается к внешним API. Репозиторий переехал, ключ подписи истёк, версия пакета, зафиксированная в коде, пропала из индекса — и задача, которая раньше отрабатывала за секунду, падает с ошибкой сети или 404.
По отдельности каждая причина выглядит малозначительной. Вместе, за полгода без единого прогона, они превращают рабочий плейбук в артефакт, который годится разве что как документация о том, каким сервер был когда-то.
Почему это не всплывает раньше аварии
Проблема коварна тем, что она молчит. Сервер работает, мониторинг зелёный — и никто не спрашивает себя «а если бы пришлось поднять это заново прямо сейчас?». Плейбук не деградирует на глазах — он просто перестаёт быть актуальным описанием реальности, а актуальность нельзя увидеть, не попытавшись это описание применить.
Это тот же класс ошибки, что и с бэкапами, которые никто не пробовал разворачивать: файл лежит, отчёт зелёный, а восстанавливается он или нет — узнают только в момент аварии. Разница в том, что для бэкапов эта мысль уже стала общим местом, а для IaC-кода — почти нет. Плейбук воспринимается как код, а код «просто есть», пока его не открыли. Но это не библиотека, которую тестирует CI при каждом коммите, — это скрипт, который проверяется только запуском на реальном хосте, а запускать его «просто чтобы проверить» на проде страшно и в реальной работе почти никогда не происходит.
Добавьте типичную для небольших команд ситуацию: писал плейбук один человек полгода назад, возможно, уже не в этой роли. Никто не помнит, какие задачи условные, какие переменные обязательные, какие шаги вообще не тестировались за пределами первого запуска. Восстановление после аварии — худший момент, чтобы разбираться в чужом (или своём полугодовой давности) коде впервые.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверIdempotency check: зачем гонять плейбук, если менять нечего
Решение — регулярный прогон плейбука даже когда вносить нечего. Это называют idempotency check, холостым прогоном: запускаете плейбук на реальном или максимально похожем тестовом хосте и смотрите на итоговую сводку. Смысл идемпотентности подробно разобран в статье про идемпотентность на пальцах — применительно к Ansible она означает, что повторный запуск на уже настроенном хосте не должен ничего ломать и, в идеале, ничего менять.
Ansible в конце прогона выводит PLAY RECAP:
PLAY RECAP *********************************************************
web01.example.com : ok=24 changed=0 unreachable=0 failed=0 skipped=3
Три числа говорят о разном:
changed=0— идеальный результат холостого прогона: состояние сервера совпадает с описанным в коде, плейбук всё ещё точно его описывает.changed=Nбез ваших изменений в коде — сигнал config drift: что-то на сервере отличается от описанного, и Ansible это молча «починил». Стоит остановиться и разобраться, что изменилось и почему, а не просто порадоваться автоматическому исправлению.failed=N— плейбук больше не работает. Это число вы не хотите увидеть впервые в момент реальной аварии.
Периодичность зависит от критичности инфраструктуры, но раз в квартал — разумный минимум для небольшой команды, а для продакшн-критичных ролей лучше раз в месяц или при каждом обновлении базового образа.
Как встроить регулярный прогон в рабочий процесс
Держать в голове «не забыть прогнать плейбук» не работает — забудут. Нужен процесс, не зависящий от памяти конкретного человека.
Cron-задача с уведомлением. Простейший вариант — запускать плейбук в режиме --check (dry-run) по расписанию и слать результат в канал команды:
0 6 1 * * ansible-playbook -i inventory/prod site.yml --check --diff | mail -s "Ansible check: prod" ops@example.com
--check не применяет изменения, а только показывает, что было бы изменено — это безопасно гонять на проде. --diff покажет конкретные строки, которые отличаются, что упрощает разбор причины.
CI-раннер по расписанию. Более зрелый вариант — вынести прогон в CI (GitLab CI, GitHub Actions, Jenkins) по cron-триггеру, с результатом в виде артефакта и уведомлением при failed > 0 или неожиданном changed > 0. Это снимает зависимость от конкретной машины оператора и даёт историю прогонов, а не разовое письмо, которое никто не прочитал.
Привязка к репетиции восстановления. Если в команде уже есть практика регулярных учений по восстановлению из бэкапа (см. готовый сценарий учений на час), логично объединить её с проверкой плейбуков: разворачивать тестовый сервер именно через IaC-код, а не вручную из образа. Одним действием проверяются и бэкапы, и актуальность автоматизации.
Важный нюанс: --check работает не идеально для всех модулей — задачи, зависящие от результата предыдущей команды через register, или использующие command/shell без явной проверки идемпотентности, в dry-run могут вести себя иначе, чем при реальном применении. Холостой прогон в --check — быстрый первый рубеж, но не полная замена периодическому реальному прогону на тестовом окружении.
Тестовое окружение как страховка перед прогоном на проде
Гонять плейбук вслепую на боевом сервере — риск сам по себе: если задача написана неидемпотентно, --check может этого не показать, а реальный прогон сломает то, что работало. Поэтому регулярную проверку правильно устраивать на отдельном тестовом хосте, максимально похожем на прод по ОС и версиям пакетов.
Практичная схема для небольшой команды:
- Держать один недорогой VPS с той же версией дистрибутива, что и прод, специально под тестовые прогоны IaC-кода.
- Раз в цикл (месяц/квартал) пересоздавать этот сервер с нуля и накатывать плейбук — это проверяет не только идемпотентность, но и сам сценарий «поднять с нуля», который и есть цель плейбука восстановления.
- Только после чистого прогона на тестовом хосте (
changed, соответствующий ожиданиям,failed=0) — по желанию гонять--checkна проде для сверки.
| Окружение | Что проверяет | Периодичность | Риск при ошибке |
|---|---|---|---|
Прод, --check | Расхождение факта и кода (drift) | Ежемесячно | Низкий (dry-run) |
| Тестовый VPS, прогон с нуля | Идемпотентность + сценарий восстановления | Раз в квартал | Нет (не прод) |
| Прод, реальный прогон | Финальная синхронизация после разбора drift | По необходимости, осознанно | Средний — только после теста |
Если у вас пока нет структурированного подхода к IaC вообще, стоит начать с базового разбора — Infrastructure as Code: с чего начать.
Версионирование зависимостей плейбука
Отдельная категория проблем — не сам плейбук, а то, что он тянет извне. Если задачи ссылаются на пакеты «последней доступной версии» или скачивают архивы по прямому URL без фиксации версии, плейбук через полгода-год может начать ставить совсем не то, что ставил при написании — с другим поведением и другими путями к файлам.
Практические меры:
- Явно фиксировать версии пакетов там, где это возможно, а не полагаться на «последнюю доступную».
- Для внешних артефактов — держать локальное зеркало или хотя бы контрольную сумму, чтобы плейбук падал с понятной ошибкой «сумма не совпала», а не тихо ставил другую версию.
- Хранить используемые Ansible-коллекции в
requirements.ymlс зафиксированными версиями и накатывать их черезansible-galaxy collection install -r requirements.yml, а не полагаться на то, что стоит на машине оператора. - Явно проверять инвентарь перед прогоном на группу хостов — ошибка с неверной группой обычно дороже, чем устаревший плейбук.
Ничего из этого не гарантирует, что через год плейбук отработает без единой правки — гарантий тут не бывает. Но фиксация версий переводит будущую поломку из «непонятная ошибка в середине аварии» в «понятное сообщение о несовпадении версии», которое чинится за минуты, а не часы.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Как часто на самом деле нужно гонять плейбук, если ничего не меняется?
Универсального числа нет — это ориентир, и у вас может быть иначе в зависимости от скорости изменения окружения. Разумная отправная точка для небольшой команды — раз в месяц --check на проде и раз в квартал полный прогон с нуля на тестовом хосте.
Что делать, если прогон показал changed > 0, а вносить в код мы ничего не собирались?
Это и есть обнаруженный config drift. Не спешите просто «дать плейбуку исправить» — сначала разберитесь через --diff, что именно изменилось на сервере и кто это сделал. Иногда выясняется, что дрейф — осознанное и нужное изменение, которое просто забыли занести обратно в плейбук.
Плейбук писали не мы, и разбираться в нём страшно — с чего начать ревизию?
Начните с холостого прогона в --check --diff на тестовом хосте, максимально похожем на прод. Сводка сразу покажет, какие задачи падают и какие расхождения накопились, и даст конкретный список вместо абстрактного страха перед чужим кодом.
Можно ли просто переписать плейбук с нуля, раз он всё равно устарел?
Иногда это быстрее, чем чинить старый, особенно если накопилось много условной логики под давно закрытые кейсы. Но переписывание без понимания, что старый плейбук делал правильно, рискует потерять неочевидные, но нужные шаги — лучше сначала прогнать старый и зафиксировать его текущее реальное поведение, а потом решать, чинить или переписывать.
Нужно ли версионировать сам плейбук в git, если он и так лежит в репозитории?
Лежать в git — не то же самое, что быть проверяемым. Версионирование фиксирует историю изменений кода, но не отвечает на вопрос, работает ли этот код на реальном сервере прямо сейчас. Это две независимые практики, и обе нужны.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →