Git для инфраструктуры: ветвление по окружениям вместо папки «backup старый»
Перевели конфиги, playbook-и и скрипты в Git с ветками dev/stage/prod. Pull request на инфраструктуру - непривычно, но инциденты идут на убыль.
Infrastructure as Code через Git становится практикой: конфиги, playbooks и скрипты хранятся в version control с полноценным workflow
В марте мы подключили Jenkins к Ansible-репозиторию - каждый push запускает синтаксис-чек и smoke-тест. С тех пор один вопрос не давал покоя: а что у нас вообще лежит в репозитории? Только Ansible-роли, или вся инфраструктурная конфигурация?
Оказалось - не вся. Nginx-конфиги на серверах клиентов правились вручную и «синхронизировались» копированием. Файрвольные правила жили в вики с пометкой «актуально на апрель». Crontab на продовом сервере отличался от стейджа тремя строками - никто не помнил почему. Классика.
Решили зайти системно: всё, что описывает инфраструктуру, должно лежать в Git. И раз уж в Git - с нормальным ветвлением под окружения.
Что именно переезжало
Список получился длиннее, чем казалось поначалу.
- Ansible playbook-и и роли. Уже были в репозитории, но в одной ветке и без разделения по окружениям.
- Конфиги сервисов. Nginx, PostgreSQL (
postgresql.conf,pg_hba.conf), Zabbix-агент. Раньше хранились непосредственно на серверах и «бэкапировались» папкой_old. - Скрипты резервного копирования и мониторинга. Bash-скрипты, которые когда-то написали, положили на сервер и забыли где оригинал.
- Firewall-правила. У нас iptables через конфиг-файл - вполне себе текст, место в Git.
- Документация окружений. Не в смысле Word-файлов, а
hosts-файлы инвентаря Ansible с описанием ролей хостов.
Что не переезжало: секреты (пароли, ключи) - они и раньше в репозиторий не попадали, Ansible Vault для этого.
Структура веток
Модель выбрали простую - три долгоживущие ветки, соответствующие окружениям.
main (prod)
└── stage
└── dev
Изменения идут снизу вверх: dev -> stage -> prod. Применяем через pull request - сначала между dev и stage, потом между stage и prod. Звучит тяжеловесно, на практике для большинства изменений это занимает 20 минут.
Логика такая: в dev свободно экспериментируем. В stage - только то, что проверили на тестовом стенде. В main - только то, что прошло через stage и не вызвало вопросов.
Jenkins смотрит на все три ветки. Push в dev - запускается синтаксис-чек. Push в stage - полный smoke-тест на тестовых VM. Push в main - smoke-тест плюс уведомление дежурному инженеру, что в prod идут изменения.
Pull request на изменение инфраструктуры
Вот это было самым непривычным. Инженеры привыкли что «поправить конфиг» - это зайти на сервер и поправить. Pull request для этого казался бюрократией.
Первые две недели было именно так: люди открывали PR, сами же его и мержили через минуту. Смысл нулевой.
Переломный момент - один PR, в котором кто-то менял worker_processes в nginx с auto на конкретное число. Коллега при ревью спросил: «а ты учёл что на стейдже 2 ядра, а на проде 8?» Нет, не учёл. Конфиг оказался не универсальным, а жёстко заточенным под конкретное железо.
После этого случая PR-ревью стало реальным. Не формальным «посмотрел, ок», а конкретным «на каком основании именно это значение?». Несколько раз это остановило изменения, которые были бы проблемой в prod.
Drift между окружениями: главный враг
Основная проблема, которую хотели решить - конфигурационный дрейф. Это когда stage и prod когда-то были одинаковыми, а потом начали расходиться по мелочам: кто-то поправил таймаут здесь, добавил строку там, и через три месяца stage уже плохо предсказывает поведение prod.
Git с ветвлением решает это частично. Теперь разница между окружениями видна через git diff dev..stage - это не вся картина, но уже что-то. Если изменение применено в prod, но не зафиксировано в main - это видно при следующем деплое: Ansible покажет changed там, где не должно быть.
Стопроцентной защиты нет: никто не мешает зайти на сервер и поправить руками, минуя Git. Но это теперь явное нарушение договорённости, а не норма.
Что было неожиданно трудным
Первоначальный импорт. Взять конфиги с живых серверов и сделать из них «золотой» репозиторий оказалось нетривиально. Серверы расходились между собой - даже там где должны были быть одинаковыми. Пришлось разбираться: это намеренные различия или дрейф? На это ушло несколько дней.
Переменные вместо жёстких значений. Когда конфиг один на три окружения, всё специфичное для окружения должно быть в переменных. Это правильно, но требует рефакторинга конфигов, которые раньше писались под конкретный сервер. Nginx-конфиг с жёстко прописанными IP-адресами бэкендов - это не шаблон, это документ.
Объяснить зачем. «Зачем открывать PR чтобы поменять одну строку в cron?» - вопрос, который задавали не один раз. Ответ простой: чтобы через полгода был ответ на вопрос «кто и зачем это поменял». Git log как аудит-лог - это реально работает.
Текущее состояние
Репозиторий сопровождения работает три месяца по новой схеме. Ручных правок конфигов на серверах стало заметно меньше - не ноль, но меньше. Последний инцидент с «а почему на prod не так как на stage» случился в апреле - и именно он ускорил внедрение этой схемы.
Ansible при каждом деплое теперь показывает реальный drift: если что-то на сервере расходится с репозиторием, будет changed там где должен быть ok. Это неприятный сюрприз, но лучше узнать об этом при плановом деплое, чем при инциденте.
Главный вывод пока такой: pull request на инфраструктуру - это не про процесс ради процесса. Это про то, что второй человек смотрит на изменение до того как оно ушло в prod. Иногда второй человек видит то, что первый пропустил. Этого достаточно.