В прошлой статье я рассказывал, как развернул две системы мониторинга одного парка — Zabbix с самописным дашбордом и Prometheus + Grafana. Обе работают, у обеих публичные демо. Но оставался неприятный вопрос: а что, если контейнер с мониторингом умрёт? Восстанавливать вручную — это часы работы и вспоминания «а как я тогда настраивал». Настоящий инженерный ответ один: инфраструктура должна описываться кодом.
За два дня я перевёл весь парк на Ansible. Не «написал пару плейбуков», а довёл до состояния, где любой сервис пересоздаётся из репозитория за минуты, изменения проверяются в CI, а на боевом ничего не ломается. Расскажу, как это устроено и на какие грабли наступил.
Гетерогенная среда на Proxmox: несколько LXC-контейнеров (мониторинг, CI-узел) и виртуальные машины, плюс облачный сервер. Управляет всем отдельный control-node — контейнер, на котором стоит Ansible (поставил через pipx, чтобы иметь свежую версию в изолированном окружении, а не то, что лежит в репозиториях дистрибутива).
Хосты описаны в inventory группами по назначению: стек Prometheus, гипервизоры, виртуалки, узлы с node_exporter, CI-узел. Ansible ходит к ним по SSH-ключу — где напрямую по локальной сети, где через VPN-туннель.
Каждая роль отвечает за один компонент и воспроизводит его состояние точно:
Роль, которая при повторном запуске что-то меняет каждый раз, — плохая роль. Правильная роль на втором прогоне сообщает «изменений: 0». Это значит, что описанное в коде состояние уже достигнуто, и Ansible ничего не трогает зря — не перезапускает сервисы, не дёргает конфиги. Все девять ролей идемпотентны: второй прогон — ноль изменений на всех хостах. Проверял каждую.
Красивые слова про «инфраструктуру как код» ничего не стоят, пока не проверишь их на практике. Я провёл контролируемый эксперимент на наименее критичном хосте: удалил node_exporter полностью — бинарник, systemd-юнит, пользователя. В Prometheus сразу погас соответствующий таргет.
Потом запустил роль. Через ~8 секунд сервис вернулся: пользователь создан, бинарник скачан и установлен, юнит на месте, сервис запущен, таргет в Prometheus снова «up». Это и есть страховка — не теоретическая, а проверенная вживую.
Тонкий момент, из-за которого IaC часто превращается в кашу: две роли лезут в один файл. У меня blackbox-мониторинг сайтов должен был добавить свою scrape-задачу в конфиг Prometheus — а этим конфигом уже управляет роль prometheus, байт в байт.
Решение — не костыль, а правильная развязка. Prometheus умеет подключать scrape-конфигурации из отдельных файлов (директива scrape_config_files). Я добавил в роль prometheus генерическую директиву: подхватывай всё из отдельного каталога. Теперь любой будущий экспортёр кладёт туда свой фрагмент, не трогая основной конфиг. Роль blackbox владеет своим фрагментом целиком, роль prometheus — своим конфигом. Один файл — один ответственный. Никаких конфликтов.
Тот же принцип с дашбордами Grafana: у blackbox свой provider дашбордов, отдельный от основного. Роли не наступают друг другу на ноги.
Мониторинг — это боевые сервисы с публичными демо. Ронять их нельзя. Поэтому каждое изменение шло по одному сценарию:
Ни одно демо за два дня не мигнуло.
Репозиторий с ролями лежит в собственном Gitea. На каждый push срабатывает CI (Gitea Actions): линтер ansible-lint в режиме production — самом строгом. Красный линт — код не проходит. Это не даёт накопиться плохим практикам и держит роли в форме.
В свежем ansible-core убрали один параметр вывода — старый плейбук ругался. Поправил под актуальную версию. Принцип: проверять поведение конкретной версии, а не полагаться на память.
Скрипт обращался к сервису от имени одного пользователя, а реальный системный пользователь оказался другим. Поймал фактической проверкой до боевого прогона — иначе бэкап молча создавал бы пустышку. Урок: непроверенный бэкап — это не бэкап.
При предпросмотре роли, которая ставит пакет и сразу управляет сервисом, Ansible ругается: сервиса ещё нет (пакет в режиме проверки не ставится). Это не ошибка — в реальном прогоне пакет ставится первым, сервис появляется. Понял механику, не стал «чинить» несуществующую проблему.
Отдельно скажу про то, что я сознательно не стал переводить в Ansible: боевой облачный сервер — единую точку входа во всю инфраструктуру. Риск ошибки там несоизмерим с выгодой: одна опечатка в конфигурации сети или SSH — и я теряю доступ ко всему разом. Его данные и так защищены отдельной системой бэкапа. Автоматизировать ради галочки — плохая инженерия. Зрелость в том, чтобы взвесить риск и выгоду, а не закодировать всё подряд.
Весь парк — установка, конфигурация, мониторинг, алертинг, защита SSH, бэкапы — теперь описан кодом. Девять идемпотентных ролей, проверяемых в CI и версионируемых в git. Любой компонент пересоздаётся из репозитория за минуты, а не за часы ручной работы. И это проверено вживую, а не только на бумаге.
Инфраструктура как код — это не про моду на инструмент. Это про то, чтобы спать спокойно, зная: если что-то отвалится, я подниму это одной командой.
Технологии: Ansible, ansible-lint, Gitea Actions (CI/CD), Prometheus, Grafana, Zabbix, blackbox_exporter, node_exporter, fail2ban, WireGuard, Proxmox.