MongoDB のレプリケーションを Docker をつかって体験する

# はじめに

MongoDB をつかったことがなかったのですが、触ってみてレプリケーションに出会いました。
レプリケーションは **複数の mongo** を動かす必要があるので、  **Docker をつかってローカルで、レプリケーションの動きを体験** してみました。

3つのコンテナを用意して、以下のような役割を与えます。

- Primary
- Secondary
- Arbiter

<img width="703" alt="スクリーンショット 2018-03-06 11.25.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/45822/467f6919-3fe1-3e3a-e41f-55999315b384.png">

これで、Primary の mongo を止めて、Secondary が Primary に昇格するのを眺めたいと思います。

**対象読者**

- MongoDB つかったことない
- MongoDB のレプリケーション知らない
- レプリケーションの概念はなんとなくわかる
- Docker は使ったことある
- 僕

**バージョン**

- centos6
- mongo3.6


# 流れ

- コンテナを 3 つ用意する
- 各コンテナの間で通信できる IPAddress を確認
- 各コンテナで mongo を起動する
- Primary, Secondary, Arbiter の設定をする
- 動作を確認する

# Step1 コンテナを 3 つ用意する

Docker Hub にイメージを用意しましたので、それを使います。

https://hub.docker.com/r/mochizukikotaro/centos6-mongo3.6/

ターミナルを 3 つ開きます。そしてそれぞれ以下を叩きます。
３つかきますが、それぞれコンテナ名が `mongo1` `mongo2` `mongo3` としているだけです。

```console:terminal_1
$ docker run -it --name mongo1 mochizukikotaro/centos6-mongo3.6:0.0.1 bash
```

```console:terminal_2
$ docker run -it --name mongo2 mochizukikotaro/centos6-mongo3.6:0.0.1 bash
```

```console:terminal_3
$ docker run -it --name mongo3 mochizukikotaro/centos6-mongo3.6:0.0.1 bash
```

**image (mochizukikotaro/centos6-mongo3.6/) の説明**

centos6 をベースにして、mongo3.6を入れたイメージになっています。 `service mongod start` とかが打てる状態になっています。
また、 `/etc/mongod.conf` と `/etc/yum.repos.d/mongodb-org-3.6.repo` を置いてあります。

詳細はこちらをご覧ください。
https://hub.docker.com/r/mochizukikotaro/centos6-mongo3.6/

# Step2 各コンテナの間で通信できる IPAddress を確認

```console
// プロセスを確認
$ docker ps --format "{{.Image}}\t{{.Names}}"
mochizukikotaro/centos6-mongo3.6:0.0.1	mongo3
mochizukikotaro/centos6-mongo3.6:0.0.1	mongo2
mochizukikotaro/centos6-mongo3.6:0.0.1	mongo1

// IPAddress を確認
$ dk inspect -f '{{.NetworkSettings.IPAddress}}' mongo1
172.17.0.2
```

上記のように IPAddress を確認できます。結果は以下の通りです。これをあとでレプリカセットを設定するときに使います。

|コンテナ名|IP|
|-------|---|
|mongo1|172.17.0.2|
|mongo2|172.17.0.3|
|mongo3|172.17.0.4|


# Step3 各コンテナで mongo を起動する

```console:各コンテナ内
# service mongod status <- 確認したければ
# service mongod start
```

ただ、start するだけですね。

# Step3 Primary, Secondary, Arbiter の設定をする

ここが本題ですね。基本的に terminal_1 で作業をします。

まずは `rs.status()` でレプリカセットのステータスを見ます。

```console:terminal_1
# mongo
> rs.status()
{
	"info" : "run rs.initiate(...) if not yet done for the set",
	"ok" : 0,
	"errmsg" : "no replset config has been received",
	"code" : 94,
	"codeName" : "NotYetInitialized"
}
```

まだセットしてないなら、 `rs.initiate(...)` するようにとあります。

なので、早速したいと思います。

