Управление конфигурацией ПО и баз данных держится на одном правиле: параметры работы приложения и его данные хранятся отдельно. Конфигурация (переменные окружения, строки подключения, флаги, учётные данные) версионируется как код, а содержимое базы меняется только через контролируемые миграции, у которых есть понятный путь назад.
Рабочий минимум выглядит так. Пароли и токены уходят в защищённое хранилище и не попадают в Git. Конфигурационные файлы лежат в репозитории и рендерятся из шаблонов под каждую среду. Схема БД описывается миграциями с прямым и обратным действием, а приложение во время обновления работает и со старой, и с новой версией схемы. Это даёт воспроизводимость окружений, аудит изменений и предсказуемый откат релиза.
Ниже: границы между конфигурацией и данными, выбор инструментов миграций, работа с секретами, шаблонизация конфигов, откат миграций и проверка совместимости версий.
Почему конфигурация и данные должны быть разделены
Это разделение задаёт границу между тем, что версионируется, и тем, что защищается резервными копиями. Нарушение границы приводит к утечкам, невоспроизводимым окружениям и опасным откатам.
Что относится к конфигурации, а что к данным
Конфигурация описывает, как приложение работает. В неё входят:
- переменные окружения: адрес БД, порт, уровень логирования, лимиты памяти;
- строки подключения к внешним сервисам: брокеру сообщений, кэшу, S3-совместимому хранилищу;
- учётные данные и токены доступа;
- флаги функций и параметры экспериментов;
- настройки логирования, метрик и трассировки;
- параметры пулов соединений и таймаутов.
Данные описывают, с чем приложение работает: строки в таблицах, файлы, загруженные пользователями, состояние сессий, содержимое очередей. Из репозитория данные не восстанавливаются, поэтому живут они в резервных копиях и репликах.
Схема БД стоит отдельно. Это структура: таблицы, столбцы, типы, индексы, ограничения. Её версионируют миграциями, а не копированием строк, и инструменты для неё свои: Flyway, Liquibase, Alembic, встроенные миграции Django.
Конфигурация хранится в окружении, а не в коде. Так формулирует правило методология 12-factor app.
Практический пример для приложения на Python: настройки подключения лежат в файле .env, который внесён в .gitignore, а в репозитории остаётся .env.example с перечнем переменных без значений. Данные при этом находятся в PostgreSQL, а структура таблиц описана файлами миграций.
Последствия смешивания конфигурации и данных
- Утечка секретов. Токен, закоммиченный в репозиторий, остаётся в истории коммитов даже после удаления файла. Простого удаления мало, нужна ротация ключа и разбор журналов доступа.
- Невоспроизводимость окружений. Когда параметры зашиты в код или в дампы базы, тестовый стенд не собрать из репозитория, а расхождения между dev и prod растут с каждым релизом.
- Путаница при откате. Если миграция схемы и правка данных выполняются одним шагом, откат релиза повреждает данные, которые иначе остались бы нетронутыми.
- Ошибки в CI/CD. Артефакт сборки с продовыми учётными данными уезжает в тестовую среду или попадает в кэш сборщика.
- Дрейф конфигурации. Правки «руками» на сервере расходятся с тем, что описано в репозитории, и следующий деплой их затирает либо ломается сам.
Версионирование схемы базы данных и миграции
Миграция - это файл с описанием одного изменения схемы. Инструмент ведёт таблицу применённых версий (например, flyway_schema_history или alembic_version) и применяет только новые файлы в заданном порядке. Состояние схемы в любой момент выводится из истории миграций, а не из памяти администратора.
Рабочий цикл обычно такой: разработчик создаёт миграцию, CI прогоняет её на чистой базе и на копии продакшен-данных, ревьюер оценивает блокировки и длительность, затем миграция проходит stage и prod. Обратная миграция или подтверждённый план отката идёт в том же коммите, что и прямая.
Инструменты для миграций: сравнение и выбор
| Инструмент | Формат миграций | Откат | Когда подходит |
|---|---|---|---|
| Flyway | SQL и Java | Обратные скрипты пишут вручную, автоматический undo зависит от редакции | JVM-стек и проекты, где миграции ведут на чистом SQL |
| Liquibase | XML, YAML, JSON, SQL changelog | Правила rollback описывают в changelog | Команды с разными стеками, нужен единый журнал изменений |
| Alembic | Python, SQLAlchemy | Функция downgrade() в каждой ревизии | Python-проекты на SQLAlchemy |
| Django migrations | Python, Django ORM | Часть операций получает обратное действие автоматически, сложные изменения описывают вручную | Проекты на Django |
Ориентир по выбору: для Java и Spring Boot берите Flyway или Liquibase, для Python с SQLAlchemy - Alembic, для Django - встроенные миграции. Набор флагов отката и редакции инструментов меняются от версии к версии, поэтому перед настройкой сверяйтесь с документацией именно вашей версии.
Как писать обратимые миграции
У каждой миграции должно быть прямое и обратное действие. Проверяемый пример для добавления столбца:
ALTER TABLE orders ADD COLUMN currency varchar(3); -- обратное действие ALTER TABLE orders DROP COLUMN currency;
Обратимыми обычно выходят создание и удаление индекса, добавление и удаление столбца, переименование таблицы с обратным переименованием, создание таблицы и её удаление.
Опасные операции, где откат теряет данные: удаление столбца, сужение типа (varchar(255) до varchar(50)), смена типа с потерей точности, добавление NOT NULL без значения по умолчанию на большой таблице, удаление таблицы. Перед каждой такой миграцией делайте бэкап и проверяйте, что он восстанавливается.
Транзакции. PostgreSQL, Oracle и большая часть реляционных СУБД поддерживают DDL внутри транзакции, поэтому неудачная миграция откатывается целиком. В MySQL и MariaDB DDL вызывает неявный коммит, и большой ALTER TABLE назад не откатить, остаётся план восстановления. По этой же причине длительные изменения разносят на шаги: ALTER на большой таблице держит блокировку и останавливает запись.
Идемпотентность помогает при повторном прогоне: CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, проверки на существование столбца в SQL. Тестируйте миграцию на копии продакшен-данных: это покажет и длительность, и блокировки, и объём журнала транзакций, который она породит.
Управление секретами: безопасное хранение паролей и токенов
Секреты (пароли БД, ключи API, приватные ключи, токены CI) хранятся вне репозитория и вне образов контейнеров. Проверка простая: если файл с секретом попал в историю Git, секрет считают скомпрометированным и меняют.
Инструменты для управления секретами
| Инструмент | Модель хранения | Что даёт | Ограничения |
|---|---|---|---|
| HashiCorp Vault | Централизованный сервис | Динамические секреты с TTL, аудит доступа, журнал операций, разные движки секретов | Нужно развернуть сервис, хранить unseal-ключи и сопровождать его |
| Kubernetes Secrets | Объекты внутри кластера | Нативная выдача секретов в Pod, монтирование файлом или переменной | Значения по умолчанию не шифруются, нужны шифрование etcd и строгий RBAC |
| SOPS | Зашифрованные файлы в Git | Шифрует значения, оставляя ключи читаемыми, работает с age, GPG и облачными KMS | Управление ключами расшифровки на стороне команды |
| Ansible Vault | Шифрование файлов и переменных | Встроен в Ansible, отдельный сервис не нужен | Ключ расшифровки всё равно нужно где-то хранить |
| AWS Secrets Manager, GCP Secret Manager, Azure Key Vault | Управляемый сервис | Ротация, версии секретов, интеграция с IAM и журналами | Привязка к облаку и оплата за секреты и запросы к ним |
Сценарии выбора: команда без Kubernetes обычно обходится SOPS или Ansible Vault плюс защищённые переменные в CI. Для кластера Kubernetes включают шифрование etcd и ограничивают доступ к Secrets через RBAC. Когда нужны короткоживущие учётные данные для БД с автоматической сменой, подходит Vault с динамическими секретами.
Практики безопасной работы с секретами в CI/CD
- Держите секреты в защищённых переменных: GitHub Environments и Secrets, переменные GitLab CI с флагами Protected и Masked.
- Не печатайте значения в лог. Маскирование работает, но не спасает от base64, разбиения строки по символам и вывода внутри многострочного блока.
- Помните, что pull request из форка не должен получать секреты. В GitHub Actions это поведение по умолчанию, и отключать его ручными настройками не стоит.
- Заменяйте долгоживущие ключи на OIDC-токены: GitHub Actions и GitLab CI умеют получать временные учётные данные в облаке без статического ключа.
- Ротируйте секреты по расписанию и после каждого ухода сотрудника с доступом.
- Ведите аудит обращений к хранилищу и ставьте алерт на всплеск чтения в нерабочие часы.
Конвейер удобно строить так, чтобы секреты подставлялись в момент выполнения задачи, а не хранились в артефакте. Разбор архитектуры пайплайна с этапами сборки, тестов и ручного подтверждения перед prod - в статье автоматизированное развёртывание приложений через CI/CD.
Хранение конфигурационных файлов в репозитории и шаблонизация
Конфигурационные файлы без секретов держат в Git: это даёт историю изменений, ревью и откат. Дублирование конфигов по средам решается шаблонами и переопределениями, а не копиями файлов.
Шаблонизация конфигов для разных сред
- Ansible: один шаблон Jinja2 и разные наборы переменных в group_vars/dev.yml и group_vars/prod.yml.
- Helm: общий values.yaml плюс values-dev.yaml, values-stage.yaml, values-prod.yaml; рендер через helm template в CI для проверки результата.
- kustomize: базовый набор манифестов и overlays под каждую среду без шаблонного синтаксиса.
Сгенерированные конфиги проверяют до деплоя: yamllint для синтаксиса, схема JSON для обязательных полей, helm lint для чартов, kubectl apply с dry-run на стороне сервера. Ошибка в шаблоне, найденная в CI, стоит минуты, та же ошибка на проде стоит простоя.
Подстановка значений и приоритеты
Типичный порядок приоритета: значения по умолчанию в коде, затем файл конфигурации, затем переменные окружения, затем аргументы командной строки. Переменные окружения обычно перекрывают файлы, что позволяет менять поведение контейнера без пересборки образа.
Проблема появляется, когда приоритет не задокументирован и значение приходит из неожиданного места. Держите .env.example в актуальном состоянии, фиксируйте порядок источников в README сервиса и логируйте на старте, какой источник победил (без вывода самих значений для секретов).
Откат миграций и проверка совместимости версий
Откат проектируют до применения миграции, а не в момент инцидента. Рабочих вариантов три, и они не исключают друг друга.
Стратегии отката миграций
- Обратная миграция. Подходит, если изменение обратимо без потери данных: добавленный столбец, индекс, новая таблица.
- Восстановление из бэкапа с point-in-time recovery. Нужно, когда миграция удалила или переписала данные. Требует проверенного бэкапа и известного времени восстановления.
- Откат на уровне приложения. Флаг функции отключает новую логику, схема остаётся на месте. Часто это самый дешёвый способ вернуть рабочее состояние.
Схема expand/contract (расширение и сжатие) делает откат безопасным. Сначала добавляете новый столбец или таблицу, приложение пишет и в старое, и в новое место, затем чтение переключается на новое, и только после периода стабильности старые объекты удаляются отдельной миграцией. Удаление выносят в конец, поэтому откат на любом шаге не теряет данные.
Бэкап без проверки восстановления ничего не гарантирует. Методику проверки копий, расчёт времени восстановления и порядок действий при откате разбирают в материале о планировании миграции и снижении рисков.
Проверка совместимости версий приложения и БД
Правило: во время деплоя приложение версии N и версия N+1 должны работать с одной и той же схемой. Достигается это расширяющими миграциями и дисциплиной релиза.
- Не удаляйте и не переименовывайте столбец в том же релизе, где меняется код. Переименование идёт в два шага: новый столбец, запись в оба, переключение чтения, удаление старого.
- Новые столбцы делайте nullable или со значением по умолчанию, чтобы старый код продолжал вставлять строки.
- Проверяйте контракты API и схемы событий между сервисами, если версии обновляются не одновременно.
- Прогоняйте миграцию на копии продакшен-данных и запускайте интеграционные тесты уже против мигрированной схемы.
- Используйте канареечный деплой: часть трафика на новой версии покажет несовместимость до полного переключения.
Если изменения затрагивают не только схему, но и всю инфраструктуру, план отката строится шире: проверка совместимости сервисов, окна простоя и порядок переключения описаны в статье о миграции ИТ-инфраструктуры и её скрытых сложностях.
Типичные ошибки и как их избежать
- Секреты в Git. Причина обычно в спешке при первом коммите. Решение: .gitignore для .env, .env.example вместо реальных значений, сканер секретов в pre-commit и в CI.
- Нет обратной миграции. Решение: обратный сценарий в том же коммите, а для необратимых изменений - письменный план отката с бэкапом.
- Миграции без тестирования. На пустой базе они проходят мгновенно, на проде блокируют таблицу на минуты. Решение: прогон на копии продакшен-данных с замером времени и блокировок.
- Ручные правки схемы на продакшене. Миграции перестают отражать реальность, и следующий релиз падает. Решение: любые изменения только через миграции, а расхождения ищут сверкой схемы.
- Миграция данных и схемы одним шагом. При откате теряются данные. Решение: разделять изменение структуры и перенос данных на отдельные шаги.
- Блокирующие миграции в часы пик. Решение: выносить тяжёлые ALTER в окно обслуживания либо разбивать на создание нового объекта и постепенное переключение.
- Избыточные права у CI-раннера. Утечка токена даёт полный доступ к базе. Решение: отдельные учётные записи для миграций с правами только на нужную схему.
Разбор похожих провалов с неполными бэкапами, изменениями в продакшене без ревью и избыточными правами доступа есть в статье о четырёх ошибках системных администраторов.
Чек-лист по управлению конфигурацией
- Разделите конфигурацию и данные: параметры и секреты вне базы, пользовательские данные вне репозитория.
- Проверьте историю репозитория на секреты и отзовите всё найденное, включая ключи, удалённые из последних коммитов.
- Выберите инструмент миграций под свой стек и убедитесь, что таблица версий схемы ведётся автоматически.
- Опишите прямое и обратное действие для каждой миграции, а необратимые изменения выделите в отдельный список.
- Переведите конфиги на шаблоны (Jinja2, Helm, kustomize) вместо копий файлов по средам.
- Перенесите секреты в хранилище (Vault, SOPS, Ansible Vault, менеджер секретов облака) и включите маскирование в CI.
- Добавьте в пайплайн прогон миграции на копии данных, интеграционные тесты и ручное подтверждение перед prod.
- Проверяйте бэкап восстановлением и фиксируйте целевое время восстановления.
- Введите правило обратной совместимости схемы на один релиз вперёд и канареечный деплой для проверки на части трафика.
Начните с первых двух пунктов: вынесите секреты из репозитория и включите миграции с обратными сценариями. Это даёт основной эффект и не требует перестройки всего процесса сразу, а остальные шаги можно добавлять по мере роста нагрузки и числа окружений.