🍃

Dockerコンテナ上でgolang-migrateを使ってMongoDBのマイグレーション

2022/10/18に公開

はじめに

MongoDB のマイグレーション、どうするのが良いかなーと思って調査してみました。
検索してると、Go で書かれたgolang-migrate/migrateがシンプルで良さそうだったので、試すことにしました。
気に入ったポイントは、ここらです。

  1. 対応してる DB が多い。(PostgreSQL にも MongoDB に対応してた)
  2. 機能がシンプルそう。
  3. 情報と Star が多い。(2022/10/17 時点で 9.9k)
  4. Go で書かれてる(中身を読みたい訳ではないけども、気に入ってる言語で書かれてる方が気分が上がる。)

PostgreSQL と MongoDB で試してみたんですが、MongoDB の情報があまり見つからなかったので、MongoDB 中心に記載します。
特にdocker-compose.ymldocker runを使って Docker コンテナ上で動作確認する情報が見当たらなかったので、そこら辺を中心に記載します。
golang-migrate をコンテナでやりたいんだ!って人の参考になればと思います。

ちなみに調査の初めは、開発に使ってる言語に対応してるツールじゃないといけないかなーと思ってたけど、ORM (Object-Relational Mapping) 使わなかったら、そんな制約ないのかと気づいて、golang-migrate 試すことにしました。

以下では、MongoDB での環境構築・ローカルでの動作確認、コンテナでの動作確認、最後に PostgreSQL での動作確認の順で説明します。

コードはoptimisuke/hello-golang-migrateにおいてます。

MongoDB

環境構築

まず、docker compose を使って、MongoDB と PostgreSQL のコンテナを作ります。

ローカルからアクセスできるように、ポートの設定もしてます。
後半で、公式のコンテナ(migrate/migrate)を立ち上げてマイグレーションする方法も書いてますが、その場合、ポートの設定は不要です。

使ってるイメージは、MongDB の公式のやつを使ってます。

docker-compose.yml
version: "3"
services:
  mongo:
    image: mongo
    ports:
      - 27017:27017
    networks:
      - network
    volumes:
      - mongo_db_data:/data/db
      - ./initdb.d:/docker-entrypoint-initdb.d/
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
  postgres:
    image: postgres:14.1-alpine
    ports:
      - 5432:5432
    networks:
      - network
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin-password
      LANG: ja_JP.utf8
      TZ: Asia/Tokyo
volumes:
  postgres_data:
  mongo_db_data:
networks:
  network:

加えて、事前準備として、認証の設定するために、少し真面目にユーザーを設定しています。
下記ファイルをマウントすることで、コンテナ立ち上げ時にユーザーを作成してくれます。

initdb.d/init.js
db = db.getSiblingDB("admin");
db.auth("root", "password");
db.createUser({
  user: "testuser",
  pwd: "password",
  roles: [
    {
      role: "readWrite",
      db: "hoge",
    },
  ],
});

これで、動くはずですが、MongoDB のこともうちょい知りたい人は、以前書いた記事(はじめての MongoDB with Docker)を読んでもらえると嬉しいです。

加えて、動作確認のためにローカルでmongoshを使ったので、下記を参照して MongoDB をインストールしました。
Install MongoDB Community Edition on macOS — MongoDB Manual

また、golang-migrate/migrateは MacOS を使ってるので、下記コマンドで入れました。

brew install golang-migrate

コンテナのみ使うのであれば、上記不要ですが、いきなりコンテナで確認するには不安があったので入れて確認しました。
他の OS の場合は、下記ドキュメント参照です。
migrate/cmd/migrate at master · golang-migrate/migrate

ローカルでの動作確認

PostgreSQL はチュートリアル(migrate/TUTORIAL.md)がありますが、MongoDB はありません。(README (migrate/database/mongodb)のみ。PR しよかな。)
なので、色々調べながら、試してみました。
基本的には、PostgreSQL と同じなのですが、権限まわりでつまづいてました。

まず、下記コマンドでマイグレーション用の up と down のファイルを作ります。

migrate create -ext json -dir mongo/migrations -seq create_hoge

その後、中身を記載します。

ここらの記事を参考にしました。

以下に、コレクション&ドキュメントの追加とコレクションの削除するファイルを記載します。

mongo/migrations/000001_create_hoge.up.json
[
    {
        "insert": "hoge",
        "documents": [
            {
                "column1": "test1",
                "column2": "test2"
            }
        ]
    }
]
mongo/migrations/000001_create_hoge.down.json
[
  {
    "drop": "hoge"
  }
]

上記参照先に、他にもインデックスを追加したりする方法が書かれてます。

次に、以下のコマンドでマイグレーションを実施します。データベースへの接続情報とファイルのパスとサブコマンドの up/down を書いてます。

migrate -database "mongodb://testuser:password@localhost:27017/hoge?authSource=admin" -path ./mongo/migrations up
migrate -database "mongodb://testuser:password@localhost:27017/hoge?authSource=admin" -path ./mongo/migrations down