### まずは Primary をつくる

ちなみに、 `/etc/mongod.conf` には以下の設定をしています。

```conf:/etc/mongod.conf_抜粋
replication:
  replSetName: "testRepl"
```

ですので、mongo 内で以下を打ちます。

```js:terminal_1
rs.initiate(
   {
      _id: "testRepl",
      members: [
         { _id: 0, host : "172.17.0.2:27017" },
      ]
   }
)
```

IPAddress は Step2 で取得した mongo1 コンテナの IPAddress です。
上をうつと下のように帰ってきて、無事成功しました。

```console:terminal_1
{
	"ok" : 1,
	"operationTime" : Timestamp(1520298587, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1520298587, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
```

すると以下のように `rs.status()` が変わります。

```console:terminal_1
testRepl:PRIMARY> rs.status()
{
	"set" : "testRepl",
	"date" : ISODate("2018-03-06T01:13:23.932Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1520298799, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1520298799, 1),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1520298799, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1520298799, 1),
			"t" : NumberLong(1)
		}
	},
	"members" : [
		{
			"_id" : 0,
			"name" : "172.17.0.2:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1643,
			"optime" : {
				"ts" : Timestamp(1520298799, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2018-03-06T01:13:19Z"),
			"electionTime" : Timestamp(1520298587, 2),
			"electionDate" : ISODate("2018-03-06T01:09:47Z"),
			"configVersion" : 1,
			"self" : true
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1520298799, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1520298799, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
```

これで、mongo1 が Primary になりました。

### つづいて Secondary をつくる

mongo2 を Secondary にします。 `rs.add()` を使います。
mongo2 の IP が `172.17.0.3` なので、以下のようになります。

```console:terminal_1
testRepl:PRIMARY> rs.add({_id: 1, host: "172.17.0.3:27017"})
{
	"ok" : 1,
	"operationTime" : Timestamp(1520298995, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1520298995, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
```

これで、 terminal_2 を確認しますと、以下のように Secandary として入力を待っている感じです。

```console:terminal_2
testRepl:SECONDARY>
```

### 最後に Arbiter をつくる

mongo3 の IP は `172.17.0.4` なので以下のようにします。

```console:terminal_1
testRepl:PRIMARY> rs.addArb("172.17.0.4:27017")
{
	"ok" : 1,
	"operationTime" : Timestamp(1520299760, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1520299760, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
```

mongo3 を確認すると、無事に Arbiter となっています。

```console:terminal_3
testRepl:ARBITER>
```

# 動作を確認する

まずは Primary で適当にデータを入れます。

```console:terminal_1
testRepl:PRIMARY> use study
switched to db study
testRepl:PRIMARY> db.user.insert({name: 'taro'})
WriteResult({ "nInserted" : 1 })
testRepl:PRIMARY> db.user.find()
{ "_id" : ObjectId("5a9def656b45e5c362a6bf4d"), "name" : "taro" }
```

それを Secondary で確認します。

```console:terminal_2
testRepl:SECONDARY> rs.slaveOk()
testRepl:SECONDARY> use study
switched to db study
testRepl:SECONDARY> db.user.find()
{ "_id" : ObjectId("5a9def656b45e5c362a6bf4d"), "name" : "taro" }
```

つぎに、Primary である mongo1 を止めて見ます。以下のように切り替わることがわかりました。

![mongo.gif](https://qiita-image-store.s3.amazonaws.com/0/45822/399eedbc-4220-c596-a653-4b5146dd8537.gif)

また、 Primary で `rs.stepDown()` を打つことで、自身を Secondary に落として、他方を Primary にあげることもできます。

以上です。


# 補足

### いろいろなかったことにしたいとき

mongod.conf の replication をコメントアウト

```
show databases
use local
db.dropDatabase()
```

コメントインして、再起動。

### rs.initiate() をなかったことにしたいとき

```
use local
db.system.replset.remove({})
```