過去にNode.jsとMongoDBを組み合わせたアプリ開発は経験済みでしたが、Docker上で稼働させようと、最新バージョンで試みたところ、いろいろハマったので、備忘録を残しておく。
組み合わせ
サーバー | バージョン |
---|---|
Docker | 19.03.8 |
Node.js | 12.16.3 |
MongoDB | 4.2.5 |
Node.jsモジュール | バージョン |
---|---|
express | 4.17.1 |
mongodb | 3.5.7 |
docker-composeの構成
フォルダ構成
root_folder
├ src (node.jsプログラムフォルダ)
│ ├ package.json
│ ├ test-mongo.js
│ └ node_modules
├ data
│ └ db (mongodbのデータベースファイル)
├ docker-compose.yml
└ .env
docker-compose.ymlファイル
node.jsとmongodbがそれぞれ別のコンテナで構成されている。外部から8080ポートでNode.jsのアプリケーションサーバーに接続する。MongoDBには直接接続しないので、Portの設定はコメントされている。
dbフォルダをMongoDBのコンテナにマウントして永続データはココに出力される。またsrcフォルダをNode.jsコンテナにマウントして、ホスト側で編集してコンテナ内で実行できるようになっている。
Node.jsからMongoDBにアクセスできるように、専用の仮想ネットワークapp-network
でつながるようにしている。
あと、ホスト側のpasswd, groupファイルをNode.jsコンテナに読取専用でマウントしており、外部のLinuxユーザhoge
をコンテナ内で使えるようにしている。これで内外のユーザで作成したファイルに対してのアクセスでPermisson denied
が出ないようにしている。
version: '3'
services:
app:
image: node:12.16.3-stretch-slim
container_name: container1
#network_mode: "host"
ports:
- 8080:8080
restart: always
working_dir: /home/app
tty: true
user: "${UID}:${GID}"
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- ./user:/home/${APP_USER}
- ./src:/home/app
- ./data:/home/data
command: bash
#command: node index.js
networks:
- app-network
depends_on:
- mongo
mongo:
image: mongo:4.2.5-bionic
container_name: container2
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
#ports:
# - 27099:27017
volumes:
- ./data/db:/data/db
command:
- mongod
networks:
- app-network
networks:
app-network:
external: true
.envファイル
docker-compose.ymlと同じ場所に同名ファイルを置くと、docker-compose.ymlとコンテナ内で使える環境変数になる。実行するホストで内容が変わるようなものを設定しておく。
UID=1000
GID=1000
APP_USER=hoge
MONGO_INITDB_ROOT_USERNAME=test
MONGO_INITDB_ROOT_PASSWORD=pass
MONGO_INITDB_DATABASE=example
test-mongo.js
const mongodb = require('mongodb')
const MongoClient = mongodb.MongoClient
/*
MongoDBにtest/passでログインして、exampleデータベースに接続する
personsコレクションに対して、ユーザを1件追加して、
そのコレクションを全件検索する
*/
console.log("---- test connect mongodb server ---")
var url = 'mongodb://test:pass@mongo:27017/'
var db_name = 'example'
var collection_name = 'persons'
console.log(url)
console.log(db_name)
console.log(collection_name)
const option = {
useNewUrlParser: true,
useUnifiedTopology: true,
}
MongoClient.connect(url, option, (err, client) => {
if( err != null || client == null ){
console.log(" !! failed to connect mongo db server !! ")
console.log(err)
}else{
console.log(" @@ Connected successfully to server @@ ")
const db = client.db(db_name)
var rec = {"name":"hanako", "age":12}
db.collection(collection_name).insertOne(rec, (err, res)=>{
if( err != null ){
console.log("err: insert")
console.log(err)
client.close()
}else{
console.log("succeeded: insert")
db.collection(collection_name).find({}).toArray( (err, result)=>{
if(err != null){
console.log("err: select")
console.log(err)
client.close()
}else{
console.log("succeeded: select")
console.log(result)
client.close()
}
})
}
})
}
})
console.log("-- execute end --")
ハマったところ
docker-composeで「network undefined」と言われた
事前にdocker networkを作成しておく
> docker network create app-network
あと、以下の設定を追加(まだこのあたり理解不足。。)
networks:
app-network:
external: true
Node.jsからMongoDBに繋がらなかった。
複数のコンテナが同一ネットワークにつながる状態なると、サービス名が仮想内のネットワーク名になるので、プログラム内のURLでは
var url = 'mongodb://test:pass@mongo:27017/'
のようにmongo
という名前でアクセスできる。localhost
とか127.0.0.1
では繋がらない。
MongoDBのアクセス権設定でおかしかった
デフォルトでは、ユーザ無しにだれでもアクセスできる状態で起動される。docker版のMongoDBは、以下の環境変数を設定した状態でコンテナ初回起動すると、endpointのスクリプトでユーザ登録を自動でやってくれる。
MONGO_INITDB_ROOT_USERNAME=test
MONGO_INITDB_ROOT_PASSWORD=pass
MONGO_INITDB_DATABASE=example
ところが、当初はMONGO_INITDB_DATABASE
の指定をしてなくて、中途半端?な状態でサーバーが起動して、ログインできない問題が発生していた。MONGO_INITDB_DATABASE
はプログラムからアクセスするDB名を指定する必要がある。
また、初回をユーザ設定なしに起動(つまり初期DBが生成)された後で、環境変数を設定してコンテナを起動しても、設定したユーザでアクセスできない。どうしても設定したい場合は、いったんダンプバックアップして、data/db
フォルダを丸ごと削除してDBを最初から作成しなおすしか方法がわからなかった。
一番のハマりポイントかもしれない。
正しく設定されたDBが作成されれば、
> mongo -u test
``
や
```javascript
var url = 'mongodb://test:pass@mongo:27017/'
でアクセスできる。
以下に初回コンテナ実行後にDBパスワードを設定する方法が記載
Docker公式MongoDBにパスワードを設定する方法
https://blog.bagooon.com/?p=1670
MongoDB ユーザー認証設定は必ずしましょう
https://qiita.com/h6591/items/68a1ec445391be451d0d
Node.jsのMongoクライアントがエラーになる
最新のクライアントモジュールの仕様が変わっている模様。
まずは、connect
メソッドが失敗する。これはコンソールに警告が出るのでわかりやすい。以下のoptionを接続時に追加で指定する。(古いAPIがDeprecatedになっている)
const option = {
useNewUrlParser: true,
useUnifiedTopology: true,
}
MongoClient.connect(url, option, (err, client) => {
})
「not found collection」云々言われた。
これもAPIの仕様が変わっている。URLで指定しているにもかかわらず、DBオブジェクトを取得するコードを追加する必要がある。最終行のclient.db(db_name)
である
var db_name = "example"
MongoClient.connect(url, option, (err, client) => {
if( err != null || client == null ){
console.log(" !! failed to connect mongo db server !! ")
console.log(err)
}else{
console.log(" @@ Connected successfully to server @@ ")
const db = client.db(db_name)
初歩的なミスでしたが、半日ほど時間を費やしました。
Dockerのネットワーク周りはまだ理解が乏しいので、押さえておく場所はまだたくさんある。