コネクションの URI は下記参照です。上記コマンドでは、ユーザー情報のあるデータベースを指定するauthSourceしか設定してませんが、他にも色々あるみたいです。ユーザーは、initdb.d/init.jsに書いたように、testuserを指定しています。
Connection String URI Format — MongoDB Manual

コンテナでの動作確認

公式のコンテナ(migrate/migrate)を使ってマイグレーションする方法も記載します。
上記イメージは、migrate/Dockerfileで作ったもののようです。
インストールするだけでなくENTRYPOINTmigrateコマンドを実行してるところが特徴的かなと思います。

コンテナを使って、コマンドを実行する方法は、公式の README.md のここにも書いてました。

ちなみに、最初 docker-compose.yml でコンテナを立ち上げっぱなしにして、マイグレーションしたい時にコマンド実行する感じにしようかなと思いましたが、ENTRYPOINTmigrateが設定されているので、コマンド実行したら止める系なのかなと思い、やめました。
今回は、docker runでマイグレーションを実行してみましたが、下記サイトにあるようにdocker-compose.ymlに書いて、docker compose upしたときに Migration するようにしておくのもありだと思います。
database - How to run golang-migrate with docker-compose? - Stack Overflow

docker runを使ったマイグレーションコマンドはこんな感じになります。
事前にネットワークの設定をdocker network lsで確認しておく必要があります。

docker run -v $(pwd)/mongo/migrations:/migrations --network=hello-golang-migrate_network migrate/migrate -path=/migrations/ -database "mongodb://testuser:password@mongo:27017/hoge?authSource=admin" up

以下のように、DockerfileENTRYPOINTで、migrateコマンドが指定してあるので、それが実行されオプションの--help部分を上書きしてる感じです。

ENTRYPOINT ["migrate"]
CMD ["--help"]

ついでに、Makefile はこんな感じかなと思います。もうちょい、変数を使った方が良いと思いますが。

up:
	docker run -v ${PWD}/mongo/migrations:/migrations --network=hello-golang-migrate_network migrate/migrate -path=/migrations/ -database "mongodb://testuser:password@mongo:27017/hoge?authSource=admin" up
down:
	docker run -v ${PWD}/mongo/migrations:/migrations --network=hello-golang-migrate_network migrate/migrate -path=/migrations/ -database "mongodb://testuser:password@mongo:27017/hoge?authSource=admin" down -all

最後の-allは、途中でインタラクティブに聞いてくる質問を全て yes で進めるためのオプションです。
Add an option to assume "yes" to all prompts · Issue #370 · golang-migrate/migrate

PostgreSQL

PostgreSQL の場合は、チュートリアルが割としっかり書かれてるので、下記ドキュメントを参照するのが良いと思いますが、参考までに記載します。
migrate/TUTORIAL.md at master · golang-migrate/migrate

こちらも、シンプルに書かれてて参考になりました。
golang-migrate での PostgreSQL マイグレーション備忘録

最初に MongoDB と同じように、下記コマンドでファイルを作成します。

migrate create -ext sql -dir postgres/migrations -seq create_bookmarks_table

そのあと、下記コマンドでマイグレーションを実施します。

export POSTGRESQL_URL=postgres://admin:admin-password@localhost:5432/admin?sslmode=disable
migrate -database ${POSTGRESQL_URL} -path postgres/migrations up

docker run使う場合は MongoDB と同じように下記コマンドで実行できます。

docker run -v $(pwd)/postgres/migrations:/migrations --network=hello-golang-migrate_network migrate/migrate -path=/migrations/ -database "postgres://admin:admin-password@postgres:5432/admin?sslmode=disable" up
docker run -v $(pwd)/postgres/migrations:/migrations --network=hello-golang-migrate_network migrate/migrate -path=/migrations/ -database "postgres://admin:admin-password@postgres:5432/admin?sslmode=disable" down -all

おわりに

MongoDB 使うとき、マイグレーションツールって本当に必要なのか?問題もあるのかなと思います。マイグレーション使わなくてもコレクションを生成できたり、JavaScript で操作できたりするので。
ただ、インデックスとかちょっとしたデータを入れたりを宣言型っぽく管理したいユースケースもあるのかなと思って試してみました。(リレーショナルデータベースの考え方に縛られてるだけなのかもしれませんが。)

使い勝手は、シンプルでとても良い感じでした。
ORM を使ってなくて、RDB と NoSQL のマイグレーションを統一したい等の要件があれば、golang-migrate/migrateは最適な気がします。

実際使う時には、以下の項目をもう少しケアする必要がありそうなので、そのうちまた試してみたいなと思います。

  • 設定ファイルとか Makefile にがっつりパスワードを書いちゃってるので、環境変数から取得する
  • Kubernetes とかで運用するときに、Migration 用のコンテナを立てる
  • Migration を CICD に組み込む
GitHubで編集を提案

Discussion