2014年9月7日日曜日

AWS上に実践的なMongoDBのレプリケーション構成を構築

AWS上に実践的なMongoDBのレプリケーション構成を構築する手順をまとめます。
前回MongoDBインストール後に作成したAMIから、異なるAvailability ZoneにEC2を作成します。
ホスト名(mongo1、mongo2)を定義して、なるべくec2-userで操作します。
今回はReplicationの設定が中心です。Shardingの設定は別の機会に…。

OS: amazon-linux-ami/2014.03 (64-bit)
MongoDB: 2.6.4


■システム構成概要図と方針

mongo1サーバ(10.54.10.90) → port=30001:Primary(初期)
mongo2サーバ(10.54.30.152) → port=30002:Secondary(初期)、port=30003:Arbiter
ReplicaSet Name: psa ←名前は適当です。 Primary/Secondary/Arbiterの頭文字。
MongoDBを利用するアプリの都合ではりますが、フェイルオーバー時の差分更新を考慮して、Priorityは設定せず、PrimaryとSecondaryは随時入れ替わることを想定してディレクトリ構成はできるだけ同一とします。
まず、MongoDBをインストールしたAMIを作成し、先にSecondary/Arbiter側から設定します。次にPrimary側でreplication設定すると作業の流れがスムーズです。


******* Secondary/Arbiter *******

前回作成したAMIからmongo2サーバを作成。mongo1サーバとは別のAZを利用する。
replication設定は後ほどPrimaryを想定しているmongo1サーバで実施するため、Secondary/Arbiterの設定を先に進める。


■EC2概要
Instance: i-bb41xxx2 (mongo2)
Private IP: 10.54.30.152
Availability zone: ap-northeast-1c
OS: amazon-linux-ami/2014.03 (64-bit)


■ホスト名変更
[ec2-user@ip-10-54-30-152 ~]$ sudo cp -p /etc/sysconfig/network /etc/sysconfig/network.org
[ec2-user@ip-10-54-30-152 ~]$ sudo vi /etc/sysconfig/network
[ec2-user@ip-10-54-30-152 ~]$ diff /etc/sysconfig/network /etc/sysconfig/network.org
2c2
< HOSTNAME=mongo2
---
> HOSTNAME=localhost.localdomain
[ec2-user@ip-10-54-30-152 ~]$ sudo reboot


■hostsファイルの設定
[ec2-user@mongo2 ~]$ sudo vi /etc/hosts
[ec2-user@mongo2 ~]$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain

10.54.10.90   mongo1
10.54.30.152  mongo2
[ec2-user@mongo2 ~]$


■設定ファイル作成(初期SecondaryおよびArbiter)
※Arbiterは固定だが、Primary/Secondaryは入れ替わることを想定しているので、secondary.confのようなファイル名にはしない。
[ec2-user@mongo2 ~]$ vi /data/mongo_rs/conf/mongod.conf
[ec2-user@mongo2 ~]$ cat /data/mongo_rs/conf/mongod.conf
bind_ip=mongo2
port=30002
dbpath=/data/mongo_rs/db
pidfilepath=/var/run/mongodb/mongod.pid
logpath=/var/log/mongodb/mongod.log
logappend=true
fork=true
replSet=psa
[ec2-user@mongo2 ~]$ mkdir /data/mongo_rs/arb
[ec2-user@mongo2 ~]$ vi /data/mongo_rs/conf/arbiter.conf
[ec2-user@mongo2 ~]$ cat /data/mongo_rs/conf/arbiter.conf
bind_ip=mongo2
port=30003
dbpath=/data/mongo_rs/arb
pidfilepath=/var/run/mongodb/arbiter.pid
logpath=/var/log/mongodb/arbiter.log
logappend=true
fork=true
replSet=psa
[ec2-user@mongo2 ~]$


◆参考:ファイルディスクリプタ数上限値拡張
デフォルトの1024→2048に変更。
[ec2-user@mongo2 ~]$ ulimit -a | grep open; ulimit -n 2048; ulimit -a | grep open
open files                      (-n) 1024
open files                      (-n) 2048
[ec2-user@mongo2 ~]$
システムの規模に応じて上限値を拡張する。
MongoDBのオフィシャルサイトでは"64000"を推奨しているようですが、EC2インスタンスがしょぼいので今回は2048に設定してみます。
この値はログインしているシェルで保持されているため、恒久的な設定でないことに注意!


■mongod起動
## Secondary
[ec2-user@mongo2 ~]$ ulimit -n 2048; sudo mongod --config /data/mongo_rs/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1732
child process started successfully, parent exiting
[ec2-user@mongo2 ~]$
※ec2-userをmongodグループに所属させたり、pidファイルやlogファイルの権限を変えてみたが、ec2-userで起動すると失敗する。
mongodはrootで起動することを想定している??

