🐳

[docker-compose] DB continer(service) へ migration を行う

2021/12/29に公開

要件

  • docker-compose で DB container 起動時に、同時に migration を行いたい
  • 既に起動している DB container へ 任意のタイミングで migration したい
  • できれば migration bin をlocalにインストールして行うのではなく、docker を使用したい

実行環境

  • MacOS Big Sur v11.6
  • Apple M1

Tools

Database Migration tool
MySQL golang migrate

https://github.com/golang-migrate/migrate

結論

file system

以下のようなディレクトリ構成を前提としています。
本稿は、migrationが主題であってseedは本質ではないので割愛します。
また、migrationの内容(DDL)自体も何でもいいので、sqlファイルの中身も割愛します。

.
├── db
│   ├── conf.d
│   │   └── my.cnf
│   ├── migrations
│   │   │── Dockerfile
│   │   │── entrypoint.sh
│   │   └── migrations
│   │       └── 0001_init.up.sql
│   │       └── 0001_init.down.sql
│   │       └── 0002_xxxx.up.sql
│   │       └── 0002_xxxx.down.sql
│   └── seeds
│       │── xxxx.sql
│       │── ...
└── docker-compose.yml

docker-compose

docker-compose.yml
version: "3"

services:
  db:
    image: mysql:5.6
    platform: linux/amd64
    environment:
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - db_volume:/var/lib/mysql:cached
      - ./db/conf.d:/etc/mysql/conf.d
  migrate:
    build: ./db/migrations
    depends_on:
      - db
    command: ["up"]
    environment:
      WAIT_HOSTS: db:3306
      MIGRATIONS_DIR: /migrations
      MYSQL_HOST: db
      MYSQL_PORT: 3306
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: password
    volumes:
      - ./db/migrations/migrations:/migrations
volumes:
  db_volume:
    driver: local

migrate Dockerfile

migrate/Dockerfile
FROM migrate/migrate

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.9.0/wait /wait
RUN chmod +x /wait

COPY entrypoint.sh /usr/local/bin
RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT [ "entrypoint.sh" ]

migrate entrypoint.sh

migrate/entrypoint.sh
#!/bin/sh

/wait
/migrate \
  -path $MIGRATIONS_DIR \
  -database "mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DATABASE}" \
  $@

Point1: db service を待機

docker-compose upで全serviceを起動する場合、migration container は db(mysqld) の起動が完了し、tcp接続ができるようになるまで待機する必要があります。
docker-compose service 間の依存は dependes_on で宣言的に定義できるものの、あくまで container の起動順序が制御されるだけであって、依存先の container が ready になるまで依存元の container の起動を待機する訳ではありません。

なので、 docker-compose-wait を利用し、特定の service が ready 状態になるまで待機するようにします。

migrate/Dockerfile
~

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.9.0/wait /wait
RUN chmod +x /wait

~

Point2: Docker entrypoint

golang migrate の official docker image では、 ENTRYPOINT が定義されているので、上記の docker-compose-wait を利用するべく ENTRYPOINT を上書く必要があります。

https://github.com/golang-migrate/migrate/blob/master/Dockerfile

ENTRYPOINT /wait && migrate

ただし、上記のように定義すると、走るプロセスは /bin/sh となり、CMDmigrate の引数として渡すことができません。

docker inspect 89ab8ebc951e (container id/name)

[
    {
        "Id": "89ab8ebc951e3ee27a655960762227895d528173dcd7ba3dafc65c391ce5abee",
        "Created": "2021-12-29T09:57:56.691705503Z",
        "Path": "/bin/sh",
        "Args": [
            "-c",
            "/wait \u0026\u0026 migrate",
            "up"
        ],
...

なので、ENTRYPOINT を shell script file とし、CMDをその引数として実行するようにします。

COPY entrypoint.sh /usr/local/bin
RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT [ "entrypoint.sh" ]
migrate/entrypoint.sh
#!/bin/sh

/wait
/migrate \
  -path $MIGRATIONS_DIR \
  -database "mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DATABASE}" \
  $@

結果↓

docker inspect 127b9306a3da

[
    {
        "Id": "58b4b845757725c2623dc8df99d04f41c8e3aff9626f0ee26f1ac74bc7be77f8",
        "Created": "2021-12-29T10:16:34.032928673Z",
        "Path": "entrypoint.sh",
        "Args": [
            "up"
        ],
...

これで、docker-compose run の引数で migration を行うことができるようになりました。

How to use

DB service 起動時に migration まで行う

docker-compose up

起動中の DB service に対し、追加で migration を行う

e.g.

docker-compose run migrate down 1

migration は行わないで DB の起動だけしたい

docker-compose up db

Discussion