Terraform + Ansible: как мы формализовали IaC-процесс и зачем это вообще нужно
Terraform создаёт VM и сеть, Ansible конфигурирует ОС и приложения, GitLab CI запускает всё при merge в master. Описываем структуру репо и review-процесс.
Terraform + Ansible как устойчивая связка для provisioning и configuration management - формализация IaC-процесса с GitLab CI пайплайном
Когда инфраструктура небольшая, можно держать всё в голове. Когда она растёт - начинается "а кто добавил это правило в файрволл", "почему на стейджинге nginx 1.10, а на проде 1.12", "кто руками поставил пакет и не занёс в плейбук". Мы через это прошли, и к середине этого года у нас наконец сложился процесс, который не стыдно назвать IaC без кавычек.
Разделение ответственности: Terraform делает инфраструктуру, Ansible делает систему
Ключевое решение, которое сняло большую часть вопросов - чёткая граница между двумя инструментами.
Terraform - это всё что связано с инфраструктурным уровнем: создать виртуальную машину, настроить сеть, создать диск, назначить IP, прописать группу безопасности. Terraform знает о существовании ресурсов в облаке или гипервизоре и управляет ими через API. После terraform apply у нас есть работающая VM с сетевыми настройками, но на ней только базовый образ ОС.
Ansible берёт эту VM и делает из неё то что нужно: ставит пакеты, генерирует конфиги из шаблонов, заводит пользователей, настраивает сервисы. Ansible не знает как создать виртуальный диск в vSphere - это не его дело. Он знает как настроить nginx и положить правильный nginx.conf.
Граница проходит примерно там, где заканчивается API провайдера и начинается SSH. Всё что делается до первого SSH - Terraform. Всё что после - Ansible.
Это разделение казалось очевидным когда его сформулировали, но до этого у нас был период когда часть провижнинга делалась через provisioner "remote-exec" прямо в Terraform-конфиге, а часть через Ansible. Получалось два места где живёт конфигурация ОС, и синхронизировать их вручную - развлечение на любителя.
Структура репозитория
Репо у нас называется infra, и в нём два верхнеуровневых раздела:
infra/
terraform/
modules/
vm-base/
network/
security-groups/
environments/
prod/
compute/
main.tf
variables.tf
outputs.tf
network/
main.tf
staging/
compute/
main.tf
network/
main.tf
ansible/
roles/
common/
nginx/
postgres/
app-backend/
inventory/
prod.yml
staging.yml
playbooks/
site.yml
web.yml
db.yml
group_vars/
all.yml
prod.yml
staging.yml
Terraform-инвентарь и Ansible-инвентарь пересекаются по именам хостов: Terraform создаёт VM с определёнными тегами или именами, Ansible-инвентарь описывает те же хосты. Синхронизация пока руками - это неидеально, но динамический инвентарь из Terraform state мы попробовали и пока отложили: слишком много переменных при работе с несколькими окружениями одновременно.
GitLab CI: что запускается и когда
Пайплайн срабатывает при push в любую ветку и при merge в master.
При создании MR запускаются:
terraform plan- показывает diff к инфраструктуре, сохраняется как артефактansible-lint+yamllint- проверка синтаксиса и стиля ролейmolecule testдля ролей которые изменились в этом MR
При merge в master запускаются:
terraform applyс сохранённым plan-файлом из MR (только если plan был)- Ansible
--check- ещё одна проверка без реального применения - Ручной триггер на
ansible-playbook- автоматически не запускается
Последний пункт - осознанный выбор. Автоматический деплой конфигурации при merge в master звучит красиво, но требует уровня доверия к тестам которого у нас пока нет. molecule покрывает роли в изоляции, но не гарантирует что комбинация из пяти ролей на реальном хосте не даст сюрприз. Поэтому Ansible-часть запускается вручную после мержа, с артефактами из CI и глазами инженера на экране.
Terraform apply при мерже - автоматический, потому что там есть plan.bin который был проревьюен, и apply применяет строго его. Инфраструктурные изменения как правило атомарнее конфигурационных.
Review-процесс
MR в infra проходит через два типа проверки.
Автоматика: CI запускает plan и lint, прикрепляет вывод terraform plan в комментарий к MR через GitLab API. Это означает что ревьюер видит точный diff инфраструктуры прямо в интерфейсе, не запуская ничего локально.
Ревью коллеги: обязательно для любого изменения в environments/prod/ или в ролях которые используются на проде. Для стейджинга - рекомендательно, но на практике тоже делается, просто быстрее.
Отдельно договорились про несколько правил. Первое - никаких изменений в prod через консоль или SSH напрямую: если что-то не прошло через MR, оно не должно появляться на проде. Это дисциплина, не техническое ограничение, и она ломается под давлением дедлайнов - знаем по опыту. Второе - хотфиксы тоже через MR, просто маленький и быстрый.
Что реально работает, что скрипит
Хорошо: история изменений инфраструктуры в Git - это отдельная ценность. Когда что-то сломалось в 3 ночи, git log --oneline за последние сутки часто сразу указывает на виновный коммит.
Хорошо: terraform plan в MR как живой документ изменений. Никаких "я добавил правило в файрволл" - вот коммит, вот plan, вот кто апрувил.
Скрипит: синхронизация инвентаря между Terraform и Ansible. IP-адрес новой VM появляется в Terraform outputs, но в Ansible-инвентарь его нужно добавить руками. Это место где можно ошибиться и потом долго искать почему playbook не находит хост.
Скрипит: тесты для Ansible покрывают роли в изоляции, но интеграционных тестов на уровне "полный стек на реальном окружении" нет. Это осознанный пробел который давит на совесть.
Процесс не идеальный, но работает. Сопровождение инфраструктуры с таким подходом становится понятнее: новый инженер видит в Git полную историю, а не легенды о том почему на одном сервере стоит нестандартная версия пакета.
Следующий шаг который обсуждаем - динамический инвентарь из Terraform state и автоматическая передача IP-адресов из outputs в Ansible без ручного шага. Это уберёт главное место где процесс зависит от человека с руками.
- Terraform 0.9: единый remote backend для всей команды · 6 апреля 2017
- Ansible 2.2: переписываем библиотеку ролей под стабильный include_role · 17 января 2017