## Arbiter
[ec2-user@mongo2 ~]$ ulimit -n 2048; sudo mongod --config /data/mongo_rs/conf/arbiter.conf
about to fork child process, waiting until server is ready for connections.
forked process: 17768
child process started successfully, parent exiting
[ec2-user@mongo2 ~]$

## 起動確認
[ec2-user@mongo2 ~]$ /etc/init.d/mongod status
mongod (pid 17768 1732) is running...
[ec2-user@mongo2 ~]$ ps aux | head -1; ps aux | grep mongod
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      1732  0.1  3.1 725500 32024 ?        Sl   15:27   0:00 mongod --config /data/mongo_rs/conf/mongod.conf
root     17768  0.1  3.1 717308 32076 ?        Sl   15:32   0:00 mongod --config /data/mongo_rs/conf/arbiter.conf
ec2-user 17862  0.0  0.0 110280   840 pts/0    S+   15:36   0:00 grep mongod
[ec2-user@mongo2 ~]$

## 停止させる場合
mongod stopコマンドだとSecondary/Arbiterのどちらかしか停止できない。
完全に停止させるにはkillコマンドを使用する。


■データベース接続確認
[ec2-user@mongo2 ~]$ mongo mongo2:30002
MongoDB shell version: 2.6.4
connecting to: mongo2:30002/test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
> show dbs
admin  (empty)
local  0.078GB
> exit
bye
[ec2-user@mongo2 ~]$ mongo mongo2:30003
MongoDB shell version: 2.6.4
connecting to: mongo2:30003/test
> show dbs
admin  (empty)
local  0.078GB
> exit
bye
[ec2-user@mongo2 ~]$
Secondary/Arbiterの準備はこれで完了です。次はPrimaryの設定になります。


◆参考:MongoDBインスタンスの設定値の確認
[ec2-user@mongo2 ~]$ cat /proc/1732/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             7772                 7772                 processes
Max open files            2048                 2048                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       7772                 7772                 signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us
[ec2-user@mongo2 ~]$
※1732はSecondaryのPIDです。


◆参考:MongoDBモニタリングツール
[ec2-user@mongo2 ~]$ mongostat -h mongo2 --port 30002
connected to: mongo2:30002
insert  query update delete getmore command flushes mapped  vsize    res faults  locked db idx miss %     qr|qw   ar|aw  netIn netOut  conn repl       time
    *0      1     *0     *0       0     1|0       0    80m   709m    31m      0 local:0.0%          0       0|0     0|0    62b     3k     1  REC   15:41:44
    *0      1     *0     *0       0     1|0       0    80m   709m    31m      0 local:0.0%          0       0|0     0|0    62b     3k     1  REC   15:41:45
    *0      1     *0     *0       0     1|0       0    80m   709m    31m      0 local:0.0%          0       0|0     0|0    62b     3k     1  REC   15:41:46
    *0      1     *0     *0       0     1|0       0    80m   709m    31m      0 local:0.0%          0       0|0     0|0    62b     3k     1  REC   15:41:47
    *0      1     *0     *0       0     1|0       0    80m   709m    31m      0 local:0.0%          0       0|0     0|0    62b     3k     1  REC   15:41:48
^C
[ec2-user@mongo2 ~]$



******* Primary *******

■EC2概要
Instance: i-eca4xxx5 (mongo1)
Private IP: 10.54.10.90
Availability zone: ap-northeast-1a
OS: amazon-linux-ami/2014.03 (64-bit)


■ホスト名変更
[ec2-user@ip-10-54-10-90 ~]$ sudo cp -p /etc/sysconfig/network /etc/sysconfig/network.org
[ec2-user@ip-10-54-10-90 ~]$ sudo vi /etc/sysconfig/network
[ec2-user@ip-10-54-10-90 ~]$ diff /etc/sysconfig/network /etc/sysconfig/network.org
2c2
< HOSTNAME=mongo1
---
> HOSTNAME=localhost.localdomain
[ec2-user@ip-10-54-10-90 ~]$ sudo reboot


■hostsファイルの設定
[ec2-user@mongo1 ~]$ sudo vi /etc/hosts
[ec2-user@mongo1 ~]$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain

10.54.10.90   mongo1
10.54.30.152  mongo2
[ec2-user@mongo1 ~]$
※AWSの場合、SecurityGroupでmongo2サーバとの通信を許可しておいてください。


