Kubernetes 환경에서 Flyway 사용하기

Flyway는 데이터베이스 마이그레이션을 자동화하고 관리하기 위한 도구로, SQL 기반 마이그레이션 스크립트를 버전 관리할 수 있으며 애플리케이션 배포 파이프라인과 자연스럽게 통합됩니다. 애플리케이션에 Flyway를 포함하여 Kubernetes에 배포할 경우, 여러 Pod가 동시에 실행되면 DB 테이블 락(table lock) 문제로 인해 마이그레이션 충돌이 발생할 수 있습니다. 이를 방지하기 위해 분산 락(distributed lock)을 사용하여 먼저 시작된 Pod가 다른 Pod의 마이그레이션 완료를 기다리도록 구현할 수 있습니다.

또 다른 접근 방법으로, Flyway 마이그레이션을 애플리케이션에서 완전히 분리하고, Kubernetes 환경에서 Job 리소스로 관리하는 방법이 있습니다. 이 방식은 마이그레이션을 배포와 별도로 한 번만 실행되도록 보장하여, 여러 개의 애플케이션 Pod가 동시에 기동될 때 발생하는 충돌 문제를 근본적으로 해결합니다. 이렇게 Job으로 분리하여 DB 마이그레이션을 실행하면 네트문제로 잠시 실패를 해도 재시도가 가능하며, 애플리케이션과 완전히 분리함으로써 문제 발생 시 대처가 더 용이해집니다.

이 두 가지 접근 방식은 모두 유효하며, 프로덕션 환경에서는 보통 후자인 Kubernetes Job을 이용한 분리 전략이 더 선호됩니다. 이는 마이그레이션의 안정성과 배포 파이프라인의 견고성을 크게 향상시키기 때문입니다.

이번 글에서는 Kubernetes 방식으로 Flyway를 사용하여 데이터베이스 마이그레이션하는 방법에 대해 살펴보겠습니다.
Flyway 대한 자세한 사용법은 Redgate Flyway Documentation를 참고하세요.


1. SQL 스크립트 준비

./sql 디렉토리에 Flyway 마이그레이션용 SQL 스크립트 파일을 준비합니다. 각 스크립트 파일은 Flyway 규칙을 따라 버전과 설명이 포함된 이름으로 작성해야 합니다.

“V1__init.sql”

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

“V2__add_email.sql”

ALTER TABLE users ADD COLUMN email VARCHAR(255);

kubectl 명령어를 사용하여 ./sql 폴더의 SQL 파일을 가지고 flyway-sql-configmap 파일을 생성합니다.

kubectl create configmap flyway-sql \
  --from-file=./sql \
  --dry-run=client -o yaml > flyway-sql-configmap.yaml

2. Flyway 마이그레이션 설정

Flyway를 활용해 데이터베이스의 스키마와 데이터를 관리하는 설정 파일을 생성합니다. 아래 설정은 MiradbDB의 기존 Project 데이터베이스를 기준으로 baseline을 잡고, 마이그레이션 스크립트 위치를 지정하여 Flyway가 DB 스키마를 관리할 수 있도록 구성했습니다.

“flyway.toml”

[environments.mysql]
url = "jdbc:mysql://mariadb:3306/project"
user= "root"
password = "password"
baselineOnMigrate = true
baselineVersion = "0"

# schemas =
# defaultSchema =
# connectRetries = 
# connectRetriesInterval =
# initSql =
# jdbcProperties =


[flyway]
environment = "mysql"
locations = ["filesystem:/flyway/sql"]

kubectl 명령어를 사용하여 ./sql 폴더의 SQL 파일을 가지고 flyway-config-configmap 파일을 생성합니다.

kubectl create configmap flyway-config \
  --from-file=./flyway.toml \
  --dry-run=client -o yaml > flyway-config-configmap.yaml

3. Flyway 마이그레이션 Job

Kubernetes에서 Flyway 마이그레이션을 수행하는 Job 리소스를 구성합니다.

“flyway-migration-job.yaml”

apiVersion: batch/v1
kind: Job
metadata:
  name: flyway-migration
spec:
  template:
    spec:
      containers:
      - env:      
        - name: _JAVA_OPTIONS
          value: "-XX:UseSVE=0" # for arm64 only
        name: flyway
        image: flyway/flyway:11.11.1-alpine
        args: [ migrate ]
        volumeMounts:
        - name: flyway-sql
          mountPath: /flyway/sql
        - name: flyway-config
          mountPath: /flyway/conf          
      volumes:
      - name: flyway-sql
        configMap:
          name: flyway-sql
      - name: flyway-config
        configMap:
          name: flyway-config

