MongoDB レプリカセット on Ubuntu — 3ノード冗長構成の構築

データベース

MongoDBを本番に使うなら、単体構成は障害でデータごと止まるリスクがあります。レプリカセット(Replica Set)を組めば、3ノードが同じデータを持ち合い、1台が落ちても自動でフェイルオーバーする冗長構成になります。

結論から言うと、構築は rs.initiate() の1コマンドで完成します。今回は mongo:7.0 公式イメージを使った3ノード構成を実際に Docker で組み、PRIMARY をわざと停止してフェイルオーバー時間まで実測しました。ここで意外な結果が出たので、その数字も正直に載せます。

この記事のポイント

  • MongoDB 7.0.37 / mongosh 2.8.3 を mongo:7.0 コンテナ3台で実構築(2026-06-14 確認)
  • rs.initiate() 一発で PRIMARY×1 + SECONDARY×2 が完成し、{ ok: 1 } を実取得
  • PRIMARY を docker stop で正常停止すると わずか1.2秒で自動フェイルオーバー(実測)
  • 一方 docker kill の強制クラッシュでは 約10秒かかる。差は electionTimeoutMillis にあります
  • 再起動した旧 PRIMARY は手動操作なしで SECONDARY として自動復帰
  • mongo-express(WebUI)で実際の稼働画面も確認します

目次

  1. レプリカセットとは何か
  2. 前提環境と検証構成
  3. MongoDB のインストール(Ubuntu 22.04/24.04)
  4. Docker で3ノードを起動する
  5. レプリカセットを初期化する
  6. レプリケーションを動作確認する
  7. フェイルオーバーを実際に試す(実測)
  8. VPS 本番環境での設定ポイント
  9. よくあるエラーと解決策
  10. まとめ

レプリカセットとは何か

MongoDB のレプリカセットは、同じデータを持つ複数の mongod プロセスのグループです。mongod は MongoDB 本体のサーバープロセスを指します。1台が PRIMARY として書き込みを受け付け、残りの SECONDARY が oplog(operations log:PRIMARY で起きた全変更の操作ログ)を受け取って同じ状態に保ちます。

MongoDB レプリケーションの仕組み(概念図)
MongoDB レプリケーションの仕組み(概念図)

最大のメリットは 自動フェイルオーバー です。PRIMARY が落ちると、残った SECONDARY が過半数(過半数投票)で新しい PRIMARY を選びます。3ノード構成なら過半数は2票なので、1台落ちても運用を続けられます。逆に2台落ちると過半数を満たせず、残る1台は SECONDARY のまま書き込みを受け付けなくなります。

前提環境と検証構成

注意

本記事は mongo:7.0 公式 Docker イメージ(ベースは Ubuntu 22.04 jammy)で検証しています。VPS にネイティブインストールする場合の MongoDB 公式リポジトリも Ubuntu 22.04 jammy ベースのため、24.04 でも同じリポジトリ(jammy 用)を使います。

まずは実際に取得した環境情報です。バージョンはすべて mongod --version / mongosh --version の実出力です。

検証環境とソフトウェアバージョン(実測)
検証環境とソフトウェアバージョン(実測)
項目 検証環境(Docker) 本番推奨(VPS)
イメージ / OS mongo:7.0(Ubuntu 22.04 jammy ベース) Ubuntu 22.04 / 24.04 LTS
MongoDB バージョン 7.0.37(2026-06-14 実測) 7.0.x(公式リポジトリ最新)
mongosh 2.8.3 2.8.x
ノード数 3(同一ホスト、コンテナ分離) 3(別ホスト推奨)
ポート 27041 / 27042 / 27043 27017(各サーバー)
フェイルオーバー時間 正常停止 1.2秒 / 強制クラッシュ 10.2秒(実測)

記事中のコンテナ名は mongo1r / mongo2r / mongo3r、ポートは他コンテナと衝突しないよう 27041〜27043 を使っています。VPS 本番では各サーバーで標準の 27017 を使えば問題ありません。

MongoDB のインストール(Ubuntu 22.04/24.04)

