Ansible Galaxy и роли: рефакторим монолитный playbook на части
Разбили монолитный playbook на роли, подключили Ansible Galaxy для nginx, postgresql, ntp. Что реально экономит время, а где всё равно пишешь сам.
Ansible Galaxy растёт как хаб переиспользуемых ролей; сообщество публикует роли для типовых сервисов
Пару недель назад писали про базовый playbook для провизионирования серверов. Финальный абзац там звучал как план: разбить монолит на роли. Вот что из этого вышло.
Повод появился сразу - у нас три клиента на сопровождении, и каждый требует немного разного набора сервисов. Общее есть у всех: nginx как фронтенд, PostgreSQL под базы, NTP для синхронизации времени. Копировать 60 строк yaml из одного репозитория в другой и потом следить за расхождениями - это не автоматизация, это имитация.
Что такое роль в Ansible и зачем городить
Роль - это просто соглашение о структуре директорий. Ansible ожидает конкретные папки: tasks/, handlers/, templates/, vars/, defaults/, files/. Если папки нет - ничего страшного, просто игнорируется. Никакой магии, просто порядок.
Главное что это даёт: роль становится самодостаточным блоком. Хочешь nginx - подключил роль nginx, и туда потянулись tasks, handlers и templates. Не надо держать в голове что шаблон конфига лежит вот там, а handler на reload - в другом месте playbook.
Структура проекта теперь выглядит так:
ansible/
site.yml
inventory/
group_vars/
roles/
common/
nginx/
postgresql/
ntp/
site.yml превратился из 180-строчного монстра в 30 строк, где основное - это список ролей для каждой группы хостов. Читать стало в разы проще.
Ansible Galaxy: что реально взяли, что написали сами
Ansible Galaxy существует уже полтора года, но мы присматривались осторожно. Чужой код в продакшене - это зависимость, за которой надо следить.
Попробовали ansible-galaxy install для трёх задач.
NTP. Взяли роль с Galaxy - и она заработала. Поддерживает CentOS и Debian, настраивает серверы через переменную ntp_servers, идемпотентна. Ничего лишнего. Именно такой кандидат, где брать готовое разумно: логика простая, вариаций немного, тест тривиальный.
PostgreSQL. Попробовали несколько популярных ролей - ни одна не подошла без правок. Одна тянет PGDG-репозиторий, но версию прибивает гвоздями. Другая настраивает pg_hba.conf так, что наши клиентские конфигурации надо было либо переписывать под её переменные, либо после неё патчить. В итоге написали свою роль на 90 строк: ставим из репозитория, генерируем postgresql.conf и pg_hba.conf из Jinja2-шаблонов с нашими переменными, запускаем сервис. Контроль над результатом стопроцентный.
Nginx. Похожая история. Публичных ролей много, но большинство написаны под специфичный способ организации виртуальных хостов - либо один сайт на хост, либо структура с sites-available как в Ubuntu. У нас конфиги организованы иначе, под наш шаблон. Тоже написали свою.
Итог по Galaxy: одна из трёх задач закрылась готовой ролью. Остальное проще было написать, чем адаптировать. Это не значит что Galaxy бесполезен - просто надо понимать что берёшь.
Где переиспользование реально заработало
Роль common - самое ценное что получилось. Там собрано то, что нужно любому серверу: базовые пакеты, пользователи и ключи, SSH-конфиг, Zabbix-агент, timezone. Раньше это было размазано по базовому playbook. Теперь - одна роль, которая подключается везде.
Когда в прошлую неделю надо было поднять сервер для нового клиента - взяли site.yml, прописали хост в инвентарь, указали нужные роли, запустили. common отработала без изменений. Настраивать пришлось только специфику: какая версия PostgreSQL, какие виртуальные хосты nginx, порты приложения.
Вот это и есть переиспользование в деле - не абстрактная "переносимость", а конкретный новый сервер без копипасты.
Про структуру переменных
Одна неочевидность, на которой споткнулись: разница между vars/ и defaults/ в роли. defaults/ - это значения по умолчанию, которые переопределяются из group_vars или host_vars. vars/ - более жёсткие значения, они перекрывают внешние переменные. Если положить всё в vars/ - получишь роль, которую нельзя нормально параметризовать снаружи. Мы так и сделали поначалу, и потом удивлялись почему group_vars игнорируются.
Правило простое: всё что должно быть конфигурируемым - в defaults/, константы роли - в vars/.
Где пока шероховато
Зависимости между ролями через meta/main.yml работают, но надо аккуратно. Если роль postgresql объявляет зависимость от common - при повторном запуске common выполнится дважды. Обычно это безвредно (идемпотентность), но handlers могут сработать лишний раз. Пока решаем дисциплиной в playbook, а не через meta/.
Версионирование ролей из Galaxy тоже не идеально. ansible-galaxy install без явной версии берёт последний коммит. Для requirements.yml есть поле version, мы его указываем - иначе обновление Galaxy в CI может сломать то, что работало.
В целом рефакторинг занял примерно три вечера. Монолит превратился в набор ролей, новые серверы разворачиваются из переиспользуемых кубиков. Galaxy пригодился для мелкой рутины - там где логика типовая. Для специфичных вещей проще написать самому: знаешь что внутри, можешь поправить без боязни что следующий ansible-galaxy install принесёт сюрприз.