前言
由于公司業(yè)務(wù)需要,我們打算自己搭建 MongoDB 的服務(wù),因為 MongoDB 的云數(shù)據(jù)庫好貴,我們這次采用副本集的方式來搭建集群,三臺服務(wù)器,一主、一副、一仲裁
基本概念
Replica Set 副本集:一個副本集就是一組 MongoDB 實例組成的集群,由一個主(Primary)服務(wù)器和多個備份(Secondary)服務(wù)器構(gòu)成
- 主節(jié)點(master):主節(jié)點接收所有寫入操作。主節(jié)點將對其數(shù)據(jù)集所做的所有更改記錄到其 oplog。
- 副節(jié)點(secondary):復(fù)制主節(jié)點的 oplog 并將操作應(yīng)用到其數(shù)據(jù)集,如果主節(jié)點不可用,一個合格的副節(jié)點將被選為新的主節(jié)點。
- 仲裁節(jié)點(arbiter):負(fù)載選舉,當(dāng)主節(jié)點不可用,它將從副節(jié)點中選一個作為主節(jié)點。
Sharding 分片:
Master-slave 主備
- MongoDB 4.0 以上版本運(yùn)行時提示:[main] Master/slave replication is no longer supported,也就是 MongoDB 4.0 后不在支持主從復(fù)制
一、環(huán)境準(zhǔn)備
使用 CentOS 7.6 64bit 系統(tǒng),安裝 Docker、Docker-compose、Docker-Swarm
二、生成 KeyFile
- MongoDB 使用 KeyFile 認(rèn)證,副本集中的每個 MongoDB 實例使用 KeyFile 內(nèi)容作為認(rèn)證其他成員的共享密碼。MongoDB 實例只有擁有正確的 KeyFile 才可以加入副本集。
- keyFile 的內(nèi)容必須是 6 到 1024 個字符的長度,且副本集所有成員的 KeyFile 內(nèi)容必須相同。
- 有一點要注意是的:在 UNIX 系統(tǒng)中,KeyFile 必須沒有組權(quán)限或完全權(quán)限(也就是權(quán)限要設(shè)置成 X00 的形式)。Windows 系統(tǒng)中,keyFile 權(quán)限沒有被檢查。
- 可以使用任意方法生成 keyFile。例如,如下操作使用 openssl 生成復(fù)雜的隨機(jī)的 1024 個字符串。然后使用 chmod 修改文件權(quán)限,只給文件擁有者提供讀權(quán)限。
- 這是 MongoDB 官方推薦 keyFile 的生成方式:
# 400權(quán)限是要保證安全性,否則mongod啟動會報錯
openssl rand -base64 756 > mongodb.key
chmod 400 mongodb.key
二、創(chuàng)建跨主機(jī)網(wǎng)絡(luò)
搭建集群我們肯定是跨主機(jī)通訊,要搭建 Overlay Network 網(wǎng)絡(luò),我們就要用到 Docker Swarm 這個工具了。Docker Swarm 是 Docker 內(nèi)置的集群工具,它能夠幫助我們更輕松地將服務(wù)部署到 Docker daemon 的集群之中。
既然要將 Docker 加入到集群,我們就必須先有一個集群,我們在任意一個 Docker 實例上都可以通過 docker swarm init 來初始化集群。
$ sudo docker swarm init
Swarm initialized: current node (t4ydh2o5mwp5io2netepcauyl) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-4dvxvx4n7magy5zh0g0de0xoues9azekw308jlv6hlvqwpriwy-cb43z26n5jbadk024tx0cqz5r 192.168.1.5:2377
在集群初始化后,這個 Docker 實例就自動成為了集群的管理節(jié)點,而其他 Docker 實例可以通過運(yùn)行這里所打印的 docker swarm join 命令來加入集群。
加入到集群的節(jié)點默認(rèn)為普通節(jié)點,如果要以管理節(jié)點的身份加入到集群中,我們可以通過 docker swarm join-token 命令來獲得管理節(jié)點的加入命令。
$ sudo docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-60am9y6axwot0angn1e5inxrpzrj5d6aa91gx72f8et94wztm1-7lz0dth35wywekjd1qn30jtes 192.168.1.5:2377
我們通過這些命令來建立用于我們服務(wù)開發(fā)的 Docker 集群,并將相關(guān)開發(fā)同事的 Docker 加入到這個集群里,就完成了搭建跨主機(jī)網(wǎng)絡(luò)的第一步。
建立跨主機(jī)網(wǎng)絡(luò)
接下來,我們就通過 docker network create
命令來建立 Overlay 網(wǎng)絡(luò)。
$ sudo docker network create --driver overlay --attachable mongodbs
在創(chuàng)建 Overlay 網(wǎng)絡(luò)時,我們要加入 --attachable 選項以便不同機(jī)器上的 Docker 容器能夠正常使用到它。
在創(chuàng)建了這個網(wǎng)絡(luò)之后,我們可以在任何一個加入到集群的 Docker 實例上使用 docker network ls 查看一下其下的網(wǎng)絡(luò)列表。我們會發(fā)現(xiàn)這個網(wǎng)絡(luò)定義已經(jīng)同步到了所有集群中的節(jié)點上。
$ sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
## ......
y89bt74ld9l8 mongodbs overlay swarm
## ......
接下來我們要修改 Docker Compose 的定義,讓它使用這個我們已經(jīng)定義好的網(wǎng)絡(luò),而不是再重新創(chuàng)建網(wǎng)絡(luò)。
我們只需要在 Docker Compose 配置文件的網(wǎng)絡(luò)定義部分,將網(wǎng)絡(luò)的 external 屬性設(shè)置為 true,就可以讓 Docker Compose 將其建立的容器都連接到這個不屬于 Docker Compose 的項目上了。
networks:
mesh:
external: true
通過這個實現(xiàn),我們在開發(fā)中就使整個服務(wù)都處于一個可以使用別名映射網(wǎng)絡(luò)中,避免了要對不同功能聯(lián)調(diào)時切換服務(wù) IP 的煩瑣流程。在這種結(jié)構(gòu)下,我們只需要讓我們開發(fā)的 Docker 退出和加入不同的集群,就能馬上做到切換不同聯(lián)調(diào)項目。
二、編寫 docker-compose 文件
主節(jié)點
version: "3"
services:
master:
image: mongo:4.1
container_name: master
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 123456
TZ: "Asia/Shanghai"
volumes:
# 掛載 MongoDB 數(shù)據(jù)目錄
- "/data/docker/mongodb/data/mongo:/data/db:rw"
# 掛載 KeyFile
- "/data/docker/mongodb/data/mongodb.key:/data/mongodb.key"
ports:
- "27018:27017"
networks:
- mongodbs
command:
# 密碼
--auth
# 副本集名稱
--replSet testSet
--oplogSize 128
--keyFile /data/mongodb.key
# Swarm 跨主機(jī)網(wǎng)絡(luò)網(wǎng)絡(luò)
networks:
mongodbs:
external: true
副節(jié)點
version: "3"
services:
secondary:
image: mongo:4.1
container_name: secondary
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 123456
TZ: "Asia/Shanghai"
volumes:
- "/data/docker/mongodb/data/mongo:/data/db:rw"
- "/data/docker/mongodb/data/mongodb.key:/data/mongodb.key"
ports:
- "27018:27017"
networks:
- mongodbs
command:
--auth
--replSet testSet
--oplogSize 128
--keyFile /data/mongodb.key
networks:
mongodbs:
external: true
仲裁節(jié)點,因為仲裁節(jié)點不需要存儲數(shù)據(jù),他只是用來當(dāng)主節(jié)點掛掉后選舉新的主節(jié)點,所以不需要密碼、映射端口等操作
version: "3"
services:
arbiter:
image: mongo:4.1
container_name: arbiter
restart: always
volumes:
- "/data/docker/mongodb/data/mongo:/data/db:rw"
- "/data/docker/mongodb/data/mongo_key:/mongo:rw"
networks:
- mongodbs
command:
mongod --replSet testSet --smallfiles --oplogSize 128
networks:
mongodbs:
external: true
三、啟動容器
接下來我們分別在三臺服務(wù)器中使用容器編排啟動容器
四、配置副本集
進(jìn)入主節(jié)點容器內(nèi)部
docker exec -it master mongo
在 mongo shell 里執(zhí)行:
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "7abd89794aa7:27017",
"ok" : 1
}
繼續(xù)執(zhí)行:
testSet:SECONDARY> rs.add('secondary:27017')
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1599562800, 1),
"signature" : {
"hash" : BinData(0,"wrxMUIX/0bEyLgCVoQqdLvH59T0="),
"keyId" : NumberLong("6870069879538450434")
}
},
"operationTime" : Timestamp(1599562800, 1)
}
繼續(xù)執(zhí)行,其中 true 表示這個節(jié)點是仲裁節(jié)點
testSet:PRIMARY> rs.add('arbiter:27017',true)
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1599562838, 1),
"signature" : {
"hash" : BinData(0,"p9ub49lLD8ij8nkxpfu2l/AvRRY="),
"keyId" : NumberLong("6870069879538450434")
}
},
"operationTime" : Timestamp(1599562838, 1)
}
查看配置
testSet:PRIMARY> rs.conf()
{
"_id" : "testSet",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "7abd89794aa7:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "secondary:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "arbiter:27017",
"arbiterOnly" : true,
"buildIndexes" : true,
"hidden" : false,
"priority" : 0,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("5f576426fe90ef2dd8cd2700")
}
}
查看狀態(tài)
testSet:PRIMARY> rs.status()
{
"set" : "testSet",
"date" : ISODate("2020-09-08T11:45:12.096Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2020-09-08T11:45:02.775Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2020-09-08T11:45:02.775Z"),
"appliedOpTime" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2020-09-08T11:45:02.775Z"),
"lastDurableWallTime" : ISODate("2020-09-08T11:45:02.775Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1599565492, 1),
"lastStableCheckpointTimestamp" : Timestamp(1599565492, 1),
"members" : [
{
"_id" : 0,
"name" : "7abd89794aa7:27017",
"ip" : "10.0.1.41",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2784,
"optime" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-08T11:45:02Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1599562790, 2),
"electionDate" : ISODate("2020-09-08T10:59:50Z"),
"configVersion" : 3,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "secondary:27017",
"ip" : "10.0.1.233",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 2711,
"optime" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1599565502, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-08T11:45:02Z"),
"optimeDurableDate" : ISODate("2020-09-08T11:45:02Z"),
"lastHeartbeat" : ISODate("2020-09-08T11:45:11.494Z"),
"lastHeartbeatRecv" : ISODate("2020-09-08T11:45:11.475Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "7abd89794aa7:27017",
"syncSourceHost" : "7abd89794aa7:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "arbiter:27017",
"ip" : null,
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"lastHeartbeat" : ISODate("2020-09-08T11:45:10.463Z"),
"lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Error connecting to arbiter:27017 :: caused by :: Could not find address for arbiter SocketException: Host not found (authoritative)",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : -1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1599565502, 1),
"signature" : {
"hash" : BinData(0,"7/ei+8UrhlpIny9zKeWuAFpn46c="),
"keyId" : NumberLong("6870069879538450434")
}
},
"operationTime" : Timestamp(1599565502, 1)
}
五、驗證 MongoDB 可用性
先進(jìn)入主節(jié)點服務(wù)器添加一條數(shù)據(jù)
docker exec -it master mongo
use admin
db.auth('root', '123456')
use test
db.test.insert({name:"muyang",age:20})
在來副節(jié)點服務(wù)器查看是否已經(jīng)同步了這條數(shù)據(jù)
[root@linux secondary] docker exec -it secondary mongo
testSet:SECONDARY> use admin
testSet:SECONDARY> db.auth('root', '123456')
testSet:SECONDARY> use test
testSet:SECONDARY> db.test.find()
2020-09-08T19:03:02.295+0800 E QUERY [js] uncaught exception: Error: listCollections failed: {
"operationTime" : Timestamp(1599562972, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1599562972, 1),
"signature" : {
"hash" : BinData(0,"mhsrpGHRl7qZg2QOjyS3RbBb/Yc="),
"keyId" : NumberLong("6870069879538450434")
}
}
} :
testSet:SECONDARY> rs.slaveOk()
testSet:SECONDARY> db.users.find()
{ "_id" : ObjectId("5f5764b1f909544b783696c2"), "name" : "muyang", "age" : 20 }
在 secondary 查詢時報如下錯誤:
not master and slaveok=false
這是正常的,因為 secondary 是不允許讀寫的,如果非要解決,方法如下:
testSet:SECONDARY> rs.slaveOk()
到此這篇關(guān)于Docker 搭建集群MongoDB的實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)Docker 搭建集群MongoDB內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!