LoginSignup
2
3

More than 1 year has passed since last update.

docker-composeでMongoDBの障害自動回復(フェイルオーバー)を検証してみた

Last updated at Posted at 2021-12-28

はじめに

今までは緊急性が高くないプロジェクトではMongoDBをStandalone(単一構成)で利用して来たが、今回はMongoDBで障害発生時にフェイルオーバー(自動でPrimaryからSecondaryに切り替える仕組み)を検証してみました。

フェイルオーバーとは?

フェイルオーバーは、現用系コンピュータサーバ/システム/ネットワークで異常事態が発生したとき、自動的に冗長な待機系コンピュータサーバ/システム/ネットワークに切り換える機能を意味する。これに対して、何らかの異常を察知して、人間が手動で切り替えを行うことをスイッチオーバーという。 ウィキペディアから引用

データベースに何らかの理由で障害が発生したら修復するまでサービスが止まることがよくありますが、MongoDBはフェイルオーバー構成になっていればPrimary(読み書き構成)に障害が発生しても自動的に降格され、SecondaryやArbiterによって投票されて投票数が多い構成がSecondaryからPrimaryに自動昇格して障害なく通常運用が可能な仕組みです。
replica-set-trigger-election.bakedsvg.png

環境構築の前提条件

docker-composeがインストールされていること
Docker Compose version v2.2.1で検証しました。

フェイルオーバーの検証流れ

docker-composeのソースコードダウンロード

Github(mongo-replicaset-docker-compose)からソースコードをクローンしてください。
クローンが終わったら$ docker-compose up -dを実行して環境構築してください。

レプリカセット確認

  • Primaryのコンテナに接続します。
$ docker exec -it mongodb-primary bash

mongoコマンドでPrimaryメンバーに接続します。

$ mongo -u root -p toor

rs.statusコマンドでレプリカセットの状況を確認します。

replset:PRIMARY> rs.status();
{
        ...
    "members" : [
        {
            "_id" : 0,
            "name" : "mongodb-primary:27017",
            "health" : 1, -- 1/稼働, 2/停止
            "stateStr" : "PRIMARY",
                        ...
        },
        {
            "_id" : 1,
            "name" : "mongodb-secondary1:27018",
            "health" : 1,
            "stateStr" : "SECONDARY", -- データの読み書き可能なメンバー
                        ...
        },
        {
            "_id" : 2,
            "name" : "mongodb-secondary2:27019",
            "health" : 1,
            "stateStr" : "SECONDARY", -- データの読み書き可能なメンバー
                        ...
        },
        {
            "_id" : 3,
            "name" : "mongodb-arbiter:27020",
            "health" : 1,
            "stateStr" : "ARBITER", -- SECONDAYが偶数の場合に投票数を奇数にするための仕組み、データストアは持ちませんです。データは持ちません。

                        ...
        }
    ],
        ...
}

PrimaryからSecondaryに複製されていること検証

Primaryに映画データを書き込みます。

replset:PRIMARY> use movie;
replset:PRIMARY> db.movie.find();
replset:PRIMARY> db.movie.find({});
replset:PRIMARY> db.movie.insert({title: "千と千尋の神隠し"});
WriteResult({ "nInserted" : 1 })
replset:PRIMARY> db.movie.insert({title: "となりのトトロ"});
WriteResult({ "nInserted" : 1 })
replset:PRIMARY> db.movie.insert({title: "ハウルの動く城"});
WriteResult({ "nInserted" : 1 })
replset:PRIMARY> db.movie.insert({title: "もののけ姫"});
WriteResult({ "nInserted" : 1 })
replset:PRIMARY> db.movie.find();
{ "_id" : ObjectId("61cb1bee1484b6d46186ac15"), "title" : "千と千尋の神隠し" }
{ "_id" : ObjectId("61cb1c061484b6d46186ac16"), "title" : "となりのトトロ" }
{ "_id" : ObjectId("61cb1c141484b6d46186ac17"), "title" : "ハウルの動く城" }
{ "_id" : ObjectId("61cb1c201484b6d46186ac18"), "title" : "もののけ姫" }
replset:PRIMARY> exit
bye
root@mongodb-primary:/# exit
exit

Secondaryに複製されたデータがあること確認

$ docker exec -it mongodb-secondary1 bash                                                
root@mongodb-secondary1:/# mongo -u root -p toor --port 27018
replset:SECONDARY> db.getMongo().setSecondaryOk(); <<-- ここ忘れずに実行しないとクエリが実行できません。
replset:SECONDARY> use movie;
switched to db movie
replset:SECONDARY> db.movie.find();
{ "_id" : ObjectId("61cb1bee1484b6d46186ac15"), "title" : "千と千尋の神隠し" }
{ "_id" : ObjectId("61cb1c061484b6d46186ac16"), "title" : "となりのトトロ" }
{ "_id" : ObjectId("61cb1c141484b6d46186ac17"), "title" : "ハウルの動く城" }
{ "_id" : ObjectId("61cb1c201484b6d46186ac18"), "title" : "もののけ姫" }
replset:SECONDARY> exit
bye
root@mongodb-secondary1:/# exit
exit