■設定ファイル作成(初期Primary)
[ec2-user@mongo1 ~]$ vi /data/mongo_rs/conf/mongod.conf
[ec2-user@mongo1 ~]$ cat /data/mongo_rs/conf/mongod.conf
bind_ip=mongo1
port=30001
dbpath=/data/mongo_rs/db
pidfilepath=/var/run/mongodb/mongod.pid
logpath=/var/log/mongodb/mongod.log
logappend=true
fork=true
replSet=psa
[ec2-user@mongo1 ~]$


■mongod起動
[ec2-user@mongo1 ~]$ ulimit -n 2048; sudo mongod --config /data/mongo_rs/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1615
child process started successfully, parent exiting
[ec2-user@mongo1 ~]$
この時点ではまだreplicationされていませんのでご注意を。


■replication設定
いよいよrepricationの設定です。rs.initiate()実行後にコマンドプロンプトが
"ReplicaSet名:PRIMARY>"に変更されます。

## ReplicaSetの初期化
[ec2-user@mongo1 ~]$ mongo mongo1:30001
MongoDB shell version: 2.6.4
connecting to: mongo1:30001/test
> rs.initiate()
{
        "info2" : "no configuration explicitly specified -- making one",
        "me" : "mongo1:30001",
        "info" : "Config now saved locally.  Should come online in about a minute.",
        "ok" : 1
}
>
psa:PRIMARY> rs.conf()
{
        "_id" : "psa",
        "version" : 1,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongo1:30001"
                }
        ]
}
psa:PRIMARY>

## SecandaryとArbiter追加
psa:PRIMARY> rs.add("mongo2:30002")
{ "ok" : 1 }
psa:PRIMARY> rs.addArb("mongo2:30003")
{ "ok" : 1 }
psa:PRIMARY>

## 設定の確認
psa:PRIMARY> rs.conf()
{
        "_id" : "psa",
        "version" : 3,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongo1:30001"
                },
                {
                        "_id" : 1,
                        "host" : "mongo2:30002"
                },
                {
                        "_id" : 2,
                        "host" : "mongo2:30003",
                        "arbiterOnly" : true
                }
        ]
}
psa:PRIMARY> rs.status()
{
        "set" : "psa",
        "date" : ISODate("2014-09-04T07:13:39Z"),
        "myState" : 1,
        "members" : [
                {
                        "_id" : 0,
                        "name" : "mongo1:30001",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 461,
                        "optime" : Timestamp(1409814782, 1),
                        "optimeDate" : ISODate("2014-09-04T07:13:02Z"),
                        "electionTime" : Timestamp(1409814552, 2),
                        "electionDate" : ISODate("2014-09-04T07:09:12Z"),
                        "self" : true
                },
                {
                        "_id" : 1,
                        "name" : "mongo2:30002",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 53,
                        "optime" : Timestamp(1409814782, 1),
                        "optimeDate" : ISODate("2014-09-04T07:13:02Z"),
                        "lastHeartbeat" : ISODate("2014-09-04T07:13:38Z"),
                        "lastHeartbeatRecv" : ISODate("2014-09-04T07:13:38Z"),
                        "pingMs" : 2,
                        "syncingTo" : "mongo1:30001"
                },
                {
                        "_id" : 2,
                        "name" : "mongo2:30003",
                        "health" : 1,
                        "state" : 7,
                        "stateStr" : "ARBITER",
                        "uptime" : 37,
                        "lastHeartbeat" : ISODate("2014-09-04T07:13:38Z"),
                        "lastHeartbeatRecv" : ISODate("2014-09-04T07:13:39Z"),
                        "pingMs" : 2
                }
        ],
        "ok" : 1
}
psa:PRIMARY>

## 念のためSecondary側でも確認
[ec2-user@mongo1 ~]$ mongo mongo2:30002
MongoDB shell version: 2.6.4
connecting to: mongo2:30002/test
psa:SECONDARY> rs.conf()
{
        "_id" : "psa",
        "version" : 3,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongo1:30001"
                },
                {
                        "_id" : 1,
                        "host" : "mongo2:30002"
                },
                {
                        "_id" : 2,
                        "host" : "mongo2:30003",
                        "arbiterOnly" : true
                }
        ]
}
psa:SECONDARY>


◆参考:replicationの動作検証
Primaryで作成した1000件のレコードが、Secondaryにレプリケートされいているか確認。