flyway-migration Job을 배포하면, 다음과 같은 순서로 Pod 생성 작업이 진행됩니다.

  • SQL 스크립트와 Flyway 설정이 포함된 ConfigMap을 Volume으로 매핑하고 마운트합니다.
  • Flyway가 DB 접속하여 데이터베이스에 마이그레이션 작업을 수행합니다.
  • 마이그레이션 작업이 완료된 후, Job으로 생성된 Pod는 자동으로 완료가 됩니다.

4. Flyway 마이그레이션 실행

다음과 같이 MariaDB 데이터베이스를 생성하고, 위에서 만든 SQL 스크립트와 Flyway 설정을 가지고 Flyway 마이그레이션 Job을 배포합니다.

# MariaDB DB 배포
kubectl run mariadb --image=mariadb:11.7.2 \
 --env="MARIADB_ROOT_PASSWORD=password"\
 --env="MYSQL_DATABASE=project"
kubectl expose pod mariadb --port=3306 --target-port=3306 --name=mariadb

# Flyway Config, SQL ConfigMap 배포
kubectl apply -f flyway-sql-configmap.yaml
kubectl apply -f flyway-config-configmap.yaml

# Flyway 마이그레이션 Job 배포
kubectl apply -f flyway-migration-job.yaml

Flyway 마이그레이션 진행 현황은 로그를 통해 확인합니다.

# Flyway 마이그레이션 로그 확인
kubectl logs -f job/flyway-migration

Database: jdbc:mysql://mariadb:3306/project (MariaDB 11.7)
Schema history table `project`.`flyway_schema_history` does not exist yet
Successfully validated 2 migrations (execution time 00:00.012s)
Creating Schema History table `project`.`flyway_schema_history` ...
Current version of schema `project`: << Empty Schema >>
Migrating schema `project` to version "1 - init"
Migrating schema `project` to version "2 - add email"
Successfully applied 2 migrations to schema `project`, now at version v2 (execution time 00:00.012s)

참고로, Kubernetes의 Job은 한 번 실행이 완료되면 동일한 이름으로는 재실행되지 않습니다. 재실행을 위해서는 기존 Job의 이름을 변경하거나, 삭제 후 재포해야합니다.


5. Pod간 배포 순서 제어

일반적으로 Flyway 마이그레이션 Job이 먼저 진행된 이후, 어플리케이션 실행이 되어야 정상적으로 동작이 됩니다. 이를 위해 InitContainer를 사용하여, Flyway 마이그레이션 Job이 완료되었는지 확인을 하고 애플리케이션 Pod를 시작할 수 있는 조건을 설정할 수 있습니다.

먼저 InitContainer가 API 그룹에서 Kubernetes 리소스를 조회할 수 있는 권한을 구성하고 배포합니다.

kubectl apply -f -<<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: resource-reader-role
  namespace: default
