クラウドサーバー上でMongoDBクラスタを構築してみた

# はじめに

オンプレミス環境では、システムを構築する前にサーバー調達から物理作業・OSインストールが完了しないと構築作業が進めないのですが、クラウド環境では、ボタンを押すだけでネットワークからサーバーまで短時間で準備できます。そして、最近の「Infrastructure as Code」で、ボタンも押す必要がなくなりました。設定ファイルを記述することでサーバーやネットワーク構成が自動的にプロビージョンニングされるようになり、コマンドを実行するだけで「インフラストラクチャー」ができあがってしまいます。

個人的にはこれを見てすごく興味深いので、MongoDBクラスタをコード実行で構築できるようにしたくやってみました。ちょうど@mosuke5さんの[`terraform`レポジトリ](https://github.com/mosuke5/terraform_for_alibabacloud_examples)のサンプルがあったので、サンプルを見ながらやってみました。

# 構成

![image.png](https://qiita-image-store.s3.amazonaws.com/0/74420/e8db4d07-e98c-36b1-d711-34efdde5dcdd.png)

Mongodbのレプリケーション構成では、最低レプリカ3台が必要です。そして、可用性を担保するため、それぞれのレプリカが別のスイッチに刺さる必要があります。そのため、Alibabaクラウド上では、以下の図どおりのコンポネントを作ります

![architecture_mongo_cluster.png](https://qiita-image-store.s3.amazonaws.com/0/74420/ed320141-0de0-0ea4-8507-b4b07bc70efb.png)

### コンポーネント

* VPCネットワーク。CIDR: 10.0.0.0/16
* それぞれのレプリカのプライベートネットワーク
    * 10.0.0.0/19: プライマリレプリカ
    * 10.0.32.0/19: セカンダリレプリカ
    * 10.0.64.0/19: セカンダリレプリカ
* mongoクラスタ用のECSインスタンス
    * テストのため、1CPU-1GBメモリを使います
* mongoクラスタのECSはグローバルIPを持つと外からデータストアへアクセスができてしまうため、プライベートIPしか付与しません。ソフトウェア更新やmongoインストールするために、それぞれのゾーンに[SNATゲートウェイ](http://qiita.com/talzeus/items/2cd54f6e3042af217e4c)を構築します。SNATのECSがmongoクラスタへ影響しないように、別のサブネットワークにおきます。
    * パブリックCIDR: 10.0.128.0/20


# 構築
## Terraformによる自動構築

設計したコンポーネントに従って[alicloud providers](https://www.terraform.io/docs/providers/alicloud/index.html)を使用して実装します。

詳細のソースコードは[mosuke5さんのレポジトリ](https://github.com/mosuke5/terraform_for_alibabacloud_examples/tree/master/mongo_cluster)に参照すればよいですが、`providers`は以下のふうになります。

```

resource "alicloud_vpc" "vpc" {
  name = "mongovpc"
  cidr_block = "${var.vpc_cidr}"
}

# vswitchの作成。mongo-primary
resource "alicloud_vswitch" "mongo-primary-switch" {
  name              = "mongo-primary-switch"
  vpc_id            = "${alicloud_vpc.vpc.id}"
  cidr_block        = "${var.mongo_primary_cidr}"
  availability_zone = "${var.zones[0]}"
}

# vswitchの作成。mongo-secondary0
resource "alicloud_vswitch" "mongo-secondary0-switch" {
  name              = "mongo-secondary0-switch"
  vpc_id            = "${alicloud_vpc.vpc.id}"
  cidr_block        = "${var.mongo_secondary0_cidr}"
  availability_zone = "${var.zones[1]}"
}
...

```

上記のプログラムを `terraform apply --var-file="terraform.tfvars"` を実行することで、図の構成が作成されます。

**VSwitch/VRouter**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/74420/73b38c20-d69f-2924-cce2-418af34f2bbf.png)

**サーバー**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/74420/d2c724e3-b4d3-80ee-4f56-ea3bda430e58.png)

## レプリカ設定

terraform を実行した後では、mongodbクラスタ (サーバー、ミドルウェア）ができあがります。クラスタの設定は [ansible](https://ansible.com)とかで自動化できます。ただ、今回は手動でやります。

まず、mongodbは全インターフェイスで通信できるように、`bindIp`を削除し、`replication` set名を設定します。設定語に`mongodb`を起動します。この作業をmongodbの3台をそれぞれ行います。

```bash
[mongoadmin@mongo-primary ~]$ sudo vim /etc/mongodb.conf
net:
  port: 27017
#  bindIp: 127.0.0.1  # Listen to local interface only, comment to listen on all interfaces.

replication:
  replSetName: testrepl

# :wq
[mongoadmin@mongo-primary ~]$ sudo systemctl start mongod.service
[mongoadmin@mongo-primary ~]$ sudo systemctl status mongod.service
● mongod.service - High-performance, schema-free document-oriented database
   Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2017-06-05 10:02:25 CST; 3s ago
     Docs: https://docs.mongodb.org/manual
  Process: 9504 ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb (code=exited, status=0/SUCCESS)
  Process: 9502 ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb (code=exited, status=0/SUCCESS)
  Process: 9501 ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb (code=exited, status=0/SUCCESS)
 Main PID: 9509 (mongod)
   CGroup: /system.slice/mongod.service
           └─9509 /usr/bin/mongod --quiet -f /etc/mongod.conf run

Jun 05 10:02:25 mongo-primary systemd[1]: Starting High-performance, schema-free document-oriented database...
Jun 05 10:02:25 mongo-primary systemd[1]: Started High-performance, schema-free document-oriented database.
Jun 05 10:02:25 mongo-primary mongod[9506]: about to fork child process, waiting until server is ready for connections.
Jun 05 10:02:25 mongo-primary mongod[9506]: forked process: 9509
```

次には、mongoシェルでreplication設定を行います。

```bash
[mongoadmin@mongo-primary ~]$ mongo
MongoDB shell version v3.4.4
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.4
Welcome to the MongoDB shell.
...
>
> rs.initiate( {
...    _id : "testrepl",
...    members: [
...        { _id : 0, host : "10.0.26.159:27017" },
...        { _id : 1, host : "10.0.43.6:27017" },
...        { _id : 2, host : "10.0.91.206:27017" }
...    ]
... })
{ "ok" : 1 }
> rs.status()
..
{
			"_id" : 0,
			"name" : "10.0.26.159:27017",
			"health" : 1,
...
},
{
			"_id" : 1,
			"name" : "10.0.43.6:27017",
			"health" : 1,
...
},
{
			"_id" : 2,
			"name" : "10.0.91.206:27017",
			"health" : 1,
...
}
```
それぞれの`"health": 1`となっていることを確認します。

各**secondary**で当インスタンスがslaveインスタンスなことを設定します。

```bash
testrepl:SECONDARY> db.getMongo().setSlaveOk()
```

## 動作確認

`testCollection`を`mongo-primary`サーバーで作って、データが`secondary`へコピーされることを確認します

```bash 
testrepl:PRIMARY> db.testCollection.insertOne({what: "Test mongodb", status: "ok"})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("5934bf70cefc8e44e631908e")
}
```

**secondary**で `testCollection`をクエリしてみます。

```bash
testrepl:SECONDARY> db.testCollection.find()
{ "_id" : ObjectId("5934bf70cefc8e44e631908e"), "what" : "Test mongodb", "status" : "ok" }
```

# おわりに

`terraform`を使ってシンプルなmongoクラスタを作ってみました。`terraform`を使うことで、クラウドサーバーの購入・設定のような作業を自動化できます。