Ansible-репозиторий: Galaxy для hardening, собственные роли для бизнес-логики, порядок в layout
Ansible Galaxy набирает обороты как хаб переиспользуемых ролей. Показываем как мы делим репозиторий: роли из Galaxy для базового hardening, свои для бизнес-логики, плюс layout roles/group_vars/inventory.
Ansible Galaxy набирает популярность как хаб переиспользуемых ролей для автоматизации инфраструктуры
Ansible Galaxy существовал давно, но последние несколько месяцев ощущается реальный прирост качественных ролей - особенно в сегменте hardening и базовых сервисов. Мы несколько раз наступали на одни и те же грабли, когда каждый проект имел свою «самодельную» роль для SSH-конфига и auditd-правил. Потратили пару итераций и пришли к структуре, которую теперь тиражируем на новые проекты сопровождения.
Откуда вопрос вообще
Изначально всё начиналось как monorepo: один репозиторий, внутри - горка playbook-ов с задачами прямо в play, никаких ролей. Работало. Пока проектов было два-три.
Когда стало больше - начался классический хаос: одна команда исправила параметр sysctl в своём playbook, другая - нет, клиенты на одинаковой инфраструктуре оказались с разными конфигурациями. Тогда разнесли по ролям, но каждый проект тащил роли в себя, дублирование никуда не делось.
Сейчас подход другой.
Два типа ролей
Мы делим роли на два чётко разных класса:
Базовый hardening и общие сервисы - это кандидаты для Galaxy. SSH hardening, базовая настройка auditd, sysctl-параметры безопасности, fail2ban, нормальный NTP-клиент. Такие вещи не содержат никакой бизнес-логики, одинаковы для всех клиентов, и сообщество их пишет и проверяет неплохо. Роль geerlingguy.security или hardening.os-hardening от hardening.io - добротные вещи, которые поддерживаются людьми, которые думают об этом больше нас.
Бизнес-логика и клиентская специфика - только свои роли. Деплой приложения, настройка PostgreSQL под конкретную схему, интеграция с мониторингом клиента, его специфические политики паролей и ротации ключей. Это в Galaxy не отдашь - да и незачем.
Граница между классами поначалу кажется размытой, но на практике она проявляется быстро: если роль нужно параметризовать через client_name или project_id - это своя. Если нет - смотри Galaxy.
Layout репозитория
Вот как выглядит стандартный layout, который мы теперь воспроизводим:
ansible-infra/
inventory/
prod/
hosts
group_vars/
all.yml
webservers.yml
staging/
hosts
group_vars/
all.yml
webservers.yml
dev/
hosts
group_vars/
all.yml
group_vars/ # переменные, общие для всех окружений
all.yml
roles/
galaxy/ # Galaxy-роли через requirements.yml
internal/ # собственные роли
deploy-app/
postgres-config/
monitoring-agent/
requirements.yml # список Galaxy-зависимостей
site.yml
deploy.yml
Ключевое решение здесь - inventory/prod, inventory/staging, inventory/dev как отдельные директории, а не один hosts файл с группами. У каждого окружения свой hosts и свои group_vars. Это снимает целый класс ошибок, когда переменная из prod просачивается в staging потому что кто-то не так написал [webservers:vars].
group_vars/all.yml на верхнем уровне - это переменные, действительно общие для всего: имя проекта, контакты для алертов, версия приложения по умолчанию. Окружение-специфичные переменные живут только внутри своего inventory/.
Galaxy через requirements.yml
Galaxy-роли не коммитим в репозиторий. Только requirements.yml:
- name: hardening.os-hardening
version: "3.2.0"
- name: geerlingguy.ntp
version: "1.6.4"
- name: geerlingguy.security
version: "1.6.1"
Версии фиксируем явно - без них ansible-galaxy install при следующем запуске может притащить что-то несовместимое. Установка - отдельный шаг в CI перед запуском playbook-а:
ansible-galaxy install -r requirements.yml -p roles/galaxy/
Директория roles/galaxy/ в .gitignore. Чистый принцип: зависимости не версионируем, версионируем манифест зависимостей.
Как это работает в playbook-е
Типичный site.yml выглядит примерно так:
- name: базовый hardening всех серверов
hosts: all
become: yes
roles:
- hardening.os-hardening
- geerlingguy.ntp
- geerlingguy.security
- name: настройка веб-серверов
hosts: webservers
become: yes
roles:
- internal/nginx-config
- internal/deploy-app
- name: настройка БД
hosts: dbservers
become: yes
roles:
- internal/postgres-config
Galaxy-роли идут первыми - они создают базовый уровень безопасности. Внутренние роли строятся поверх него и могут предполагать, что базовые вещи уже настроены.
Что пришлось поправить
Не всё прошло гладко. Несколько наблюдений:
Galaxy-роли часто имеют свои опinionated умолчания, которые конфликтуют с нашей настройкой. hardening.os-hardening по умолчанию довольно агрессивен с sysctl - например, отключает IPv6. Нескольким клиентам он нужен. Решается через переменные роли, но нужно читать документацию роли внимательно и тестировать на staging.
Версии Galaxy-ролей и Ansible 2.0 иногда дают неожиданные комбинации. Одна из ролей использовала deprecated-синтаксис sudo:, который в Ansible 2.0 генерирует warnings. Не критично, но засоряет вывод. Либо искать роль с поддержкой Ansible 2.x, либо форкать и чинить.
Шифрование переменных через Vault хорошо ложится на эту структуру: inventory/prod/group_vars/all.yml может содержать vault_-переменные, зашифрованные отдельным vault.yml. Мы про это уже писали, и здесь всё работает так же.
Где сейчас
Структура обкатана на нескольких проектах, и основной выигрыш - не в том, что роли переиспользуются сами по себе. Главное: когда новый человек смотрит на репозиторий незнакомого проекта, он сразу видит что откуда берётся. Galaxy - чужое, internal/ - наше, inventory/prod - прод, inventory/dev - дев. Никаких расследований.
Galaxy как хаб продолжает расти, и качество ролей неравномерное - есть очень добротные вещи, есть написанные на коленке. Мы ориентируемся на роли с нормальным CI, внятным README и активными коммитами. Автор неважен - важно, что роль проверяется и поддерживается.