VPS にネイティブインストールする場合の手順です。Ubuntu 標準リポジトリには新しい MongoDB が含まれないため、MongoDB 公式リポジトリを追加してからインストールします。

手順1:必要パッケージと GPG キーを追加する




ubuntu@mongo1: ~
$ sudo apt-get update && sudo apt-get install -y gnupg curl ca-certificates
Reading package lists… Done
Building dependency tree… Done
$ curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc \
| sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg –dearmor

手順2:リポジトリを追加して MongoDB を入れる




ubuntu@mongo1: ~
$ echo “deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] \
https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse” \
| sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
$ sudo apt-get update && sudo apt-get install -y mongodb-org
The following NEW packages will be installed:
mongodb-org mongodb-org-database mongodb-org-server
mongodb-mongosh mongodb-org-tools …
$ mongod –version
db version v7.0.37
$ mongosh –version
2.8.3

上の v7.0.372.8.3 は、mongo:7.0 イメージで実際に確認した値です。VPS でも公式リポジトリから同じ 7.0 系が入ります。

VPS 本番環境の場合

各 VPS の /etc/mongod.confreplication.replSetName: rs0 を追加して sudo systemctl restart mongod で再起動します。詳細は「VPS 本番環境での設定ポイント」を参照してください。

Docker で3ノードを起動する

本番は3台の VPS を使いますが、動作確認は Docker 3コンテナで手元に再現できます。1台のマシンでレプリカセットの全挙動を試せるので、学習にも最適です。

手順1:Docker ネットワークを作成する

3ノードがコンテナ名で互いに通信できるよう、専用ネットワークを作ります。




ubuntu@linuxlab: ~
$ docker network create mongonet
3e5f0f20ff…

手順2:3ノードを起動する




ubuntu@linuxlab: ~
$ docker run -d –name mongo1r –network mongonet –hostname mongo1r \
-p 27041:27017 mongo:7.0 mongod –replSet rs0 –bind_ip_all
$ docker run -d –name mongo2r –network mongonet –hostname mongo2r \
-p 27042:27017 mongo:7.0 mongod –replSet rs0 –bind_ip_all
$ docker run -d –name mongo3r –network mongonet –hostname mongo3r \
-p 27043:27017 mongo:7.0 mongod –replSet rs0 –bind_ip_all
$ docker ps –filter name=mongo1r –filter name=mongo2r –filter name=mongo3r
mongo3r mongo:7.0 Up Less than a second 0.0.0.0:27043->27017/tcp
mongo2r mongo:7.0 Up Less than a second 0.0.0.0:27042->27017/tcp
mongo1r mongo:7.0 Up 1 second 0.0.0.0:27041->27017/tcp

ポイントは --replSet rs0 オプションです。これをつけないと rs.initiate() が失敗します。ここだけは順番を間違えると動きません。--hostname を付けてコンテナ名と同じホスト名にしておくと、後のメンバー登録が mongo1r:27017 のように分かりやすくなります。

レプリカセットを初期化する

3ノードが起動したら、mongo1r から rs.initiate() でレプリカセットを初期化します。

手順1:rs.initiate() を実行する




ubuntu@linuxlab: ~
$ docker exec mongo1r mongosh –quiet –eval “rs.initiate({
_id: ‘rs0’,
members: [
{ _id: 0, host: ‘mongo1r:27017’ },
{ _id: 1, host: ‘mongo2r:27017’ },
{ _id: 2, host: ‘mongo3r:27017’ }
]
})”
{ ok: 1 }

{ ok: 1 } が返れば初期化成功です。PRIMARY の選出には数秒かかるので、10秒ほど待ってから rs.status() で確認します。

手順2:rs.status() で状態を確認する

rs.status() 3ノード レプリカセット稼働状態(実測)
rs.status() 3ノード レプリカセット稼働状態(実測)



ubuntu@linuxlab: ~
$ docker exec mongo1r mongosh –quiet –eval “rs.status()”
set: ‘rs0’
myState: 1
majorityVoteCount: 2
0 mongo1r:27017 health=1 PRIMARY uptime=23 ping=-
1 mongo2r:27017 health=1 SECONDARY uptime=12 ping=0
2 mongo3r:27017 health=1 SECONDARY uptime=12 ping=0

