2014年9月21日日曜日

MongoDBのreplicationを利用してfailoverを検証する

mongodの4インスタンスでレプリケーションを構成し、各インスタンスで障害が発生した場合、どのようにフェイルオーバーするかの動作検証を行います。
votesやpriorityの値を変更して、可用性向上(Primaryインスタンスを選出できるか)を試みています。

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


■システム構成概要図


■方針
mongod0/mongo1はアプリケーションと対面するインスタンス。
mongod2はバックアップ用のインスタンス。Priorityの値を0に設定しPrimaryに昇格させない。バックアップ時の計画停止を想定。
mongod3はArbiter固定のインスタンス。
mongo-a/mongo-b/mongo-cの3台のサーバの内、1台に障害が発生してもアプリケーションに対してDBサービス提供が継続できることを目標とする。
さらに、mongod2を計画停止中に他のインスタンスに障害が発生してもPrimaryを選出してサービスが継続できることを目標とする。


■起動用設定ファイル準備
## mongo-aサーバ
[ec2-user@mongo-a ~]$ mkdir /data/mongo_rs/mongod0
[ec2-user@mongo-a ~]$ cat /data/mongo_rs/conf/mongod0.conf
bind_ip=mongo-a
port=30000
dbpath=/data/mongo_rs/mongod0
pidfilepath=/var/run/mongodb/mongod0.pid
logpath=/var/log/mongodb/mongod0.log
logappend=true
fork=true
replSet=rep1
httpinterface=true
[ec2-user@mongo-a ~]$

## mongo-bサーバ
[ec2-user@mongo-b ~]$ mkdir /data/mongo_rs/mongod1
[ec2-user@mongo-b ~]$ cat /data/mongo_rs/conf/mongod1.conf
bind_ip=mongo-b
port=30001
dbpath=/data/mongo_rs/mongod1
pidfilepath=/var/run/mongodb/mongod1.pid
logpath=/var/log/mongodb/mongod1.log
logappend=true
fork=true
replSet=rep1
[ec2-user@mongo-b ~]$
サーバが異なるインスタンスについてはポート番号を変える必要はないが、管理しやすさを考慮してインスタンス番号と合わせてみた。

## mongo-cサーバ
[ec2-user@mongo-c ~]$ mkdir /data/mongo_rs/mongod2
[ec2-user@mongo-c ~]$ mkdir /data/mongo_rs/mongod3
[ec2-user@mongo-c ~]$ cat /data/mongo_rs/conf/mongod2.conf
bind_ip=mongo-c
port=30002
dbpath=/data/mongo_rs/mongod2
pidfilepath=/var/run/mongodb/mongod2.pid
logpath=/var/log/mongodb/mongod2.log
logappend=true
fork=true
replSet=rep1
[ec2-user@mongo-c ~]$ cat /data/mongo_rs/conf/mongod3.conf
bind_ip=mongo-c
port=30003
dbpath=/data/mongo_rs/mongod3
pidfilepath=/var/run/mongodb/mongod3.pid
logpath=/var/log/mongodb/mongod3.log
logappend=true
fork=true
replSet=rep1
[ec2-user@mongo-c ~]$


■起動/停止/ステータス確認スクリプト準備
## 起動用
[ec2-user@mongo-a ~]$ vi start-mongod0.sh
[ec2-user@mongo-a ~]$ cat start-mongod0.sh
sudo mongod --config /data/mongo_rs/conf/mongod0.conf
[ec2-user@mongo-a ~]$ chmod 755 start-mongod0.sh
[ec2-user@mongo-a ~]$
※オフィシャルサイトでは起動前に"ulimit -n 64000"を推奨しているようだが、EC2がしょぼいので今回はパス。

## 停止用
[ec2-user@mongo-a ~]$ vi stop-mongod0.sh
[ec2-user@mongo-a ~]$ cat stop-mongod0.sh
sudo kill -9 `ps -ef | grep mongod0.conf | grep -v grep | awk '{print $2}'`
[ec2-user@mongo-a ~]$ chmod 755 stop-mongod0.sh
[ec2-user@mongo-a ~]$

