MongoDB Community Edition 3.6.2 - Installing on Windows 10

# 概要

Windows10に開発・検証用にMongoDB Community Edition 3.6.2をインストールし、初歩的な設定を行うまでの作業メモです。
インストーラ版は使わずZIP Archive版で手動インストールします。

**環境**

* Windows 10 Professional
* MongoDB Community Edition 3.6.2

**参考**

* [Install MongoDB Community Edition on Windows] (https://docs.mongodb.com/v3.6/tutorial/install-mongodb-on-windows/)

# MongoDB Community Editionのインストール

## アーカイブファイルのダウンロード

MongoDB Download Centerページの"All Version Binaries"というテキストリンクをクリックし、ダウンロードページから"mongodb-win32-x86_64-2008plus-ssl-3.6.2.zip"をダウンロードします。

## アーカイブファイルの展開

アーカイブファイルを適当な場所へ展開します。この記事では"D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2"に展開した前提で進めます。
展開ディレクトリにbinディレクトリがあるので環境変数pathに通しておきます。

次にdb、logディレクトリを作成します。

```text
D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2\db
D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2\log
```

また、展開ディレクトリ下に設定ファイルと起動用のバッチファイルを作成します。

```text
D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2\mongod.cfg
D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2\startup.bat
```

### 設定ファイル

* [MONGODB MANUAL | Reference > Configuration File Options] (https://docs.mongodb.com/v3.6/reference/configuration-options/)

```text:mongod.cfg
systemLog:
    destination: file
    path: "D:/dev/mongodb-win32-x86_64-2008plus-ssl-3.6.2/log/mongod.log"
    logAppend: true
storage:
    engine: wiredTiger
    dbPath: "D:/dev/mongodb-win32-x86_64-2008plus-ssl-3.6.2/db"
    journal:
        enabled: true
    directoryPerDB: true
    wiredTiger:
        engineConfig:
            cacheSizeGB: 0.5
            directoryForIndexes: true
net:
    bindIp: 127.0.0.1
    port: 27017
security:
    authorization: enabled
setParameter:
    enableLocalhostAuthBypass: true
    authenticationMechanisms: SCRAM-SHA-1
```

この設定は

* 最初から認証・認可を有効にしつつ
    * (security.authorization=true)
* 且つ、ローカルホストからの接続時には認証・認可をバイパスする
    * (setParameter.enableLocalhostAuthBypass=true)

というもので、ユーザー作成後にsetParameter.enableLocalhostAuthBypassを無効(false)に変更します。

### 起動用バッチファイル

```bat:startup.bat
D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2\bin\mongod.exe --config D:\dev\mongodb-win32-x86_64-2008plus-ssl-3.6.2\mongod.cfg
```

# 認証と認可

## 認証 (authentication)

* [Security > Authentication] (https://docs.mongodb.com/v3.6/core/authentication/)
* [Security > Authentication > Authentication Mechanisms] (https://docs.mongodb.com/v3.6/core/authentication-mechanisms/)

ここではユーザー・パスワードと認証データベースによる認証(SCRAM-SHA-1)について記述します。（認証メカニズムにはSCRAM-SHA-1、MONGODB-CR、x.509などがあるようです）

mongo shellからの認証メソッドは2種類あります。
1つはmongo shellのオプションにユーザー／パスワード、認証データベースを指定する方法です。

```bat
> mongo.exe -u "ユーザー" -p "パスワード" --authenticationDatabase "認証データベース"
```

もう1つの方法はユーザー／パスワードを指定せずにMongoDBインスタンスにアクセスし

```bat
> mongo.exe
```

認証データベースにスイッチしてauthメソッドを実行する方法です。

```js
> use "認証データベース"
switched to db "認証データベース"

> db.auth("ユーザー", "パスワード")
1
```

## 認可 (authorization)

* [Security > Role-Based Access Control] (https://docs.mongodb.com/v3.4/core/authorization/)
* [Security > Enable Auth] (https://docs.mongodb.com/v3.6/tutorial/enable-authentication/)

認証を有効にすると同時に認可も有効になります。
MongoDBはRole-Based Access Control (RBAC)というアクセス制御を採用しており、ユーザーには1つ以上のロールが付与されます。

認証・認可を有効にする前にadminデータベースにuserAdminかuserAdminAnyDatabaseロールを持つ管理者ユーザーを作成しておく必要があります。
この管理者ユーザーでユーザーの作成、ロールの付与などユーザーとロールの管理を行います。


# ユーザーの作成

* [MONGODB MANUAL | Security > Security Reference > Built-In Roles] (https://docs.mongodb.com/v3.6/reference/built-in-roles/)
* [MONGODB MANUAL | Security > Security Reference > Privilege Actions] (https://docs.mongodb.com/v3.6/reference/privilege-actions/)

## MongoDBインスタンスの起動

起動用バッチを実行してインスタンスを起動します。

## 管理者用ユーザー

オプション無しでmongo shellを起動しインスタンスに接続します。

```bat
> mongo.exe
MongoDB shell version v3.6.2
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.2
```

### rootユーザー

rootロールを持つユーザーを作成します。
（基本的に使うことはないので場合によっては作らずDBAユーザーだけでもいいかもしれません。）

```js
> use admin
switched to db admin

> db.createUser( {user:"admin", pwd:"adminpass", roles:[{role:"root", db:"admin"}]} )
Successfully added user: {
        "user" : "admin",
        "roles" : [
                {
                        "role" : "root",
                        "db" : "admin"
                }
        ]
}
```

作成したら認証します。

```
> db.auth("admin", "adminpass")
1
```

DBAユーザーでもshutdownが実行できるようにカスタムロールを作成します。

```js
> db.createRole( { role: "serverManageRole", privileges: [{ resource: { cluster: true }, actions: [ "serverStatus", "top", "shutdown" ] }], roles: [] } )
{
        "role" : "serverManageRole",
        "privileges" : [
                {
                        "resource" : {
                                "cluster" : true
                        },
                        "actions" : [
                                "serverStatus",
                                "top",
                                "shutdown"
                        ]
                }
        ],
        "roles" : [ ]
}
```

このロールには次の3つのアクションを付与しています。
serverStatusアクションはmongostat.exe、topアクションはmongotop.exeの実行に必要で、これらのツールを使わなければ不要です。

* serverStatus
    * User can perform the serverStatus command. Apply this action to the cluster resource.
* top
    * User can perform the top command. Apply this action to the cluster resource.
* shutdown
    * User can perform the shutdown command. Apply this action to the cluster resource.

[--shutdown] (https://docs.mongodb.com/v3.6/reference/program/mongod/#cmdoption-mongod-shutdown)

mongodの`--shutdown`オプションを使う方法もあるようですが、いまのところlinuxでしか利用できないので確認していません。

> The --shutdown option is available only on Linux systems.


### DBAユーザー

続いてDBAユーザーを作成します。管理作業はこのユーザーで行います。

```js
> db.createUser( {user:"scott", pwd:"tiger", roles:[{role:"dbAdminAnyDatabase", db:"admin"}, {role:"userAdminAnyDatabase", db:"admin"}]} )
Successfully added user: {
        "user" : "scott",
        "roles" : [
                {
                        "role" : "dbAdminAnyDatabase",
                        "db" : "admin"
                },
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}

> db.grantRolesToUser( "scott", [{role:"serverManageRole", db:"admin"}] )
```

作成したユーザーの情報を確認します。

```js
> db.getUser("scott")
{
        "_id" : "admin.scott",
        "user" : "scott",
        "db" : "admin",
        "roles" : [
                {
                        "role" : "serverManageRole",
                        "db" : "admin"
                },
                {
                        "role" : "dbAdminAnyDatabase",
                        "db" : "admin"
                },
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}
```

### enableLocalhostAuthBypassの設定を変える

設定ファイルを変えるために一旦shutdownをします。

```js
> db.shutdownServer()
```

setParameter.enableLocalhostAuthBypassをfalseに変えてサーバーを再起動します。
これでローカルホストからの接続時に認証が必要になります。

```text
setParameter:
    enableLocalhostAuthBypass: false
```

DBAユーザーでログインします。

```bat
> mongo.exe -u scott -p tiger --authenticationDatabase admin
MongoDB shell version v3.6.2
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.2
```

## アプリケーション用アカウント

アプリケーションから接続するアカウントを作成します。
この例ではdemoデータベースを作成し、そのDBを利用するdemo_userを作成します。

```js
> use demo
switched to db demo

> db.createUser( {user:"demo_user", pwd:"demo_pass", roles:[{role:"readWrite", db:"demo"}]} )
Successfully added user: {
        "user" : "demo_user",
        "roles" : [
                {
                        "role" : "readWrite",
                        "db" : "demo"
                }
        ]
}
```

作成したアカウントで接続できるか、別のクライアントを立ち上げて確認します。

```bat
> mongo.exe -u demo_user -p demo_pass localhost/demo
MongoDB shell version v3.6.2
connecting to: mongodb://localhost:27017/demo
MongoDB server version: 3.6.2
```

DBへの読み書きができるか確認します。

```js
> db.createCollection("product")
{ "ok" : 1 }

> db.product.insert([
{no:1, name:"うまい棒", price:10, variety:[{flavor:"メンタイ", ppl:3},{flavor:"サラミ", ppl:3},{flavor:"ピザ", ppl:2},{flavor:"オニオンサラダ", ppl:2},{flavor:"コーンポタージュ", ppl:1}], snack:true, activate:true, update_dt:ISODate("2015-01-01T00:00:00+09:00") },
{no:2, name:"ポテトフライ", price:30, variety:[{flavor:"ステーキ", ppl:2},{flavor:"フライドチキン", ppl:1},{flavor:"カルビ", ppl:2}], snack:true, activate:false, update_dt:ISODate("2015-01-15T00:00:00+09:00") },
{no:3, name:"きなこ棒", price:10, activate:true, update_dt:ISODate("2015-01-31T00:00:00+09:00")},
{no:4, name:"生いきビール", price:40, drink:true, activate:false, update_dt:ISODate("2015-02-01T00:00:00+09:00")},
{no:5, name:"フルーツヨーグル", price:20, activate:true, update_dt:ISODate("2015-02-15T00:00:00+09:00")},
{no:6, name:"コーヒー牛乳キャンディ", price:10, activate:true, update_dt:ISODate("2015-02-28T00:00:00+09:00")},
{no:7, name:"ミニコーラ", price:30, activate:true, update_dt:ISODate("2015-03-01T00:00:00+09:00")},
{no:8, name:"フエラムネ", price:60, variety:[{flavor:"コーラ", ppl:3},{flavor:"いちご", ppl:1}], activate:true, update_dt:ISODate("2015-03-15T00:00:00+09:00")},
{no:9, name:"こざくら餅", price:20, variety:[{flavor:"グレープ", ppl:2},{flavor:"コーラ", ppl:3},{flavor:"フルーツソーダ", ppl:1},{flavor:"ミックス", ppl:2}], activate:true, update_dt:ISODate("2015-03-31T00:00:00+09:00")},
{no:10,name:"たまごアイス", price:10, variety:[{flavor:"ミルク", ppl:1},{flavor:"バニラ", ppl:3},{flavor:"チョコレート", ppl:2}], activate:true, update_dt:ISODate("2015-04-01T00:00:00+09:00")},
{no:11,name:"餅太郎", price:30, activate:true, update_dt:ISODate("2015-04-15T00:00:00+09:00")},
{no:12,name:"あんこ玉", price:10, activate:true, update_dt:ISODate("2015-04-30T00:00:00+09:00")},
{no:13,name:"ラムネ", price:30, drink:true, activate:true, update_dt:ISODate("2015-05-01T00:00:00+09:00")},
{no:14,name:"みかん水", price:40, drink:true, activate:true, update_dt:ISODate("2015-05-15T00:00:00+09:00")},
{no:15,name:"さくら大根", activate:false, update_dt:ISODate("2015-05-15T00:00:00+09:00"), desc:"価格不明"}
])
BulkWriteResult({
        "writeErrors" : [ ],
        "writeConcernErrors" : [ ],
        "nInserted" : 15,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : [ ]
})
```

登録したドキュメントを検索します。

```js
> db.product.find({price:10}, {_id:0, name:1, price:1})
{ "name" : "うまい棒", "price" : 10 }
{ "name" : "きなこ棒", "price" : 10 }
{ "name" : "コーヒー牛乳キャンディ", "price" : 10 }
{ "name" : "たまごアイス", "price" : 10 }
{ "name" : "あんこ玉", "price" : 10 }
```

# 補足

## 他のコンポーネント

### mongostat

```text:example
> mongostat.exe -u scott -p tiger --authenticationDatabase admin 5
insert query update delete getmore command dirty used flushes vsize  res qrw arw net_in net_out conn                time
    *0    *0     *0     *0       0     0|0  0.0% 0.0%       0  995M 131M 0|0 1|0    39b   13.3k    1 Feb 21 23:17:17.692
    *0    *0     *0     *0       0     0|0  0.0% 0.0%       0  995M 131M 0|0 1|0    31b   10.6k    1 Feb 21 23:17:22.692
    *0    *0     *0     *0       0     0|0  0.0% 0.0%       0  995M 131M 0|0 1|0    31b   10.6k    1 Feb 21 23:17:27.694
    *0    *0     *0     *0       0     1|0  0.0% 0.0%       0  995M 131M 0|0 1|0   142b   10.7k    1 Feb 21 23:17:32.710
    *0    *0     *0     *0       0     0|0  0.0% 0.0%       0  995M 131M 0|0 1|0    31b   10.7k    1 Feb 21 23:17:37.690
    *0    *0     *0     *0       0     0|0  0.0% 0.0%       1  995M 131M 0|0 1|0    34b   2.26k    1 Feb 21 23:18:01.545
    *0    *0     *0     *0       0     1|0  0.0% 0.0%       0  995M 131M 0|0 1|0   138b   46.4k    1 Feb 21 23:18:02.689
```

### mongotop

```text:example
> mongotop.exe /u scott /p tiger /authenticationDatabase:admin 5
2018-02-21T23:28:36.402+0900    connected to: 127.0.0.1

                    ns    total    read    write    2018-02-21T23:28:41+09:00
    admin.system.roles      0ms     0ms      0ms
    admin.system.users      0ms     0ms      0ms
  admin.system.version      0ms     0ms      0ms
config.system.sessions      0ms     0ms      0ms
          demo.product      0ms     0ms      0ms
     local.startup_log      0ms     0ms      0ms
  local.system.replset      0ms     0ms      0ms
```

### pymongo

pymongoの[mongo_client] (https://api.mongodb.com/python/current/api/pymongo/mongo_client.html)を使ったサンプルコードです。

```py
import random
import datetime
from pymongo import MongoClient
from urllib.parse import quote_plus
from pymongo.errors import ConnectionFailure

user = "demo_user"
pswd = "demo_pass"
uri = "mongodb://%s:%s@localhost/demo" % (quote_plus(user), quote_plus(pswd))
client = MongoClient(uri)

try:
    db = client.demo

    db.test.drop()

    now = datetime.datetime.now()
    for n in range(100):
        r = random.randint(n, n + 100)
        db.test.insert_one({'n': n, 'r': r, 'd': (now + datetime.timedelta(days=r)).strftime("%Y-%m-%d %H:%M:%S")})

    print(db.test.count())

except ConnectionFailure:
    print("connection error")

client.close()
```