聖なる夜にMongodbDBと戯れるwith docker-compose

## 経緯

普段railsでmongodbを使うことがあるのですが、sharding周りを全くわかっとらんので、
docker-composeを利用して検証。
現状の運用上の問題解決ができるか調査してみた。
（間違ったことを書いていることもあると思うので、編集リクエストや指摘してもらえると嬉しいです。）


## 現状の構成と課題

### 構成
mongodbに限らずshardingした時の運用は辛いということで、
現状shardingは利用せず、レプリカセットだけ組んで利用しています。
railsのodmとして有名なmongoidを利用しているのですが、接続情報がこんな感じで記述します。

```yml:config/mongoid.yml
production:
  clients:
    default:
      database: mongodb_database
      hosts:
        - primary:27017
        - secondary1:27017
        - secondary2:27017
```

![image](https://qiita-image-store.s3.amazonaws.com/0/31015/14606c0b-1989-7e5f-0655-ed19337f0698.png)

### 課題
スケールアップや、AWSの基盤メンテにぶち当たった時にレプリカセットのメンバーの入れ替えを行うのですが、
入れ替え作業を行う時などに接続情報を更新してアプリケーションのデプロイが必要になりめんどくさいし、ミスりそう。

### 解決策
shardingを1系統だけ利用して、mongodの接続をmongos経由の接続にすることによって、dbの接続先の情報などをアプリケーションに持たなくてよくする。

```yml:config/mongoid.yml
production:
  clients:
    default:
      database: mongodb_database
      hosts:
        - localhost:27017
```
(図が全部primaryになってておかしいですが、後で差し替えときます:bow:)
![IMG_4668.JPG](https://qiita-image-store.s3.amazonaws.com/0/31015/34a63c0e-799c-cd40-443a-13ef758840f4.jpeg)

mongosはmonitなどで監視再起動すればいいかと考える


## 検証環境
mac
docker-for-mac
railsアプリはhostマシンで動作し、ミドルウェアはdocker上で動かしています。


### ディレクトリ構成

```
mongodb
└── docker-compose.yml
```

mongodb以外のミドルウェアはrailsのappの方のdocker-composeに記述しています。
今回は検証用にrails app のdocker-composeのmongodbの記述をコメントアウトし、
別のディレクトリを切ってmongodbだけ別途検証できるようにしています。


```yaml:docker-compose.yml
version: '2'
services:
  s1:
    image: mongo:3.2
    ports:
      - "27017:27017"
    command: mongos --configdb confRep1/c1:27019,c2:27019,c3:27019
    links:
      - d1
      - d2
      - d3
      - d4
      - d5
      - d6
      - c1
      - c2
      - c3
  s2:
    image: mongo:3.2
    ports:
      - "27018:27017"
    command: mongos --configdb confRep1/c1:27019,c2:27019,c3:27019
    links:
      - d1
      - d2
      - d3
      - d4
      - d5
      - d6
      - c1
      - c2
      - c3
  c1:
    image: mongo:3.2
    command: mongod --configsvr --replSet confRep1
    links:
      - d1
      - d2
      - d3
      - d4
      - d5
      - d6
      - c2
      - c3
  c2:
    image: mongo:3.2
    command: mongod --configsvr --replSet confRep1
    links:
      - d1
      - d2
      - d3
      - d4
      - d5
      - d6
      - c3
  c3:
    image: mongo:3.2
    command: mongod --configsvr --replSet confRep1
    links:
      - d1
      - d2
      - d3
      - d4
      - d5
      - d6
  d1:
    image: mongo:3.2
    command: mongod --replSet rep1
    links:
      - d2
      - d3
      - d4
      - d5
      - d6
  d2:
    image: mongo:3.2
    command: mongod --replSet rep1
    links:
      - d3
      - d4
      - d5
      - d6
  d3:
    image: mongo:3.2
    command: mongod --replSet rep1
    links:
      - d4
      - d5
      - d6
  d4:
    image: mongo:3.2
    command: mongod --replSet rep1
    links:
      - d5
      - d6
  d5:
    image: mongo:3.2
    command: mongod --replSet rep1
    links:
      - d6
  d6:
    image: mongo:3.2
    command: mongod --replSet rep1
```

### 検証内容
d1,d2,d3でレプリカセットを組み、アプリケーションのサンプルデータを流し込む。
d4,d5,d6をレプリカセットに参加させ、d4をprimaryに昇格させる。
d1,d2,d3をレプリカセットから外し、アプリケーションの接続情報が変わっていないが問題ないことを確認

### 検証手順

`docker-compose up`
![docker-compose-up.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/f881539a-d12f-cda5-8c86-7765df70fae1.gif)

config serverのレプリカセットが組めてないので、
mongosサーバーが荒ぶってlogを出力している

configサーバーのレプリカセットを設定する

```bash
docker exec -it mongodb_c1_1 mongo --port 27019

# mongo shell
rs.initiate()
rs.add("c2:27019")
rs.add("c3:27019")
# configサーバーはデフォルトでport:27019を使うらしい
```
![config1.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/2f23b0d2-fe40-a82c-c064-ae129b27e282.gif)

![config2.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/11068f29-941b-fa15-2b95-3a3bd6d269c5.gif)

mongodのレプリカセットを設定する

```bash

docker exec -it mongodb_d1_1 mongo

# mongo shell
rs.initiate()
rs.add("d2")
rs.add("c3")
```
![mongod.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/193880b8-017e-0979-c892-2e38549c7045.gif)



mongosにアクセスしてshardを登録する

```bash

docker exec -it mongodb_s1_1 mongo

# mongo shell
sh.addShard("rep1/d2")
#　レプリカセットに所属しいるサーバーを１台指定すればいい感じにやってくれる
```
![mongos.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/ab23b97b-d639-63ce-e3c2-2832e402d1a7.gif)

railsアプリの方でサンプルデータ投入
接続情報

```yml:config/mongoid.yml
development:
  clients:
    default:
      database: development
      hosts:
        - localhost:27017
```

データ投入が終わったところでmongos2の方で接続してみる
接続情報

```yml:config/mongoid.yml
development:
  clients:
    default:
      database: development
      hosts:
        - localhost:27018 #←portを変えた
```

```bash

bin/spring stop
bin/rails c
```

![rails_c_1.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/12494226-a387-6438-8562-1665434a0621.gif)

ちゃんと繋がりました。
今まで別のmongosで作業していても、mongocで情報が共有されるので同じように動くということですね。
shard statusを確認するとアクティブなmongosの数も確認できます。

```bash
mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("585d48811b8e027da3d3da28")
}
  shards:
        {  "_id" : "rep1",  "host" : "rep1/8d106239ab63:27017,d2:27017,d3:27017" }
  active mongoses:
        "3.2.11" : 2
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "development",  "primary" : "rep1",  "partitioned" : false }
```

ここから、レプリカセットの中身を入れ替えても、
rails consoleのセッションを保ったまま操作ができるかを検証していきます。

```bash
docker exec -it mongodb_d1_1 mongo

# mongo shell
# 本番環境ではレプリカセットに追加する時に参照されないようなオプションをつける必要があります。
# データが追いついてないのにアプリケーションから整合性がないデータが参照されてしまうからです。
# データが追いついたことを確認して、参照できるように情報を書き換えます。今回は検証なのでめんどくさいのでやっていません。
rs.add("d4")
rs.add("d5")
rs.add("d6")
```

![mongod2.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/7679240d-689c-33da-d189-fce2e242d9f5.gif)

primaryサーバーを入れ替えます

```bash
# mongo shell
conf = rs.conf()
conf.members[3].priority = 2
rs.reconfig(conf)
```

![mongod3.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/a3d4319f-eeda-84cd-2d0e-d61c5969954f.gif)

mongod d1,d2,d3をレプリカセットから外します。
これでデータが入っていた入れ物は総入れ替えになりました。

```bash
docker exec -it mongodb_d4_1 mongo

# mongo shell
rs.remove("8d106239ab63:27017")
rs.remove("d2:27017")
rs.remove("d3:27017")
```

![mongod4.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/bf6566aa-114e-7ffd-4f57-20bb46c6ea9a.gif)


rails consoleの状況はどうか
![rails-c2.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/499d79c8-12eb-3e0e-2af3-6b7577566d79.gif)

読み込み、書き込み共にうまくいきました。

最後に運用周りでshardが１つの時にconfigサーバーのバックアップは必要なのかを検証してみたいと思います。
mongos,confgサーバーをkillして構築し直した時にちゃんと参照できるのか。
(mongodサーバーのデータだけバックアップとればいい状態であれば嬉しい)

```zsh:zsh
# {}の部分は他のshellでできたか自信ないです。
docker kill mongodb_s{1,2}_1
docker rm mongodb_s{1,2}_1
docker kill mongodb_c{1,2,3}_1
docker rm mongodb_c{1,2,3}_1
```

![d-kill1.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/75175a5c-a5c3-96c8-b8b6-07a778e80617.gif)

![d-kill2.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/5bf920fc-d2a1-a8c0-a6a2-5e6826d7768b.gif)


mongosとconfigサーバーを新しく立て直す
`docker-compose up -d`

![dc-up-d.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/c1690f9e-f47b-7985-8458-e9a0e1946be6.gif)

configサーバーを構築しなおす

```bash
docker exec -it mongodb_c1_1 mongo --port 27019

# mongo shell
rs.initiate()
rs.add("c2:27019")
rs.add("c3:27019")
```
![mongos3.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/5e85cc16-45c4-4ee9-c788-482b75793630.gif)
![mongos4.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/0de2bf07-df60-e076-9f7e-48aced99f07d.gif)

```bash

docker exec -it mongodb_s1_1 mongo

# mongo shell
sh.addShard("rep1/d4")
```
![mongos5.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/f7b3405a-439e-9e4a-c4f1-a8bb27986e07.gif)

rails consoleで確認
![rails-c3.gif](https://qiita-image-store.s3.amazonaws.com/0/31015/543377ac-887e-1e37-6a2c-43c4200a7b6e.gif)
読み込みも書き込みも可能。
rails serverの方でもちゃんと動作していることを確認。

#　まとめ

1.mongodbでshardingを1にして使えば、アプリ側で接続情報を変えず(デプロイなし)にmongodbのメンテナンスができそう。
2.shard1にしておけば、configサーバーがぶっ壊れても作り直せばなんとかなりそう。(mongocのバックアップ不要)
(2に関しては自信がないので、やるなら自己責任でお願いします。)

docker-compose便利
ローカルで手軽にレプリカセットとかをdocker-composeを使えば検証できて便利だなと感じました。
`elastic search`や`redis`,`memcached`のクラスター検証にもいいかもしれません。
`mongodb`アップグレードの検証などにはとても向いていると思います。
（3.2=>3.4のアップグレードは手順が多そうだったので、今回の記事では見送りました。replica setのみだったら楽そうです。）
とくにフォアグラウンド実行すると各dockerの出力が観れてわかりやすいと感じました。

あと全然関係ないんですけど、macを新しくして、gifアニメキャプチャのソフトを新しくしたんですが、
[GIPHY Capture](https://itunes.apple.com/jp/app/giphy-capture.-the-gif-maker/id668208984?mt=12)ってアプリがすごく良かったです。
無料で、最初と最後カットできたり、フレームレートや解像度を変更できたので、手順書とかqiitaとかに貼る用途にいいかと。

[![image](https://qiita-image-store.s3.amazonaws.com/0/31015/f3eef562-0912-5488-5239-abfb1262419b.png)](https://itunes.apple.com/jp/app/giphy-capture.-the-gif-maker/id668208984?mt=12)