## 確認用
[ec2-user@mongo-a ~]$ vi mongod-status.sh
[ec2-user@mongo-a ~]$ cat mongod-status.sh
mongo mongo-a:30000 --eval "printjson(rs.status())" | egrep "name|stateStr"
echo ""
mongo mongo-a:30000 --eval "printjson(rs.conf())" | egrep "host|votes|priority|arbiter"
[ec2-user@mongo-a ~]$ chmod 755 mongod-status.sh
[ec2-user@mongo-a ~]$
各サーバに同様のスクリプトを準備。


■mongod起動
[ec2-user@mongo-a ~]$ ./start-mongod0.sh
about to fork child process, waiting until server is ready for connections.
forked process: 1871
child process started successfully, parent exiting
[ec2-user@mongo-a ~]$
同様に各サーバのmongodも起動。


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

## SecondaryとArbiter追加
rep1:PRIMARY> rs.add( { _id: 1, host: "mongo-b:30001" } )
{ "ok" : 1 }
rep1:PRIMARY> rs.add( { _id: 2, host: "mongo-c:30002", priority: 0, votes: 0 } )
{ "ok" : 1 }
rep1:PRIMARY> rs.add( { _id: 3, host: "mongo-c:30003", arbiterOnly: true } )
{ "ok" : 1 }
rep1:PRIMARY>

## 設定確認
rep1:PRIMARY> rs.conf();
{
        "_id" : "rep1",
        "version" : 4,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongo-a:30000"
                },
                {
                        "_id" : 1,
                        "host" : "mongo-b:30001"
                },
                {
                        "_id" : 2,
                        "host" : "mongo-c:30002",
                        "votes" : 0,
                        "priority" : 0
                },
                {
                        "_id" : 3,
                        "host" : "mongo-c:30003",
                        "arbiterOnly" : true
                }
        ]
}
rep1:PRIMARY> rs.status();
{
        "set" : "rep1",
        "date" : ISODate("2014-09-17T07:13:20Z"),
        "myState" : 1,
        "members" : [
                {
                        "_id" : 0,
                        "name" : "mongo-a:30000",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 592,
                        "optime" : Timestamp(1410937918, 1),
                        "optimeDate" : ISODate("2014-09-17T07:11:58Z"),
                        "electionTime" : Timestamp(1410937614, 1),
                        "electionDate" : ISODate("2014-09-17T07:06:54Z"),
                        "self" : true
                },
                {
                        "_id" : 1,
                        "name" : "mongo-b:30001",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 103,
                        "optime" : Timestamp(1410937918, 1),
                        "optimeDate" : ISODate("2014-09-17T07:11:58Z"),
                        "lastHeartbeat" : ISODate("2014-09-17T07:13:19Z"),
                        "lastHeartbeatRecv" : ISODate("2014-09-17T07:13:20Z"),
                        "pingMs" : 0,
                        "syncingTo" : "mongo-a:30000"
                },
                {
                        "_id" : 2,
                        "name" : "mongo-c:30002",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 91,
                        "optime" : Timestamp(1410937918, 1),
                        "optimeDate" : ISODate("2014-09-17T07:11:58Z"),
                        "lastHeartbeat" : ISODate("2014-09-17T07:13:19Z"),
                        "lastHeartbeatRecv" : ISODate("2014-09-17T07:13:19Z"),
                        "pingMs" : 2,
                        "syncingTo" : "mongo-a:30000"
                },
                {
                        "_id" : 3,
                        "name" : "mongo-c:30003",
                        "health" : 1,
                        "state" : 7,
                        "stateStr" : "ARBITER",
                        "uptime" : 82,
                        "lastHeartbeat" : ISODate("2014-09-17T07:13:18Z"),
                        "lastHeartbeatRecv" : ISODate("2014-09-17T07:13:18Z"),
                        "pingMs" : 2
                }
        ],
        "ok" : 1
}
rep1:PRIMARY>
votesとpriorityのdefault値は"1"となる。
default値の場合はrs.conf();のOutputとして表示されない。

