글 작성자: beaniejoy
      • Overview
      • Seed 데이터의 필요성
      • 쿼리 실행의 올바르지 못한 순서로 인한 문제 발생
      • dockerize와 shell script를 활용한 migration 순서 보장
        - docker-compose.yml
        - Dockerfile(Flyway)
        - docker-entrypoint.sh
      • Flyway docker 실행
      • 결론

 

📌 1. Overview

이 글을 읽으시기 전에 docker를 이용한 flyway migration 적용하기 게시글을 먼저 읽으시는 것을 추천합니다.
(이번 게시글은 해당 게시글에서 이어지는 내용입니다. https://beaniejoy.tistory.com/58)

 

[Flyway] docker를 이용한 flyway migration 자동화

Overview flyway docker image flyway 관련 프로젝트 내부 디렉토리 설정 docker 실행해서 migration 확인하기 정리 📌 Overview 새로운 사이드 프로젝트 진행을 하면서 flyway를 이용해 db migration을 진행하고..

beaniejoy.tistory.com

저번 게시글에서는 flyway migration을 gradle을 통한 spring project에 적용하는 방법이 아닌 docker를 활용하여 적용했습니다.
이번 게시글은 docker로 적용했던 flyway migration을 저의 사이드 프로젝트에 적용을 해보다 치명적인 문제에 직면하게되어 해당 문제의 발생 원인과 해결과정을 정리해보고자 작성하게 되었습니다.

 

📌 2. Seed 데이터의 필요성

개인 프로젝트를 진행하면서 테스트를 위해 사용할 Seed 데이터를 적용할 필요성을 느꼈습니다. 그렇잖아도 Flyway를 이용한 seed 데이터 적용하는 내용과 관련하여 강남언니 테크 블로그에 좋은 게시물이 있어 이를 참고했습니다.
(Flyway 로 Java 에서 DB schema, seed 관리하기)

 

Flyway 로 Java 에서 DB schema, seed 관리하기

앗! DB schema 관리 신발보다 싸다 by 강남언니 블로그

blog.gangnamunni.com

production 환경에서 seed를 migration할 때 같이 적용하면 안된다는 내용이 있습니다. 실제 데이터를 적용해야 되는 영역이기에 seed 데이터가 있으면 당연히 안되지만 저는 개인 프로젝트로 실제 클라우드 서버에 배포시에도 해당 seed 데이터를 적용할 생각으로 프로젝트를 구성하고 있습니다.

그래서 저는 다음과 같이 docker compose 설정 파일에 seed 적용 부분을 추가하였습니다.

version: "3.8"
services:
  db-mysql:
    image: mysql:5.7.34
    ports:
      - "3306:3306"
    env_file:
      - env/mysql.env
  migration:
    image: flyway/flyway:7.5.1
    command: -configFiles=/flyway/conf/flyway.config -locations=filesystem:/flyway/sql -connectRetries=60 migrate
    volumes:
      - ${PWD}/flyway/db-migration/main:/flyway/sql
      - ${PWD}/flyway/conf/flyway_main.conf:/flyway/conf/flyway.config
    depends_on:
      - db-mysql
  seed:
    image: flyway/flyway:7.5.1
    command: -configFiles=/flyway/conf/flyway.config -locations=filesystem:/flyway/sql -connectRetries=60 migrate
    volumes:
      - ${PWD}/flyway/db-migration/seed:/flyway/sql
      - ${PWD}/flyway/conf/flyway_seed.conf:/flyway/conf/flyway.config
    depends_on:
      - db-mysql
      - migration

migration과 seed를 따로 container로 만들어서 각각의 command를 통해 migrate 하도록 했습니다.

project
├── env
    └── mysql.env (mysql 도커 환경변수)
├── flyway 
    ├── conf
    	├── flyway_main.conf (migration 관련 flyway 설정내용)
    	└── flyway_seed.conf (seed 관련 flyway 설정내용)
    └── db-migration
    	├── main
            ├── V001_Create_table_name.sql (migration 대상 sql 파일들)
            └── ...
        └── seed
            ├── R__Seed_cafe.sql (seed 대상 sql 파일들)
            └── ...
└── docker-compose.yml (docker compose 설정)

수정된 프로젝트 예시 구조도입니다. migration할 대상 sql파일들을 테이블 구성(DDL 쿼리)부분과 Seed 데이터 구성(INSERT 쿼리)부분으로 나누었습니다. 설정 파일도 main과 seed conf파일 두 개로 나누어 관리하도록 했습니다.

 

📌 3. 쿼리 실행의 올바르지 못한 순서로 인한 문제 발생

Seed 데이터 구성을 위한 Repeatable Migration을 적용하는데 있어서 Versioned Migation이 반드시 선행되어야 합니다. 안그러면 없는 테이블을 참조하게 되어 쿼리상 에러가 발생하게 됩니다.

처음에 docker-comopse.yml에서 seed container 설정 부분에 depends_on 옵션에 migration container도 추가함으로써 migration container가 먼저 실행되도록 시도해보았으나 해결하는데 실패했습니다. depends_on 옵션은 container의 시작되는 순서만 보장되기에 컨테이너가 올라가고 실행되는 부분까지 컨테이너간의 순서를 보장해주지는 않는다는 것을 알게 되었습니다.

이로 인해 compose 설정 파일 내에서 migration, seed 컨테이너 분리를 통해 seed를 적용하는 방법에서 다른 방법을 찾아보기 시작했습니다.

 

📌 4. dockerize와 shell script를 활용한 migration 순서 보장

여러 해결 방법들이 존재했었는데 그 중에서 저는 dockerize 툴을 이용해보기로 했습니다. dockerize를 적용하기 위해 docker compose 설정파일에 migration, seed 컨테이너를 하나의 컨테이너로 구성하고 해당 컨테이너에 대한 Dockerfile을 따로 작성하였습니다.

 

4-1. docker-compose.yml

version: "3.8"
services:
  db-mysql:
    image: mysql:5.7
    ports:
      - "3306:3306"
    env_file:
      - env/mysql.env
  migration:
    build:
      context: ./flyway
      dockerfile: Dockerfile
    environment:
      FLYWAY_EDITION: community
    volumes:
      - ${PWD}/flyway/db-migration:/flyway/sql
      - ${PWD}/flyway/conf:/flyway/conf
    depends_on:
      - db-mysql
  • FLYWAY_EDITION
    community버전을 명시적으로 설정했습니다. Dockerfile에서 flyway:7-alpine image를 사용했는데 해당 버전과 MySQL 5.7 버전을 같이 실행했을 때 default로 flyway teams edition으로 적용되는 것을 방지하고자 community로 설정하였습니다.
    (flyway latest버전말고 down version한 이유는 저번 flyway 게시물에 언급드렸습니다.)
  • volumes
    migration과 seed container를 하나로 합치면서 volume 지정하기는 오히려 수월해진 것 같습니다. docker flyway는 컨테이너 생성 후 명령어 실행시 [/flyway/sql, /flyway/conf] 두 개의 디렉토리를 참고해 migration 작업을 수행합니다. 저는 table 생성 migration 부분과 seed insert하는 mirgation 부분 두 파트로 나누어 진행해야 되기에 shell script에서 config file로 참고할 디렉토리로 볼륨을 지정하였습니다.
    - /flyway/sql: docker flyway 컨테이너 생성시 실제 DB에 적용될 쿼리들을 담아놓은 디렉토리입니다. 본인이 따로 지정한 db migration용 쿼리를 담아놓은 디렉토리와 매핑시켜놓으면 됩니다.
    - /flyway/conf: flyway migrate 실행할 때 참고하는 설정파일을 담아두는 디렉토리입니다. 제가 따로 지정한 두 부분에 대한 설정파일 두 개를 담은 디렉토리를 매핑하였습니다.

 

4-2. Dockerfile (Flyway)

FROM flyway/flyway:7-alpine

USER root

# run flyway in order through dockerize utility.
RUN apk add --no-cache openssl

ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz

COPY ./docker-entrypoint.sh docker-entrypoint.sh

RUN chmod +x docker-entrypoint.sh

ENTRYPOINT ["./docker-entrypoint.sh"]
  • USER root
    alpine내에서 root 계정으로 접근 설정하였습니다. 바로 뒤에 나오는 apk에 대한 permission 실행 권한 획득을 위해서 설정했습니다.
  • ENV DOCKERIZE_VERSION ~ RUN
    dockerize 모듈을 받아오기 위한 명령어 설정입니다. 해당 내용은 dockerize github repo에서 확인할 수 있습니다.
    https://github.com/jwilder/dockerize
  • ENTRYPOINT ["./docker-entrypoint.sh"]
    dockerize 모듈 다운과 압축해제를 완료하였다면 미리 정해둔 shell script를 통해 migration을 실행하면 됩니다.
    (그 전에 해당 shell script에 chmod를 통해 실행 권한을 부여해야 합니다.)

 

4-3. docker-entrypoint.sh

#!/bin/bash
echo "wait DB container up"
dockerize -wait tcp://db-mysql:3306 -timeout 20s

# DB Migration
echo "run database migration"
flyway -configFiles=/flyway/conf/flyway_main.conf -locations=filesystem:/flyway/sql/main migrate

# Seed Migration
echo "insert seed data"
flyway -configFiles=/flyway/conf/flyway_seed.conf -locations=filesystem:/flyway/sql/seed migrate
  • dockerize wait
    mysql docker container가 완전히 올라가 서버 구동이 완료될 때 까지 wait하도록 해줍니다. dockerize에서 제공해주는 wait 덕분에 전에 flyway option으로 지정했던 "-connectRetries=60" 필요 없어졌습니다.
  • migration 실행
    migration 두 파트(tables, seed)에 대해서 실행해주는 flyway 명령어를 입력합니다. 여기서 config file과 sql 대상 쿼리 지정은 전에 docker compose 설정파일에 volume으로 각각 매핑해두었기 때문에 해당 디렉토리에서 가져다 사용하기만 하면 됩니다.
    ("/flyway/conf", "/flyway/sql")

 

📌 5. Flyway docker 실행

$ docker-compose up --build

Flyway migration을 위한 docker 설정은 완료되었습니다. docker를 띄워보겠습니다.

migration container가 실행되면서 shell script의 첫번째 dockerize wait가 실행됩니다. 아직 db-mysql container의 mysql 서버가 완전히 올라온 것은 아니기 때문에 connection refused 되면서 sleep 상태로 들어갑니다.

spring boot application도 실행 시작하였습니다. 하지만 아직 mysql 서버 구동이 완료되지 않은 상태이기에 migration은 계속 대기 상태입니다.

드디어 mysql 서버가 연결 준비완료가 되었습니다. mysql 서버와 연결이 되면서 dockerize의 역할은 종료되고 바로 flyway migration을 수행합니다. 최종적으로 migration을 성공했다는 로그가 출력되면 모든 작업은 마무리 됩니다.

 

📌 결론

Flyway를 접하면서 migration을 효과적으로 적용할 수 있는 방법을 계속 고민하고 찾아보다가 여기까지 온 것 같습니다.

처음에는 spring boot 프로젝트에 gradle 의존성으로 flyway를 가져다가 spring boot app이 실행되고 migration을 같이 수행하는 방법으로 migration을 진행하였습니다. 그런데 이 방식으로는 seed 데이터 주입에 대한 migration을 따로 설정할 수 없었고 (물론 이것도 spring boot 프로젝트 내에서 설정하는 방법이 있을 것 같은데 저는 못찾았네요..) spring boot 프로젝트 내에 flyway migration 부분을 같이 설정한다는 것은 수정에 있어서 application 전체를 다시 빌드하고 배포해야 한다는 불편이 있습니다.

물론 위의 방식도 하나의 docker compose 설정으로 묶여있어서 flyway 수정시 다같이 빌드하고 배포해야 하지만 이를 활용한다면 flyway와 database 영역을 따로 구성해서 application 영역과 분리하는 방식으로 사용하여 수정시 서로 영향을 끼치지 않도록 할 수 있다고 생각합니다.

 

참고

 

GitHub - beaniejoy/test-project-repository: 🧪 Test Repository, which manages and tests various frameworks, libraries and modu

🧪 Test Repository, which manages and tests various frameworks, libraries and modules, that consists of directories named by each topic. - GitHub - beaniejoy/test-project-repository: 🧪 Test Reposito...

github.com