PrimaryとSecondaryのデータが同じであることが確認できました!

Primaryのコンテナをダウンさせてフェイルオーバー確認

$ docker stop mongodb-primary     <<-- プライマリのコンテナの一時停止                                                         
mongodb-primary

$ docker ps -a
CONTAINER ID   IMAGE                                                COMMAND                  CREATED          STATUS                        PORTS                                 NAMES
bb5f739bbcb5   mongo-express:latest                                 "tini -- /docker-ent…"   43 minutes ago   Up 40 minutes                 0.0.0.0:8081->8081/tcp                mongo-express
e7f47e42ec32   mongo-replicaset-docker-compose_mongo-connector      "docker-entrypoint.s…"   43 minutes ago   Exited (0) 43 minutes ago                                           mongo-connector
1c69595e200e   mongo-replicaset-docker-compose_mongodb-arbiter      "docker-entrypoint.s…"   44 minutes ago   Up 44 minutes (healthy)       27017/tcp, 0.0.0.0:27020->27020/tcp   mongodb-arbiter
fa6683c69b79   mongo-replicaset-docker-compose_mongodb-secondary2   "docker-entrypoint.s…"   44 minutes ago   Up 44 minutes (healthy)       27017/tcp, 0.0.0.0:27019->27019/tcp   mongodb-secondary2
09fad2c160e7   mongo-replicaset-docker-compose_mongodb-secondary1   "docker-entrypoint.s…"   44 minutes ago   Up 44 minutes (healthy)       27017/tcp, 0.0.0.0:27018->27018/tcp   mongodb-secondary1
a63d3f8bf48d   mongo-replicaset-docker-compose_mongodb-primary      "docker-entrypoint.s…"   44 minutes ago   Exited (137) 24 seconds ago   <<-- プライマリのコンテナが停止されたこと確認

mongo-replicaset-docker-compose_mongodb-primaryのSTATUSがExitedになってること確認します。

Secondaryでレプリカセットの状況を確認する

$ docker exec -it mongodb-secondary1 bash                                               
root@mongodb-secondary1:/# mongo -u root -p toor --port 27018
replset:PRIMARY> rs.status();
{
        ...
    "members" : [
        {
            "_id" : 0,
            "name" : "mongodb-primary:27017",
            "health" : 0,   <<--- 停止状態になってる
            "stateStr" : "(not reachable/healthy)",
                        ...
        },
        {
            "_id" : 1,
            "name" : "mongodb-secondary1:27018",
            "health" : 1,
            "stateStr" : "PRIMARY",  <<--- secondaryから投票によりPrimaryに昇格されました!!
                        ...
        },
        {
            "_id" : 2,
            "name" : "mongodb-secondary2:27019",
            "health" : 1,
            "stateStr" : "SECONDARY",
                        ...
        },
        {
            "_id" : 3,
            "name" : "mongodb-arbiter:27020",
            "health" : 1,
            "stateStr" : "ARBITER",
                        ...

        }
    ],
        ...
}
replset:PRIMARY> exit
bye
root@mongodb-secondary1:/# exit
exit

元Primaryの障害対応でPrimaryに戻す

$ docker start mongodb-primary                                                           
mongodb-primary

$ docker exec -it mongodb-secondary1 bash                                                
root@mongodb-secondary1:/# mongo -u root -p toor --port 27018
replset:SECONDARY> rs.stepDown()   <<---- 自分自身を降格させる(Secondaryになる)
replset:SECONDARY> rs.status();
{
        ...
    "members" : [
        {
            "_id" : 0,
            "name" : "mongodb-primary:27017",
            "health" : 1,
            "stateStr" : "PRIMARY",
                        ...
        },
        {
            "_id" : 1,
            "name" : "mongodb-secondary1:27018",
            "health" : 1,
            "stateStr" : "SECONDARY",
                        ...
        },
        {
            "_id" : 2,
            "name" : "mongodb-secondary2:27019",
            "health" : 1,
            "stateStr" : "SECONDARY",
                        ...
        },
        {
            "_id" : 3,
            "name" : "mongodb-arbiter:27020",
            "health" : 1,
            "stateStr" : "ARBITER",
                        ...
        }
    ],
        ...
}

最後に

docker-composeを利用してレプリカセットを構築しPrimaryメンバーのデータSecondaryメンバーに複製されることとPrimaryがダウン(障害)で投票によりSecondaryがPrimaryに昇格しサービス停止することなく自動フェイルオーバーになることが確認できました。
本番環境ではこのまま使うことは推奨しません。マイクロサービスでそれぞれ別サーバーとして構成するのをおすすめします。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3