## 上記設定の目論見:
mongod2のvotesの値を0にすることで、votesの初期合計値は3となる。
mongod2を含む2インスタンスが停止しても、残りインスタンスのvotesの合計値が2となり、過半数を超える(= 2/3)ため、Primaryが選出される。


◆参考: votesの値を変更
rep1:PRIMARY> cfg = rs.conf();
{
        "_id" : "rep1",
        "version" : 4,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongo-a:30000"
                },
                {
                        "_id" : 1,
                        "host" : "mongo-b:30001"
                },
(以下略)
rep1:PRIMARY> cfg.members[1].votes = 0;
0
rep1:PRIMARY>
rep1:PRIMARY> rs.reconfig(cfg);
2014-09-17T16:23:13.386+0900 DBClientCursor::init call() failed
2014-09-17T16:23:13.388+0900 trying reconnect to mongo-a:30000 (10.54.10.90) failed
2014-09-17T16:23:13.388+0900 reconnect mongo-a:30000 (10.54.10.90) ok
reconnected to server after rs command (which is normal)

rep1:SECONDARY> rs.conf();
{
        "_id" : "rep1",
        "version" : 5,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongo-a:30000"
                },
                {
                        "_id" : 1,
                        "host" : "mongo-b:30001",
                        "votes" : 0
                },
(以下略)
※エラーメッセージの調査はですね、、、後回し。


◆参考: Replicasetから除外
あらかじめ除外対象のmongod(今回はmongod2とする)を停止しておく
[ec2-user@mongo-c ~]$ ./stop-mongod2.sh
[ec2-user@mongo-c ~]$
rep1:PRIMARY> rs.remove("mongo-c:30002");
2014-09-17T16:29:42.296+0900 DBClientCursor::init call() failed
2014-09-17T16:29:42.297+0900 Error: error doing query: failed at src/mongo/shell/query.js:81
2014-09-17T16:29:42.298+0900 trying reconnect to mongo-a:30000 (10.54.10.90) failed
2014-09-17T16:29:42.298+0900 reconnect mongo-a:30000 (10.54.10.90) ok
rep1:PRIMARY>
[ec2-user@mongo-a ~]$ ./mongod-status.sh
                        "name" : "mongo-a:30000",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo-b:30001",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo-c:30003",
                        "stateStr" : "ARBITER",

                        "host" : "mongo-a:30000"
                        "host" : "mongo-b:30001"
                        "host" : "mongo-c:30003",
                        "arbiterOnly" : true
[ec2-user@mongo-a ~]$

## 再び追加
[ec2-user@mongo-c ~]$ ./start-mongod2.sh
about to fork child process, waiting until server is ready for connections.
forked process: 2723
child process started successfully, parent exiting
[ec2-user@mongo-c ~]$
[ec2-user@mongo-a ~]$ mongo mongo-a:30000
MongoDB shell version: 2.6.4
connecting to: mongo-a:30000/test
rep1:PRIMARY> rs.add( { _id: 2, host: "mongo-c:30002", priority: 0, votes: 0 } )
{ "ok" : 1 }
rep1:PRIMARY>


◆rs関連コマンド
rep1:PRIMARY> rs.help();
        rs.status()                     { replSetGetStatus : 1 } checks repl set status
        rs.initiate()                   { replSetInitiate : null } initiates set with default settings
        rs.initiate(cfg)                { replSetInitiate : cfg } initiates set with configuration cfg
        rs.conf()                       get the current configuration object from local.system.replset
        rs.reconfig(cfg)                updates the configuration of a running replica set with cfg (disconnects)
        rs.add(hostportstr)             add a new member to the set with default attributes (disconnects)
        rs.add(membercfgobj)            add a new member to the set with extra attributes (disconnects)
        rs.addArb(hostportstr)          add a new member which is arbiterOnly:true (disconnects)
        rs.stepDown([secs])             step down as primary (momentarily) (disconnects)
        rs.syncFrom(hostportstr)        make a secondary to sync from the given member
        rs.freeze(secs)                 make a node ineligible to become primary for the time specified
        rs.remove(hostportstr)          remove a host from the replica set (disconnects)
        rs.slaveOk()                    shorthand for db.getMongo().setSlaveOk()

        rs.printReplicationInfo()       check oplog size and time range
        rs.printSlaveReplicationInfo()  check replica set members and replication lag
        db.isMaster()                   check who is primary

        reconfiguration helpers disconnect from the database so the shell will display
        an error, even if the command succeeds.
        see also http://:28017/_replSet for additional diagnostic info
rep1:PRIMARY>


■障害シミュレーション
## 正常時
[ec2-user@mongo-a ~]$ ./mongod-status.sh
                        "name" : "mongo-a:30000",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo-b:30001",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo-c:30002",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo-c:30003",
                        "stateStr" : "ARBITER",


## mongo-aサーバ停止
[ec2-user@mongo-c ~]$ ./mongod-status.sh
                        "name" : "mongo-a:30000",
                        "stateStr" : "(not reachable/healthy)",
                        "name" : "mongo-b:30001",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo-c:30002",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo-c:30003",
                        "stateStr" : "ARBITER",

mongo-bサーバのmongod1インスタンスがPRIMARYに選出される。mongod2インスタンスが選出されないのはpriorityの値を"0"に設定しているから。

## mongo-bサーバ停止
[ec2-user@mongo-a ~]$ ./mongod-status.sh
                        "name" : "mongo-a:30000",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo-b:30001",
                        "stateStr" : "(not reachable/healthy)",
                        "name" : "mongo-c:30002",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo-c:30003",
                        "stateStr" : "ARBITER",


## mongo-cサーバ停止
[ec2-user@mongo-a ~]$ ./mongod-status.sh
                        "name" : "mongo-a:30000",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo-b:30001",
                        "stateStr" : "SECONDARY",
                        "name" : "mongo-c:30002",
                        "stateStr" : "(not reachable/healthy)",
                        "name" : "mongo-c:30003",
                        "stateStr" : "(not reachable/healthy)",

Default設定のままだと生きているインスタンスが両方とも"SECONDARY"となってしまうが、votesの設定が功を奏し"PRIMARY"を保持できている。

## mongo-cのSecondaryを停止中にmongo-aサーバ停止
[ec2-user@mongo-c ~]$ ./mongod-status.sh
                        "name" : "mongo-a:30000",
                        "stateStr" : "(not reachable/healthy)",
                        "name" : "mongo-b:30001",
                        "stateStr" : "PRIMARY",
                        "name" : "mongo-c:30002",
                        "stateStr" : "(not reachable/healthy)",
                        "name" : "mongo-c:30003",
                        "stateStr" : "ARBITER",

バックアップ用インスタンスであるmongod2を停止中に、運悪く他のインスタンスが落ちても1つなら大丈夫。
以上、全パターン想定どおりとなりました。


■まとめ
## 3インスタンス構成
votes/priority
設定なし(default)
mongod0
votes:1 priority:1
mongod1
votes:1 priority:1
mongod2
votes:1 priority:1
Initial State PRIMARY SECONDARY ARBITER
Failure mongod0 × PRIMARY ARBITER
mongod1 PRIMARY × ARBITER
mongod2 PRIMARY SECONDARY ×

## 4インスタンス構成(1)
votes/priority
設定なし(default)
mongod0
votes:1 priority:1
mongod1
votes:1 priority:1
mongod2
votes:1 priority:1
mongod3
votes:1 ArbiterOnly
Initial State PRIMARY SECONDARY SECONDARY ARBITER
Failure mongod0 × PRIMARY
(SECONDARY)
SECONDARY
(PRIMARY)
ARBITER
mongod1 PRIMARY × SECONDARY ARBITER
mongod2 PRIMARY SECONDARY × ARBITER
mongod3 PRIMARY SECONDARY SECONDARY ×
mongod0,1 × × SECONDARY ARBITER
mongod0,2 × SECONDARY × ARBITER
mongod0,3 × SECONDARY SECONDARY ×
mongod1,2 SECONDARY × × ARBITER
mongod1,3 × × SECONDARY ARBITER
mongod2,3 SECONDARY SECONDARY × ×
votes/priorityがdefault値の場合、同時に2つのインスタンスに障害が発生した場合アウト!!
つまり、mongod2,mongod3が同居しているmongo-cサーバに障害が発生した時点でPrimaryを保持できなくなりサービスを継続できない。

## 4インスタンス構成(2)
votes/priority
カスタマイズ設定
mongod0
votes:1 priority:1
mongod1
votes:1 priority:1
mongod2
votes:0 priority:0
mongod3
votes:1 ArbiterOnly
Initial State PRIMARY SECONDARY SECONDARY ARBITER
Failure mongod0 × PRIMARY SECONDARY ARBITER
mongod1 PRIMARY × SECONDARY ARBITER
mongod2 PRIMARY SECONDARY × ARBITER
mongod3 PRIMARY SECONDARY SECONDARY ×
mongod0,1 × × SECONDARY ARBITER
mongod0,2 × PRIMARY × ARBITER
mongod0,3 × SECONDARY SECONDARY ×
mongod1,2 PRIMARY × × ARBITER
mongod1,3 × × SECONDARY ARBITER
mongod2,3 PRIMARY SECONDARY × ×
mongod2を計画停止中に他のmongodに障害が発生しても、Primaryを選出できるのでサービス継続可能。


こちらの情報が何かのお役に立てましたら幸いです。
今回はかなり頑張って記載したので、ご利用、転記する場合はコメントいただけるとありがたいです。m(_ _)m

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となります。


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

2014年9月6日土曜日

AmazonLinuxにMongoDBをインストール

AWS上に実践的なMongoDBのReplication構成を構築するための事前作業としてAmazonLinuxにMongoDBをインストールする手順をまとめます。
データベース格納先のディレクトリは/(ルート)とは別ボリュームのEBSを利用するようにしています。
インストール完了後にAMIを作成します。
次回以降は、このAMIから複数のEC2を作成し、MonogoDBのReplication設定を進めていきます。併せてお読みください。

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



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


■AmazonLinux初期設定作業
TimeZone変更: Asia/Tokyo
iptables off
[ec2-user@ip-10-54-10-90 ~]$ sudo date; sudo cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime; sudo date
[ec2-user@ip-10-54-10-90 ~]$ sudo chkconfig iptables off; sudo chkconfig --list iptables
[ec2-user@ip-10-54-10-90 ~]$ sudo chkconfig ip6tables off; sudo chkconfig --list ip6tables
(出力結果省略)


■yum repository の追加
[ec2-user@ip-10-54-10-90 ~]$ sudo vi /etc/yum.repos.d/mongodb.repo
[ec2-user@ip-10-54-10-90 ~]$ cat /etc/yum.repos.d/mongodb.repo
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=0
[ec2-user@ip-10-54-10-90 ~]$
オフィシャルサイトでは"enabled=1"となっているが、他用途でyumコマンドを実行したときにmongodb.repoをデフォルトで参照する必要はないので"enabled=0"とする。


■Install MongoDB
[ec2-user@ip-10-54-10-90 ~]$ sudo yum --enablerepo=mongodb install mongodb-org
(中略)
============================================================================
 Package                      Arch        Version       Repository     Size
============================================================================
Installing:
 mongodb-org                  x86_64      2.6.4-1       mongodb       4.6 k
Installing for dependencies:
 mongodb-org-mongos           x86_64      2.6.4-1       mongodb       6.8 M
 mongodb-org-server           x86_64      2.6.4-1       mongodb       9.0 M
 mongodb-org-shell            x86_64      2.6.4-1       mongodb       4.3 M
 mongodb-org-tools            x86_64      2.6.4-1       mongodb        89 M