全ノードの health: 1 が確認できました。mongo1r が PRIMARY に選出され、mongo2rmongo3r が SECONDARY として稼働しています。majorityVoteCount: 2 は「2票で過半数」、つまり3ノード中2台が合意すれば意思決定できる設定です。

レプリケーションを動作確認する

レプリカセットが本当に同期しているか、実際にデータを書き込んで確認します。

手順1:PRIMARY にデータを書き込む




ubuntu@linuxlab: ~ (mongo1r PRIMARY)
$ docker exec mongo1r mongosh –quiet –eval “
db = db.getSiblingDB(‘testdb’);
db.users.insertMany([
{ name: ‘Alice’, role: ‘admin’ },
{ name: ‘Bob’, role: ‘user’ },
{ name: ‘Charlie’, role: ‘user’ }]);
print(‘Count on PRIMARY:’, db.users.countDocuments());”
Count on PRIMARY: 3

手順2:SECONDARY でデータを読み取る

SECONDARY はデフォルトで読み取りを拒否します。setReadPref('secondaryPreferred') を指定してから確認します。




ubuntu@linuxlab: ~ (mongo2r SECONDARY)
$ docker exec mongo2r mongosh –quiet –eval “
db.getMongo().setReadPref(‘secondaryPreferred’);
db = db.getSiblingDB(‘testdb’);
db.users.find({},{name:1,role:1,_id:0}).forEach(d => print(‘ -‘, d.name,’/’,d.role));”
Count on SECONDARY(mongo2r): 3
– Charlie / user
– Bob / user
– Alice / admin

PRIMARY で書き込んだ3件が、すぐに SECONDARY にも反映されていました。oplog を介したリアルタイム同期が正常に動いています。

手順3:mongo-express(WebUI)で見てみる

コマンド出力だけだと実感がわきにくいので、MongoDB の定番 WebUI である mongo-express を同じネットワークに繋いで確認しました。レプリカセットの接続文字列を渡すだけで、現在の PRIMARY に自動接続してくれます。




ubuntu@linuxlab: ~
$ docker run -d –name mongo-express –network mongonet -p 8081:8081 \
-e ME_CONFIG_MONGODB_URL=”mongodb://mongo1r:27017,mongo2r:27017,mongo3r:27017/?replicaSet=rs0″ \
mongo-express:1.0.2
# ブラウザで http://localhost:8081 を開く(初期認証は admin / pass)
mongo-express トップ画面:MongoDB 7.0.37 とデータベース一覧(実画面)
mongo-express トップ画面:MongoDB 7.0.37 とデータベース一覧(実画面)

トップ画面の Server Status に MongoDB Version 7.0.37Hostname に現在の接続先ノードが表示されています。testdb をクリックすると、先ほど作ったコレクションが見えます。

mongo-express の testdb コレクション一覧(実画面)
mongo-express の testdb コレクション一覧(実画面)

さらに users コレクションを開くと、PRIMARY に書き込んだ Alice / Bob / Charlie の3ドキュメントがそのまま表示されました。SECONDARY 側でも同じデータが見えるので、複製が効いていることが視覚的にも分かります。

mongo-express の users コレクション:3ドキュメント表示(実画面)
mongo-express の users コレクション:3ドキュメント表示(実画面)

フェイルオーバーを実際に試す(実測)

正直、これが一番確かめたいポイントです。PRIMARY を停止して、本当に自動でフェイルオーバーするか実測しました。ここで意外な発見がありました

手順1:PRIMARY(mongo1r)を停止する




ubuntu@linuxlab: ~
$ docker stop mongo1r
mongo1r

手順2:停止後に rs.status() を確認する

自動フェイルオーバー確認(実測)
自動フェイルオーバー確認(実測)



ubuntu@linuxlab: ~ (mongo2r で確認)
$ docker exec mongo2r mongosh –quiet –eval “rs.status()”
0 mongo1r:27017 health=0 (not reachable/healthy) uptime=0
1 mongo2r:27017 health=1 PRIMARY uptime=41 ← 自動昇格
2 mongo3r:27017 health=1 SECONDARY uptime=30

