Миграции БД в пайплайне

↑ Этап 3 · CI-CD

Обязательно~6 мин чтенияУровень: middle

Зачем это на собесе

Где и когда выполнять миграции БД в 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), запрет ручных изменений
Долгая миграция вызывает таймаут пайплайнавынести в фоновую задачу, батчи

Вопросы с ответами