автоматизировать изменения схемы БД через CI/CD и GitOps
Database DevOps: миграции схемы БД в CI/CD-конвейерах
14 минут
Когда релизы приложения и изменения схемы идут разными дорожками, продакшен ломается быстро. В статье — миграции как полноценные артефакты поставки: Flyway или Liquibase, безопасный expand-contract и GitOps-управление порядком выполнения.
Когда релизы приложения и изменения схемы расходятся
Выпуск кода и обновление схемы БД часто ведут разные команды, инструменты и расписания. Отсюда сбои, когда код ждёт колонки, которых нет в базе; сложное восстановление после деструктивного DDL; повторный запуск ручного SQL; изменения в обход review и автотестов; расхождение local, staging и production. В микросервисной среде с десятками независимых схем ручное управление миграциями не масштабируется.
Схема как версионированный артефакт поставки
Принцип простой: изменения схемы — часть конвейера доставки ПО, а не разовые окна DBA. Каждое изменение — упорядоченный файл миграции в Git, review как у кода приложения, применение runner-ом с записью в служебную таблицу истории (для Flyway это flyway_schema_history). Если используете Flyway, придерживайтесь его формата имён: V001__initial_schema.sql, V002__add_users_table.sql. Разовые psql-команды в production убирают гарантии порядка, повторяемости и трассируемости.
Порядок в CI/CD и развёртывание expand-contract
После unit-тестов и до выката новой версии приложения прогоняйте ожидающие миграции на тестовой БД. Проверяйте синтаксис, ограничения и безопасность данных, затем деплойте приложение и smoke-тесты на окружении, близком к production. Для zero-downtime используйте expand-contract: expand — добавить nullable колонки или таблицы при работающей версии N; migrate — заполнить данные, пока старый код игнорирует новые поля; contract — удалить устаревшие объекты только после стабилизации N+1. Свяжите схему с feature flags: код, использующий новые структуры, выключен, пока не применены и миграция, и бинарник.
Откаты: реальность зависит от инструмента
Стратегия отката зависит от инструмента миграций и профиля риска. В Liquibase rollback поддерживается как часть модели. В Flyway многие команды работают по forward-only схеме (особенно в Community): вместо «реверса любой ценой» выпускают корректирующую миграцию, а для деструктивных инцидентов опираются на backup или point-in-time recovery. Если используете Flyway Teams undo, это дополнительный контур безопасности, но не единственный. Feature-ветки по-прежнему несут миграции, а при merge runner применяет только pending-версии.
-- V004__alter_orders_add_status_column.sql
ALTER TABLE orders ADD COLUMN status VARCHAR(20) DEFAULT 'pending';-- V005__fix_orders_status_default.sql
ALTER TABLE orders ALTER COLUMN status SET DEFAULT 'new_pending';Пример: Flyway в GitHub Actions и sync-wave Argo CD
Структура рядом с кодом сервиса: db/migrations для версионированного SQL, db/flyway.conf для подключения, workflow в CI, который validate и migrate на ephemeral PostgreSQL перед integration-тестами. В Kubernetes Argo CD может задать порядок через sync-wave, но Job миграции должен получать и конфиг, и SQL-файлы. sync-wave управляет только порядком среди отрендеренных манифестов и сам по себе не доставляет миграции в контейнер.
flyway.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
flyway.user=${DB_USER}
flyway.password=${DB_PASSWORD}
flyway.locations=filesystem:./migrations
flyway.baselineOnMigrate=true
flyway.outOfOrder=false
flyway.validateOnMigrate=truename: Database Migrations
on:
push:
paths:
- 'db/**'
- 'src/**'
jobs:
validate-migrations:
runs-on: ubuntu-latest
env:
DB_HOST: localhost
DB_PORT: 5432
DB_NAME: testdb
DB_USER: testuser
DB_PASSWORD: testpassword
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpassword
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Flyway
run: |
curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/9.22.3/flyway-commandline-9.22.3.zip -o flyway.zip
unzip flyway.zip
sudo mv flyway-9.22.3 /opt/flyway
sudo ln -s /opt/flyway/flyway /usr/local/bin/flyway
- name: Validate migration files
run: flyway -configFiles=db/flyway.conf validate
- name: Run migrations against test database
run: flyway -configFiles=db/flyway.conf migrate
- name: Run application integration tests
run: ./gradlew integrationTest
- name: Inspect migration history
run: flyway -configFiles=db/flyway.conf infoapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: orders-db-migrations
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/orders-service
targetRevision: main
path: db
destination:
server: https://kubernetes.default.svc
namespace: orders-db
syncPolicy:
automated:
prune: false
selfHeal: false
syncOptions:
- ApplyOutOfSyncOnly=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 60sapiVersion: batch/v1
kind: Job
metadata:
name: orders-migration-v004
namespace: orders-db
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: OnFailure
containers:
- name: flyway
image: flyway/flyway:9.22.3
args:
- migrate
- -configFiles=/mnt/config/flyway.conf
- -locations=filesystem:/flyway/sql
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: orders-db-credentials
key: host
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: orders
- name: DB_USER
valueFrom:
secretKeyRef:
name: orders-db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: orders-db-credentials
key: password
volumeMounts:
- name: flyway-config
mountPath: /mnt/config
- name: flyway-sql
mountPath: /flyway/sql
volumes:
- name: flyway-config
configMap:
name: flyway-config
- name: flyway-sql
configMap:
name: orders-db-migrations-sqlЛучшие практики: additive-изменения и обратимость
Сначала additive-миграции: nullable колонка, backfill, затем NOT NULL. Не делайте DROP, пока все инстансы не перестали читать объект — expand-contract и feature flags отделяют рискованный DDL от выката фичи. Тестируйте на объёме, близком к production: пять секунд на сотне строк могут стать часами на десятках миллионов. Разделяйте DDL и DML: схема применяется быстро, backfill — батчами в фоне. В PostgreSQL поведение блокировок зависит от операции и версии: часть ALTER TABLE проходит как metadata-only, а часть всё ещё может брать ACCESS EXCLUSIVE. Перед рискованным DDL задавайте lock_timeout и планируйте тяжёлые операции на окна низкого трафика.
-- Избегайте немедленного NOT NULL на существующих строках
ALTER TABLE orders ADD COLUMN priority VARCHAR(10) NOT NULL; -- упадёт
ALTER TABLE orders ADD COLUMN priority VARCHAR(10);
UPDATE orders SET priority = 'medium' WHERE priority IS NULL;
ALTER TABLE orders ALTER COLUMN priority SET NOT NULL;-- V005: изменение схемы (быстро)
ALTER TABLE orders ADD COLUMN shipped_at TIMESTAMP;
-- V006: миграция данных (можно async батчами)
UPDATE orders SET shipped_at = updated_at WHERE status = 'shipped';SET lock_timeout = '2s';
ALTER TABLE orders ADD COLUMN tracking_number VARCHAR(100);Инструменты, паритет окружений и уверенность в масштабе
Используйте Flyway, Liquibase, goose или встроенные runner-ы фреймворка — не ручной psql. Не запускайте приложение, пока миграции не успешны: через init-контейнеры, startup-проверки или deployment gates. Dev, staging и production должны использовать одинаковую модель runner-ов и конфигурации; отличаются только credentials и endpoints. Каждая миграция проходит тот же code review: назначение, стратегия восстановления, влияние на производительность, обратная совместимость. Мониторьте длительность миграций в production — скачок часто сигнализирует о росте данных, блокировках или отсутствии индексов. Управление схемой — не отдельная дисциплина от DevOps: версионирование, автоматизация и операционная репетиция дают ту же уверенность, что и релизы приложения, и сохраняют аудит каждого ALTER.
Порядок миграций в Kubernetes естественно сочетается с декларативной поставкой из гайда по GitOps с Argo CD и Flux.
Если работа со схемой тормозит релизы, ищите трение вместе с диагностикой узких мест release-пайплайна.