ここで驚いたのが速度です。PRIMARY 停止からわずか1.2秒で mongo2r が自動昇格しました。よく「フェイルオーバーは約15秒」と紹介されますが、docker stop(SIGTERM=正常終了シグナル)の場合は mongod が「私は降格します」と周囲に伝えるため、即座に再選挙が走るのです。

手順3:強制クラッシュさせると何秒かかるか

では「約15秒」はどこから来るのか。docker kill(SIGKILL=強制終了)でプロセスを問答無用で殺すクラッシュ状態を再現して計測し直しました。




ubuntu@linuxlab: ~
$ docker kill -s KILL mongo2r # 現PRIMARYを強制クラッシュ
mongo2r
# 残ノードをポーリングして新PRIMARY昇格までを計測
NEW PRIMARY = mongo3r:27017 after 10.2s
フェイルオーバー時間:正常停止 vs 強制クラッシュ(実測)
フェイルオーバー時間:正常停止 vs 強制クラッシュ(実測)

結果は 10.2秒。これは rs.conf().settings.electionTimeoutMillis の既定値が 10000ミリ秒(10秒)だからです。強制クラッシュでは心拍(heartbeat)が途絶えたことを10秒待ってから選挙を始めるので、ここまで時間がかかります。まとめると次のとおりです。

  • 計画的なメンテ再起動(docker stop / systemctl stop)→ 約1秒でほぼ無停止
  • サーバー突然死(電源断・カーネルパニック・kill -9)→ 約10秒で自動復旧

アプリ側から見ると、強制クラッシュの10秒間は一時的な接続エラーが出ます。ただし接続文字列に全ノードを書いておけば、自動で新 PRIMARY に繋ぎ直してくれます。

手順4:旧 PRIMARY を再起動して復帰させる




ubuntu@linuxlab: ~
$ docker start mongo1r
mongo1r
$ docker exec mongo2r mongosh –quiet –eval “rs.status()” # 15秒後
0 mongo1r:27017 health=1 SECONDARY uptime=12 ← 自動復帰
1 mongo2r:27017 health=1 PRIMARY uptime=58
2 mongo3r:27017 health=1 SECONDARY uptime=47

再起動後、mongo1r は 手動操作なしで SECONDARY として自動復帰しました。PRIMARY は mongo2r のまま稼働を継続し、mongo1r は停止中に溜まった oplog の差分を受け取って同期を完了させます。

VPS 本番環境での設定ポイント

VPS で3台構成を組む場合、追加で設定すべき項目があります。

/etc/mongod.conf の設定




ubuntu@mongo1: ~
$ sudo vi /etc/mongod.conf
# 以下を設定(抜粋)
net:
port: 27017
bindIp: 0.0.0.0 # ← 全IPでリッスン(UFWで必ず保護)
replication:
replSetName: “rs0” # ← レプリカセット名(3台で統一)
$ sudo systemctl restart mongod
$ sudo systemctl status mongod
● mongod.service – MongoDB Database Server
Active: active (running) …

ファイアウォール(UFW)の設定

セキュリティ上の注意

bindIp: 0.0.0.0 にした場合、27017ポートをインターネットに晒さないよう UFW で必ず制限してください。MongoDB のノード間通信は内部 IP のみに限定するのが原則です。




ubuntu@mongo1: ~
# mongo2, mongo3 のプライベートIPのみ 27017 を許可
$ sudo ufw allow from 192.168.0.102 to any port 27017
$ sudo ufw allow from 192.168.0.103 to any port 27017
Rule added
Rule added

メンテナンス用 SSH 鍵を用意する

MongoDB 自体は SSH を使いませんが、各ノードを保守する際のアクセス用に ed25519 鍵を用意しておくと安全です。コンテナ(OpenSSH_8.9p1)で実際に生成したところ、ed25519 の公開鍵は 95バイトとコンパクトで、RSA 4096(739バイト)に比べて扱いやすいです。