## レコード作成(Primary)
[ec2-user@mongo1 ~]$ mongo mongo1:30001/reptest
MongoDB shell version: 2.6.4
connecting to: mongo1:30001/reptest
psa:PRIMARY> for(var i=0; i<1000; i++) db.repcoll.insert( { "uid":i, "value":Math.floor(Math.random()*1000+1) } )
WriteResult({ "nInserted" : 1 })
psa:PRIMARY> db.repcoll.count()
1000
psa:PRIMARY> db.repcoll.find()
{ "_id" : ObjectId("5408144a85529d4cbf6eb107"), "uid" : 0, "value" : 929 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb108"), "uid" : 1, "value" : 547 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb109"), "uid" : 2, "value" : 26 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10a"), "uid" : 3, "value" : 813 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10b"), "uid" : 4, "value" : 609 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10c"), "uid" : 5, "value" : 992 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10d"), "uid" : 6, "value" : 75 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10e"), "uid" : 7, "value" : 449 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10f"), "uid" : 8, "value" : 817 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb110"), "uid" : 9, "value" : 422 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb111"), "uid" : 10, "value" : 133 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb112"), "uid" : 11, "value" : 280 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb113"), "uid" : 12, "value" : 613 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb114"), "uid" : 13, "value" : 830 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb115"), "uid" : 14, "value" : 790 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb116"), "uid" : 15, "value" : 155 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb117"), "uid" : 16, "value" : 915 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb118"), "uid" : 17, "value" : 317 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb119"), "uid" : 18, "value" : 903 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb11a"), "uid" : 19, "value" : 89 }
Type "it" for more
psa:PRIMARY>

## 確認(Secondary)
[ec2-user@mongo1 ~]$ mongo mongo2:30002/reptest
MongoDB shell version: 2.6.4
connecting to: mongo2:30002/reptest
psa:SECONDARY> db.repcoll.count()
2014-09-04T16:30:49.898+0900 count failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" } at src/mongo/shell/query.js:191
psa:SECONDARY>
→読み取り許可設定が必要。
psa:SECONDARY> db.getMongo().setSlaveOk()
psa:SECONDARY> db.repcoll.count()
1000
psa:SECONDARY> db.repcoll.find()
{ "_id" : ObjectId("5408144a85529d4cbf6eb107"), "uid" : 0, "value" : 929 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb108"), "uid" : 1, "value" : 547 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb109"), "uid" : 2, "value" : 26 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10a"), "uid" : 3, "value" : 813 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10b"), "uid" : 4, "value" : 609 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10c"), "uid" : 5, "value" : 992 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10d"), "uid" : 6, "value" : 75 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10e"), "uid" : 7, "value" : 449 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb10f"), "uid" : 8, "value" : 817 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb110"), "uid" : 9, "value" : 422 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb111"), "uid" : 10, "value" : 133 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb112"), "uid" : 11, "value" : 280 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb113"), "uid" : 12, "value" : 613 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb114"), "uid" : 13, "value" : 830 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb115"), "uid" : 14, "value" : 790 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb116"), "uid" : 15, "value" : 155 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb117"), "uid" : 16, "value" : 915 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb118"), "uid" : 17, "value" : 317 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb119"), "uid" : 18, "value" : 903 }
{ "_id" : ObjectId("5408144a85529d4cbf6eb11a"), "uid" : 19, "value" : 89 }
Type "it" for more
psa:SECONDARY>
Primaryに登録されたデータがSecondaryにレプリケートされていることを確認できました。


◆参考:MongoDBフェイルオーバー検証
Primaryに障害が発生した場合、SecandaryがPrimaryに昇格するか確認してみます。

## mongo1サーバのPrimary停止前(Arbiterに接続して確認)
[ec2-user@mongo1 ~]$ mongo mongo2:30003 --eval "printjson(rs.status())" | egrep "name|stateStr"
                        "name" : "mongo1:30001",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo2:30002",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo2:30003",
                        "stateStr" : "ARBITER",
[ec2-user@mongo1 ~]$
mongo ホスト名:ポート番号 --eval "printjson(mongoシェル)" とすると、Bashから直接mongoシェルを実行できます。

## mongo1サーバのPrimary停止
[ec2-user@mongo1 ~]$ ps -ef | grep mongo
root      1615     1  1 16:05 ?        00:02:43 mongod --config /data/mongo_rs/conf/mongod.conf
ec2-user  3681  2574  0 19:04 pts/0    00:00:00 grep mongo
[ec2-user@mongo1 ~]$ sudo kill -9 1615
[ec2-user@mongo1 ~]$
[ec2-user@mongo1 ~]$ mongo mongo2:30003 --eval "printjson(rs.status())" | egrep "name|stateStr"
                        "name" : "mongo1:30001",
                        "stateStr" : "(not reachable/healthy)",
                        "name" : "mongo2:30002",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo2:30003",
                        "stateStr" : "ARBITER",
[ec2-user@mongo1 ~]$
SecandaryがPrimaryに昇格しました!!
※この後、元Primaryを復旧させると優先順位を定義していないのでSecondaryとなります。


長文にお付き合いいただきありがとうございました。こちらの情報が何かのお役に立てましたら幸いです。

1 件のコメント: