MongoDBのReplica Setを障害から復旧させる方法を二通り書く。
MongoDB 3.4
構成について
以下の3台構成の場合の復旧方法について書く。
- primary
- arbiter
- secondary(hidden) ※バックアップ用
primaryのpriorityは1, arbiterとsecondary(hidden)のpriorityは0にしている。
primary, arbiter, secondary(hidden)の順でReplica Setに登録している。
ホスト名について、primaryサーバをdb1001, arbiterをdb1002, secondary(hidden)をdb1003とする。
バックアップを保管しているサーバをbk0000とする。
Replica SetのPrimaryのサーバ自体に障害が発生した場合
OSを起動することができない状態や、サーバのデータを消失してしまった状態など、サーバ自体をすぐに使えなくなった場合を想定。
対応
secondary(hidden)をPrimary昇格させる
手順
secondary(hidden)サーバのMongoDBからPrimary昇格のためrs.reconfig
を実行する。Secondaryからrs.reconfig
は通常実行できないので、{force: true}
をつける必要がある。
以下手順は公式ドキュメントのReconfigure a Replica Set with Unavailable Membersを参考に作成。
$ ssh db1003
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:SECONDARY> cfg = rs.conf()
# secondary(hidden)のpriorityとhiddenの設定を変える。
# 念のためpriorityを最も高く設定し、元primaryをReplica Setに再接続してもPrimary/Secondaryが入れ替わらないようにする。
rs:SECONDARY> cfg.members[2].priority = 10
rs:SECONDARY> cfg.members[2].hidden = false
# primaryを省いてconfを再修正
rs:SECONDARY> cfg.members = [cfg.members[1], cfg.members[2]]
# cfg確認
rs:SECONDARY> cfg
# 反映
rs:SECONDARY> rs.reconfig(cfg, {force: true})
rs:SECONDARY> rs.config()
rs:PRIMARY> #Primary昇格成功
事後対応
secondary(hidden)をPrimaryに昇格させてサービスを継続させた後、旧primaryの障害を取り除き、Replica Setに再参加させる。
旧primaryの障害を取り除いた後、旧primaryのMongoDBのデータを空にしてから起動する。
$ ssh db1001
# mongodの停止確認
$ sudo service mongod status
$ cd /var/lib/mongo/
# データを空にする(ディスクに余剰があれば念のため退避)
$ sudo mv mongod mongod.bk
$ sudo -u mongod mkdir mongod
$ sudo service mongod start
現primaryでrs.add
を実行し、旧primaryをReplica Setに参加させる。
$ ssh db1003
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:PRIMARY> rs.add("db1001:27017")
# 確認
rs:PRIMARY> rs.config()
rs:PRIMARY> rs.status()
旧primaryでデータの同期が始まる。データが同期されたかをログインと実データを見てみることで確認する。
$ ssh db1001
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:SECONDARY> rs.slaveOk()
# 確認
rs:SECONDARY> show dbs
現secondaryはhiddenにしたいので、priorityとhiddenを変更する。
$ ssh db1003
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:PRIMARY> cfg = rs.config()
rs:PRIMARY> cfg.members[2].priority = 0
rs:PRIMARY> cfg.members[2].hidden = true
# 確認
rs:PRIMARY> cfg
# 反映
rs:PRIMARY> rs.reconfig(cfg)
rs:PRIMARY> rs.config()
現primaryのpriorityを1に戻す。
rs:PRIMARY> cfg = rs.config()
rs:PRIMARY> cfg.members[1].priority = 1
# 確認
rs:PRIMARY> cfg
# 反映
rs:PRIMARY> rs.reconfig(cfg)
rs:PRIMARY> rs.config()
Replica Set全体が論理的に壊れた場合
人的ミスやプログラムのバグなどでデータを大量に誤って更新した場合など、正常データの復旧が難しい場合を想定。
論理バックアップ
前提として、論理バックアップを実行済みの必要がある。
今回は、secondary(hidden)サーバにてmongodumpをcronで日次で実行し、bk0000で保管している。またmongodump実施時間帯を除いて毎時oplogをdumpしている。これらを使用して1時間前までPoint-in-time Recoveryする。
bk0000から実行されるmongodumpのスクリプト抜粋は以下の通り。gzip処理が重いので、bk0000で実行せず各MongoDBサーバで実行されるようにしている。
# dump daily at 3:00
ssh ${TARGET_HOST} "mongodump --host localhost:27017 -u root -p ${PASSWORD} --oplog --gzip --archive" > ${BACKUPDIR}/${DAY}_${TARGET_HOST}_mongodump.dmp.gz
oplogdumpのスクリプト抜粋は以下の通り。先ほどと同様の理由でsshを通して実行している。
# for Point-in-Time Recovery, dump oplog periodically
oplog_from=$(date -d "$(date "+%Y-%m-%d 03:00:00")" +%s)
mkdir ${BACKUPDIR}/${DAY}_${TARGET_HOST}_mongodump.oplogD
cat <<EOF | sudo ssh ${TARGET_HOST} > ${BACKUPDIR}/${DAY}_${TARGET_HOST}_mongodump.oplogD/oplog.gz
mongodump --host localhost:27017 -u root -p ${PASSWORD} \
--authenticationDatabase=admin \
-d local -c oplog.rs \
--query '{"ts": {\$gt: Timestamp($oplog_from, 1)}}' \
--out - |
gzip
EOF
対応
primaryへmongorestoreを実行する。その後oplogを適用して1時間前までPoint-in-time Recoveryする。
ただし、現在MongoDBにあるoplogが1時間前から論理的に壊れる直前まで残っている場合は、mongorestoreする前に論理的に壊れる直前までのoplogをdumpして、さらにPoint-in-time Recoveryすることで、障害発生の直前まで元に戻せる。
$ mongodump --host localhost:27017 -u root -p ${PASSWARD} \
--authenticationDatabase=admin \
-d local -c oplog.rs \
--query '{"ts": {$gt: Timestamp({障害発生時の1時間前}, 1)}, "ts": {$lt: Timestamp({障害発生時}, 1)}}' \
--gzip \
--out {BACKUPDIR}/mongodump.oplogD
--query
オプションについて、次を参考にした。 https://qiita.com/kanagi/items/515b4ec72a4868c967c8
手順その1
mongorestore時に--drop
オプションをつけることで、既存のDatabaseを削除してからrestoreしてくれるため、特にReplica Setを初期化する必要がなければ、Primaryに対して--drop
付きでmongorestoreすればいい。
$ ssh bk0000
$ scp -p mongo.dmp.gz db1001:/tmp/mongo.dmp.gz
$ ssh db1001
$ sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --drop --oplogReplay --writeConcern 1 --numInsertionWorkersPerCollection 8 --gzip --archive=/tmp/mongo.dmp.gz
このあとoplogもrestoreし、1時間前までPoint-in-time Recoveryする。
$ ssh bk0000
$ ssh db1001 mkdir /tmp/oplogR
$ zcat mongodump.oplogD/oplog.gz | ssh db1001 'cat - > /tmp/oplogR/oplog.bson'
$ ssh db1001
$ sudo mongorestore --host localhost:27017 -uroot -p${PASSWORD} --oplogReplay /tmp/oplogR
手順その2
Replica Set全体を初期化し、Replica Setの設定を再度行ったのち、Primaryサーバに対してmongorestoreを実施する。
まずはReplica Setを組んでいるMongoDBサーバ全台を初期化する。
$ for i in db1001 db1002 db1003; do ssh $i 'sudo service mongod stop' & done;wait
$ for i in db1001 db1002 db1003; do ssh $i 'sudo mv /var/lib/mongo/mongod{,.bk}' & done;wait
$ for i in db1001 db1002 db1003; do ssh $i 'sudo -u mongod mkdir /var/lib/mongo/mongod'; done
$ for i in db1001 db1002 db1003; do ssh $i 'sudo service mongod start' & done;wait
Replica Setの設定を再投入する。
$ ssh db1001
$ mongo localhost:27017/admin
mongo> config = {
_id: '${REPLICA_SET_NAME}', members: [
{_id: 0, host: 'db1001:27017', priority: 1},
{_id: 1, host: 'db1002:27017', priority: 0, arbiterOnly: true},
{_id: 3, host: 'db1003:27017', priority: 0, hidden: true}
]
}
mongo> rs.initiate(config);
$ echo 'db.createUser({ user:"root",pwd:"${PASSWORD}", "roles" : [{"role" : "root", db:"admin"}] })' | mongo localhost:27017/admin
restoreをする。方法は--drop
をつけない点以外「手順その1」と同じ。