ubuntu@mongo1: ~
$ ssh -V
OpenSSH_8.9p1 Ubuntu-3ubuntu0.15, OpenSSL 3.0.2 15 Mar 2022
$ ssh-keygen -t ed25519 -f ~/.ssh/mongo_admin -N “” -C “ubuntu@mongo1”
Generating public/private ed25519 key pair.
Your identification has been saved in ~/.ssh/mongo_admin
The key fingerprint is:
SHA256:y8Bb7yK164dDi7FKv1CI3H9EvrXQdJ6CT6fDBHaajJc ubuntu@mongo1
$ wc -c < ~/.ssh/mongo_admin.pub # ed25519 公開鍵
95

ディスク性能について

MongoDB は書き込み速度がパフォーマンスに直結します。dd でコンテナの /data/db のディスク I/O を実測したところ、書き込み 659 MB/s(direct I/O、1GiB を1.63秒)、読み取り 1.4 GB/s でした(計測環境は Docker Desktop の仮想ディスク上)。VPS の本番(NVMe SSD)でも書き込み 200〜500 MB/s 前後が目安で、特に書き込み性能がレプリケーション遅延に影響します。

MongoDBデータ領域のディスクI/O実測(dd)
MongoDBデータ領域のディスクI/O実測(dd)

アプリ側の接続文字列

フェイルオーバーに対応するため、接続文字列には 全ノードのアドレスを列挙します。これがあるからこそ、PRIMARY が切り替わってもアプリは自動で追従できます。




アプリ設定例(Node.js / Python 共通)
# 全ノードを列挙することでフェイルオーバー時に自動切替
mongodb://192.168.0.101:27017,192.168.0.102:27017,192.168.0.103:27017/?replicaSet=rs0
# 書き込み保証を強化する場合は w=majority を追加
mongodb://…/?replicaSet=rs0&w=majority&journal=true

よくあるエラーと解決策

エラーメッセージ 原因 解決策
already initialized rs.initiate() を二重実行 rs.status() で現状を確認。再設定は rs.reconfig()
Quorum check failed メンバーに接続できない ホスト名・ポートと UFW を確認。コンテナなら同一 Docker ネットワーク上か確認
SECONDARY が PRIMARY に昇格しない 過半数割れ(2ノード停止) 3ノードでは最低2ノード稼働が必要。停止したノードを再起動する
not primary and secondaryOk=false SECONDARY への直接読み取りがデフォルト拒否 setReadPref('secondaryPreferred')readPreference=secondaryPreferred を設定
replSetName does not match 3台のレプリカセット名が不一致 全ノードの mongod.confreplSetName を同じ値に統一
著者アイコン
著者アイコン

個人的にハマったのは「フェイルオーバーが速すぎて逆に不安になる」ことでした。docker stop だと1秒で切り替わるので、ちゃんと動いているのか rs.status() を何度も叩いて確認しました。本番のサーバー障害を想定するなら docker kill でテストするのがおすすめです。

まとめ

MongoDB レプリカセット(3ノード構成)の構築と動作確認を実測で行いました。

  • MongoDB 7.0.37 は公式 Docker イメージ mongo:7.0 で即座に利用可能
  • rs.initiate() 一発で PRIMARY×1 + SECONDARY×2 の冗長構成が完成
  • 正常停止(docker stop)なら約1.2秒、強制クラッシュ(docker kill)でも約10秒で自動フェイルオーバー
  • 10秒の差は electionTimeoutMillis(既定10000ミリ秒)によるもの
  • 停止した旧 PRIMARY は再起動後に SECONDARY として自動復帰
  • 接続文字列に全ノードを列挙すればアプリ側も自動切替に対応

レプリカセットは「設定が難しそう」と思われがちですが、実際には rs.initiate() の1コマンドで完成します。本番 VPS に移す際は UFW でポートを内部 IP のみに制限することを忘れずに。3ノードを別サーバーに分散させることで、はじめて冗長構成の意味が出ます。

MongoDB を本番運用するなら、3ノードを別々のサーバーに分散させる必要があります。コストパフォーマンスの高い VPS を選ぶなら Vultr の東京リージョンがおすすめです。


Vultr 公式サイトで東京リージョンのプランを確認する →

コメント

タイトルとURLをコピーしました