Transaction Summary
============================================================================
Install  1 Package (+4 Dependent packages)

Total download size: 109 M
Installed size: 276 M
(以下略)


■version確認
[ec2-user@ip-10-54-10-90 ~]$ mongod -version
db version v2.6.4
2014-09-04T11:28:44.181+0900 git version: 3a830be0eb92d772aa855ebb711ac91d658ee910
[ec2-user@ip-10-54-10-90 ~]$


■MongoDBのデフォルト設定確認
[ec2-user@ip-10-54-10-90 ~]$ grep -v "^#\|^$" /etc/mongod.conf
logpath=/var/log/mongodb/mongod.log
logappend=true
fork=true
dbpath=/var/lib/mongo
pidfilepath=/var/run/mongodb/mongod.pid
bind_ip=127.0.0.1
[ec2-user@ip-10-54-10-90 ~]$
※コメント行と空白行を非表示。


■mongod自動起動off
replication設定でmongodを起動したいので、 デフォルト設定のmongodは起動しないようにしておく。
[ec2-user@ip-10-54-10-90 ~]$ sudo chkconfig mongod off; sudo chkconfig --list mongod
mongod          0:off   1:off   2:off   3:off   4:off   5:off   6:off
[ec2-user@ip-10-54-10-90 ~]$


■データベース格納用ボリューム準備
あらかじめ、EC2作成時に別EBS(今回は10GBとした)を作成しておく。

## EBSのデバイス名を確認
[ec2-user@ip-10-54-10-90 ~]$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk
??xvda1 202:1    0   8G  0 part /
xvdb    202:16   0  10G  0 disk
[ec2-user@ip-10-54-10-90 ~]$

## ext4でフォーマット
[ec2-user@ip-10-54-10-90 ~]$ sudo mkfs.ext4 /dev/xvdb
mke2fs 1.42.8 (20-Jun-2013)
(中略)
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

[ec2-user@ip-10-54-10-90 ~]$

## 自動マウント設定
[ec2-user@ip-10-54-10-90 ~]$ sudo vi /etc/rc.local
[ec2-user@ip-10-54-10-90 ~]$ tail -2 /etc/rc.local
# for MongoDB data
mount /dev/xvdb /data
[ec2-user@ip-10-54-10-90 ~]$


■データベース格納先ディレクトリ準備
[ec2-user@ip-10-54-10-90 ~]$ sudo mkdir /data
[ec2-user@ip-10-54-10-90 ~]$ sudo mount /dev/xvdb /data
[ec2-user@ip-10-54-10-90 ~]$ df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/xvda1     ext4      7.8G  1.2G  6.5G  16% /
devtmpfs       devtmpfs  486M   60K  486M   1% /dev
tmpfs          tmpfs     499M     0  499M   0% /dev/shm
/dev/xvdb      ext4      9.8G   23M  9.2G   1% /data
[ec2-user@ip-10-54-10-90 ~]$

## データベース格納先作成(replication考慮)
[ec2-user@ip-10-54-10-90 ~]$ sudo mkdir -p /data/mongo_rs/db

## configファイル格納先作成と所有者変更
[ec2-user@ip-10-54-10-90 ~]$ sudo mkdir /data/mongo_rs/conf
[ec2-user@ip-10-54-10-90 ~]$ sudo chown ec2-user: -R /data/mongo_rs


★AMI(201409xx_mongo_install_only)作成
データ格納先を独自に指定したいので、デフォルト設定のままではMongoDBを起動しない。
mongo1サーバおよび、mongo2サーバでホスト名を定義したそれぞれのレプリケーション用のmongod.confを作成するためこの時点でAMIを作成しておく。


今回の手順はここまでとします。Replication設定についてはこちらの記事にまとめます。
こちらの情報が何かのお役に立てましたら幸いです。サイト継続ご協力のほどお願い致します。m(_ _)m