Миграции БД в пайплайне
Зачем это на собесе
Где и когда выполнять миграции БД в CD, чтобы деплой не ломал прод. Связывает CI/CD и эксплуатацию БД.
Принципы
- схема БД — часть релиза, миграции версионируются и хранятся в репозитории;
- миграции применяются автоматически в пайплайне, а не вручную;
- изменения обратно совместимы со старой версией приложения (rolling update, откат кода);
- миграция не смешивается с тяжёлой миграцией данных (отдельные фоновые задачи);
- применённые миграции не редактируются; исправление — новой миграцией;
- roll-forward предпочтительнее down-миграций;
- пайплайн обеспечивает ровно один запуск миграции на окружение.
Где запускать
| Способ | Плюсы | Минусы |
|---|---|---|
| Отдельное задание пайплайна (job) перед деплоем | контроль, отдельные права, логи, откат/остановка деплоя при ошибке | нужен доступ раннера к БД |
Init-контейнер / Kubernetes Job (Helm pre-upgrade hook) | рядом с окружением, без доступа CI к БД | несколько реплик → гонки (нужна блокировка), сложнее отладка |
В коде приложения при старте (Migrate()) | просто | гонки инстансов, права DDL у приложения, падение старта, не подходит для прода |
| Вручную DBA | строгий контроль | медленно, ошибки |
Рекомендация: отдельный шаг пайплайна/Job с выделенной учётной записью, выполняемый до выкатки нового кода (для expand-миграций) и после выкатки (для contract).
migrate-staging:
stage: migrate
image: registry.example.com/clinic/migrator:$CI_COMMIT_SHORT_SHA # образ с bundle/SQL
environment: staging
script:
- ./efbundle --connection "$MIGRATION_CONNECTION_STRING"
resource_group: staging-db # не параллельно
needs: [docker-image]
deploy-staging:
stage: deploy
needs: [migrate-staging]Helm hook:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}-migrator:{{ .Values.image.tag }}"
envFrom: [{ secretRef: { name: db-migration } }]Порядок в релизе
1. бэкап/точка восстановления (для критичных изменений)
2. миграция expand (совместима со старым кодом)
3. выкатка нового приложения (rolling)
4. smoke-тесты, мониторинг
5. (позже, отдельным релизом) миграция contract (удаление старого)Подробнее: expand and contract, безопасные DDL, lock_timeout, CREATE INDEX CONCURRENTLY, бэкфилл батчами (см. раздел «Базы данных» → миграции).
Проверки в CI
- применить все миграции на чистой БД (Testcontainers/сервис
postgres) и запустить тесты; - проверка совместимости: схема после миграций ↔ модель приложения (
dotnet ef migrations has-pending-model-changes); - линтер миграций: squawk (PostgreSQL), Atlas lint, SQLFluff: ловят опасные операции (
ALTER ... NOT NULLбез default, индекс безCONCURRENTLY,DROP COLUMN); - прогон на копии данных (обезличенной) для оценки времени и блокировок;
- проверка, что применённые миграции не изменены (checksum Flyway
validate).
Права и безопасность
- отдельная учётная запись
migrator(DDL) иapp(DML); миграции не выполняют от суперпользователя; - строка подключения миграций — в защищённом хранилище, доступна только jobs деплоя на protected-ветках;
- пароль не логируется; сеть: раннер/джоба видит БД через VPN/приватную сеть, не из публичного интернета;
- аудит: кто и когда применил миграцию (таблица истории + лог пайплайна).
Откат
- откат кода: возврат предыдущего образа — возможен благодаря обратной совместимости;
- откат миграции: чаще roll-forward (новая миграция-исправление);
- критичные сбои: восстановление из бэкапа/PITR (RPO/RTO заранее известны);
- «down» скрипты тестируются, если используются; destructive down не автоматизируют на проде.
Мульти-окружения и мульти-тенанты
- миграции применяются последовательно: dev → staging → prod, одним и тем же артефактом;
- один и тот же набор миграций во всех окружениях; различия данных — seed отдельно (справочники, не тестовые данные в проде);
- много БД/тенантов: оркестрация по списку с остановкой при первой ошибке, отчёт, возобновление.
Проблемы
| Проблема | Решение |
|---|---|
| Миграция зависла на блокировке | lock_timeout, короткие транзакции, мониторинг pg_stat_activity, повтор |
| Две миграции одновременно | блокировка инструмента (Flyway/EF lock) + resource_group/concurrency |
| Деплой прошёл, миграция нет | порядок и needs, блокировка выкатки при ошибке |
| Расхождение схем окружений | дрейф: сравнение схем (Atlas, pg_dump --schema-only), запрет ручных изменений |
| Долгая миграция вызывает таймаут пайплайна | вынести в фоновую задачу, батчи |
Вопросы с ответами
Где лучше запускать миграции БД в CD?
Отдельным шагом пайплайна или Kubernetes Job до деплоя приложения с выделенной учёткой; не в момент старта приложения.
Как сделать откат релиза, если миграция уже применена?
Схема остаётся обратно совместимой со старым кодом (expand), поэтому откатывается только приложение; исправление схемы — новой миграцией; при катастрофе — восстановление из бэкапа.
Как проверить миграции до прода?
Применить на чистой БД и на обезличенной копии боевых данных в CI/staging, прогнать линтер опасных операций и тесты, проверить время и блокировки.