rules:
  - apiGroups: ["batch"]
    resources: ["jobs"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "daemonsets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: resource-reader-binding
  namespace: default
subjects:
  - kind: ServiceAccount
    name: default
    namespace: default
roleRef:
  kind: Role
  name: resource-reader-role
  apiGroup: rbac.authorization.k8s.io
EOF

k8s-wait-for 이미지를 사용하여 flyway-migration Job이 완료될때까지 기다리도록 InitContainer를 구성합니다. 여기서 메인 컨테이너는 flyway-migration Job을 Pod로 변경하고 실행 커맨드는 ‘sleep infinity’로 설정하였습니다.

kubectl apply -f -<<EOF
apiVersion: v1
kind: Pod
metadata:
  name: flyway-migration-pod
  namespace: default
spec:
  initContainers:
  - name: wait-for-migration
    image: groundnuty/k8s-wait-for:no-root-v2.0
    args: ["job", "flyway-migration"]
  containers:
  - env:      
    - name: _JAVA_OPTIONS
      value: "-XX:UseSVE=0" # for arm64 only
    name: flyway
    image: flyway/flyway:11.11.1-alpine
    command: [ "/bin/sh", "-c", "sleep infinity" ]
    volumeMounts:
    - name: flyway-sql
      mountPath: /flyway/sql
    - name: flyway-config
      mountPath: /flyway/conf          
  volumes:
  - name: flyway-sql
    configMap:
      name: flyway-sql
  - name: flyway-config
    configMap:
      name: flyway-config
EOF

flyway-migration-pod를 배포를 하면, 다음과 같은 순서로 Pod 생성 작업이 진행됩니다.

  • wait-for-migration InitContainer가 flyway-migration Job이 완료될 때까지 대기합니다.
  • flyway-migration Job이 완료되면 다음으로 flyway 컨테이너를 실행합니다.
  • flyway-migration Job이 실패를 하면 바로 InitContainer가 실패를 하게되고 Pod 실행이 중단됩니다.

이렇게, InitContainer를 이용하여 Pod간 실행 순서를 조정하는 방법에 대해 알아보았습니다. 또한 Helm과 ArgoCD를 이용하여 특정 리소스를 먼저 실행하도록 설정함으로써, Pod 생성 전에 필요한 작업을 선행할 수도 있습니다.


6. 마이그레이션 테이블 검증 및 자동복구

Flyway는 데이터베이스 마이그레이션을 안전하고 신뢰성 있게 수행할 수 있도록 다음과 같은 기능을 제공합니다.

  • info → 전체 마이그레이션의 현황을 확인합니다.
  • validate → SQL 스크립트와 schema_version 테이블 상태를 검증합니다.
  • repair → 실패한 마이그레이션이나 체크섬 오류를 자동으로 복구합니다.

Flyway가 Job으로 실행되어 만들어진 Pod는 완료되어 접근이 불가능하기 때문에, “4. Pod간 배포 순서 제어"에서 만들어진 flyway-migration-pod를 활용하여 flyway의 info, validate, repair 기능을 실행하여 보겠습니다.

# flyway-migration-pod shell 접속
kubectl exec -it flyway-migration-pod -- bash

# Get migration info
flyway info

Database: jdbc:mysql://mariadb:3306/project (MariaDB 11.7)
Schema version: 2

+-----------+---------+-------------+------+---------------------+---------+----------+
| Category  | Version | Description | Type | Installed On        | State   | Undoable |
+-----------+---------+-------------+------+---------------------+---------+----------+
| Versioned | 1       | init        | SQL  | 2025-08-18 02:46:39 | Success | No       |
| Versioned | 2       | add email   | SQL  | 2025-08-18 02:46:39 | Success | No       |
+-----------+---------+-------------+------+---------------------+---------+----------+

# Validate Flyway migrations.
flyway validate

Database: jdbc:mysql://mariadb:3306/project (MariaDB 11.7)
Successfully validated 2 migrations (execution time 00:00.027s)

# Repair Flyway schema_version table
flyway repair

Database: jdbc:mysql://mariadb:3306/project (MariaDB 11.7)
Repair of failed migration in Schema History table `project`.`flyway_schema_history` not necessary. No failed migration detected.
Successfully repaired schema history table `project`.`flyway_schema_history` (execution time 00:00.029s).

7. ArgoCD에서 Job 동기화

InitContainer는 처음 배포 시, Job 리소스가 먼저 실행되고 어플리케이션 Pod가 변경되었을 때 배포 순서를 제어하기 위해 사용할 수 있지만, 운영 중에는 SQL 변경이 발생하면 이런 통제가 불가능합니다. 이 문제를 해결하기 위한 방법 중 하나로 ArgoCD를 활용할 수 있습니다.

ArgoCD는 Kubernetes를 위한 GitOps 도구로 Git 리포지토리 상태를 기준으로 클러스터 리소스를 자동으로 동기화하고 관리합니다. SQL이 변경되어 마이그레이션 작업을 먼저 실행하고 어플리케이션을 rollout해야 하는 경우, ArgoCD의 sync-wave 기능을 적용하여 실행 순서를 제어할 수 있습니다.

“flyway-migration-job.yaml”

apiVersion: batch/v1
kind: Job
metadata:
  name: flyway-migration
  annotations:
    argocd.argoproj.io/sync-wave: "0" # 1)
    argocd.argoproj.io/sync-options: Replace=true # 2)
    
    :

  volumes:
  - configMap:
      defaultMode: 420
      name: flyway-sql-<kustomize-hash>  # 3)
    name: sql-config
  1. argocd.argoproj.io/sync-wave: “0” → ArgoCD가 Application을 싱크할 때 이 Job을 가장 먼저 실행하도록 지정합니다. 이후에 실행되어야 하는 리소스에는 0보다 큰 값을 설정해야 합니다.
  2. argocd.argoproj.io/sync-options: Replace=true → Job은 완료되면 재사용할 수 없으므로, 스펙이 변경되면 기존 Job을 삭제한 후 새로 생성합니다.
  3. flyway-sql- → Kustomize의 configMapGenerator를 사용하여 ConfigMap 리소스의 이름에 해시 추가

이렇게 하면 GitOps 환경에서 SQL 스크립트(ConfigMap) 변경하면 Job의 스펙이 변경되어 Flyway 마이그레이션 Job이 먼저 실행되고 애플리케이션이 rollout되어 안전하게 변경 사항이 반영될 수 있습니다.


8. 마무리

이번 글에서는 Flyway 마이그레이션을 애플리케이션에서 완전히 분리하여, Kubernetes 환경에서 Job 리소스로 구성하는 방법에 대해 알아보았습니다.

Flyway 마이그레이션을 어플리케이션에서 분리하여 관리함으로써, 다수의 인스턴스가 생성될 때 생겨나는 충돌 문제를 해결하고 DB 관리의 효율성을 높이지만, 운영 중에 SQL 추가나 변경에 따른 안정적인 마이그레이션을 위해서는 여전히 많은 고려사항이 존재합니다.