やりたいこと
Dockerコンテナ(docker-compose)を使ってローカルで起動しているMongoDB+自分用Webアプリで、FirebaseのRealtime Databaseのようにデータ更新を同期的に画面に反映してくれる Change Streams を使いたい。
問題
MongoDBのChange Streamsを使うには、MongoDBをレプリケーションした状態で起動させる必要がある。しかし、docker-compseを利用したレプリケーションの設定方法については、Web検索でも一筋縄ではいかない情報が多かった。
以下にこの問題について議論されている。(2023/7時点でも未解決?)
https://github.com/docker-library/mongo/issues/339
解決策とその手順
ディレクトリ構造
まず、以下のディレクトリ構造を作成する。docker-compose.ymlファイルとMongoDBの初期化スクリプトを配置する。
.
├── app(Webアプリ)
├── docker
│ └── db
│ └── init
│ └── init.js
├── docker-compose.yml
└── README.md
docker-compose設定
次にdocker-compose.ymlファイルを作成する。このファイルではMongoDBのレプリケーション設定や、ヘルスチェック、ボリュームの設定を行う。
version: '3'
services:
#web:
#略
mongo:
image: mongo:6.0.6
environment:
- AUTH=no
command: [--replSet, my-replica-set, --noauth, --bind_ip_all]
ports:
- 27017:27017
healthcheck:
test: mongosh mongo-init.js
interval: 10s
start_period: 30s
volumes:
- mongodb_data:/data/db
- ./docker/db/init/db_init.js:/mongo-init.js
restart: always
mongo-express:
image: mongo-express
container_name: mongo_express
restart: always
ports:
- 8081:8081
environment:
#ME_CONFIG_MONGODB_URL: mongodb://@mongo:27017/
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: password
ME_CONFIG_MONGODB_SERVER: mongo
ME_CONFIG_MONGODB_PORT: 27017
depends_on:
- mongo
# mongodbのデータはdocker volumeで管理されるので、消すときは docker volume rm mongodb_data
volumes:
mongodb_data:
MongoDB初期化スクリプト
docker-compose.ymlで指定したパスに、MongoDBの初期化スクリプトを作成する。このスクリプトでは、レプリケーションの設定と管理ユーザーの作成を行う。
init = false;
print("Init script ...")
try {
if (!db.isMaster().ismaster) {
print("Error: primary not ready, initialize ...")
rs.initiate(
{
_id:'my-replica-set',
members: [
{ _id:0,
host: "mongo:27017"
}
]
}
)
quit(1);
} else {
if (!init) {
admin = db.getSiblingDB("admin");
admin.createUser(
{
user: "root",
pwd: "password",
roles: ["readWriteAnyDatabase"]
}
);
init = true;
}
}
} catch(e) {
rs.status().ok
}
これらの設定により、docker-composeを使ってMongoDBをレプリカセットで起動し、Change Streamsを使うことができるはずだ。
各コンテナの詳細
上記の設定で使用しているコンテナの詳細とその役割について説明する。
Web
自分のWebアプリを記述する部分。今回の記事の内容からは外れる内容なので省略している。
MongoDB
使用するMongoDBのバージョンは6.0.6で、レプリケーションを利用するために --replSet
オプションをつけて起動する。ローカルでの開発用なので最小構成としてプライマリーノードを1つだけ起動する。
--bind_ip_all
はローカルでの開発用として外部からの接続を許可するためにつけている。
初期設定スクリプト(init.js)では管理者ユーザーの作成を行う。管理者ユーザーはGUIでMongoDBの操作を行えるようにmongo-expressから参照できるように作成する。
起動に使うスクリプトは volumes
にて - ./docker/db/init/db_init.js:/mongo-init.js
として指定している。
volumes
をdocker volumeにしている理由は、パーミッションの問題でMongoDBを起動できなかったため。
healthcheck
で起動している db-init.js
こそ今回の肝。
上述したとおりレプリカセットでMongoDBのコンテナイメージを実行すると、docker-entrypoint-initdb.dがうまく動作しない問題(仕様)のせいでユーザーの作成やレプリケーションの初期設定をdocker-entrypoint-initdb.dでは実装できない。
そこでハック的に healthcheck
で無理やりこれらの作業を実施しているというわけである。
また、初回のコンテナ起動時はレプリケーションの初期化が完了していないためエラーで落ちるので、restart: always
が必要。
mongo-express
mongo-expressはMongoDBのGUIクライアントで、MongoDBの操作をGUIで行いたい場合に利用する。環境変数を用いてMongoDBへの接続設定を行う。
これのおかげでデータの操作(追加・削除・変更)がGUIからできるのでとても便利。
参考資料
- https://stackoverflow.com/questions/76013265/how-to-do-a-mongodb-6-single-node-replicaset-with-docker-compose
- https://n-laboratory.jp/articles/mongodb-replicaset-docker
- https://github.com/docker-library/mongo/issues/339
まとめ
ローカルでMongoDBを使ってレプリケーションを試すくらいであればシングルノードでも問題ないはず。公式のどきゅめんとにもあるように本番では3台以上構成で起動することを推奨する。
関連記事: https://gihyo.jp/dev/serial/01/mongodb/0004
なお、実はここまで苦労せずともローカルから MongoDB Atlas Database に繋いでしまえばすぐにでもレプリケーションされたMongoDBを利用することができる。
しかし、開発に使う場合はローカル環境を使いたいんやで……という場合もあると思うので、参考にしてもらえると幸いである。