一、准备环境
MongoDB 副本集部署至少 3 个节点(奇数节点),为了保障数据安全性,可考虑将 MongoDB 节点分布在不同的主机上,本示例使用一台主机部署 3 个 MongoDB示例。
1、创建 MongoDB 集群数据相关目录
# 创建 MongoDB 集群根目录及 3 个子目录
mkdir -p /mongodb-cluster/{mongo01, mongo02, mongo03}
# 创建 3 个 MongoDB 下子目录
mkdir -p /mongodb-cluster/mongo01/{certs, configdb, data, db, logs}
mkdir -p /mongodb-cluster/mongo02/{certs, configdb, data, db, logs}
mkdir -p /mongodb-cluster/mongo03/{certs, configdb, data, db, logs}
# 准备给上面 logs 目录赋予操作权限(注意:未赋予 logs 目录权限,运行会报错)
chmod 777 /mongodb-cluster/mongo01/logs
chmod 777 /mongodb-cluster/mongo01/logs
chmod 777 /mongodb-cluster/mongo01/logs
2、生成服务端、集群内部成员、客户端相关证书
由于部署 MongoDB 副本集,集群成员和客户端需要与服务端进行认证通信,保证通信的可靠性,目前 MongoDB 提供如下方式进行通信认证:
security.clusterAuthMode
类型:String
默认值:keyFile
用于集群认证的方式。如果您使用 内部 x.509 身份验证,请在此处指定。此选项可以具有以下值之一:
价值 | 描述 |
---|---|
keyFile | 使用密钥文件进行身份验证。只接受密钥文件。 |
sendKeyFile | 用于滚动升级目的。发送密钥文件进行身份验证,但可以同时接受密钥文件和 x.509 证书。 |
sendX509 | 用于滚动升级目的。发送 x.509 证书进行身份验证,但可以同时接受密钥文件和 x.509 证书。 |
x509 | 受到推崇的。发送 x.509 证书进行身份验证并仅接受 x.509 证书。 |
通过上面说的几种通信认证方式,默认 keyFile 密钥文件方式认证,这种方式也是最简单的,只能用于测试环境下使用,通常我们为了服务端和客户端通信安全,则会使用 x509 认证方式,下面我就使用 x509 方式进行配置 TLS 协议认证。
注意:因为大多数 CA 高可靠证书,都是由第三方厂商提供,需要收费的。(这里自己通过 openssl 生成自签名 CA 证书,实现通信认证)。
自签名证书生成
(1)、生成根证书 ca.pem
# 生成 CA 私钥(ca.key)(不加密)
openssl genrsa -out ca.key 2048
# 生成 CA 证书签名请求(ca.csr)
# 每个集群成员证书和服务端证书中必须具有相同的 O、OU 和 DC (具体可参考:https://www.mongodb.com/docs/manual/core/security-x.509)
openssl req -new -key ca.key -out ca.csr # 注意:在执行这条命令后,需要按要求填写内容,最后面两项可选,密码可不填
# 生成自签名 CA 证书(ca.pem), 直接免费 100 年有效
openssl x509 -req -days 36500 -in ca.csr -signkey ca.key -out ca.pem
(2)、生成服务端证书 server.pem
# 生成 server 端私钥 (server.key) 不加密
openssl genrsa -out server.key 2048
# 生成 server 证书签名请求 (server.csr)
openssl req -new -key server.key -out server.csr # 注意:输入内容和上面的保持一致
# 使用 ca 证书签署服务端 csr 以生成服务端证书 (server.crt)
openssl ca -days 36500 -in server.csr -out server.crt -cert ca.pem -keyfile ca.key # 注意:执行这条命令会出现 2 处错误,按如下方式解决,再执行
# 第一个错误
/etc/pki/CA/index.txt No such file or directory
# 直接执行如下命令
touch /etc/pki/CA/index.txt
# 第二个错误
/etc/pki/CA/serial No such file or directory
# 直接执行如下命令后,文件里面输入数字,如:01,再保存
vim /etc/pki/CA/serial
# 解决完问题后,再执行上面的命令对证书进行签名(后面输入区域,都输入 y 即可)
openssl ca -days 36500 -in server.csr -out server.crt -cert ca.pem -keyfile ca.key
# 删掉 server.crt 中的 certificate 信息,保留加密证书内容即可
# 合并证书和私钥成 PEM 文件, 构建命令如下:
cat server.key server.crt > server.pem
# 再验证自签名证书是否成功(返回,OK 代表成功)
openssl verify -CAfile ca.pem server.pem
(3)、生成客户端证书 client.pem(流程和服务端一样,只是稍微改了下名)
# 生成 client 端私钥 (client.key) 不加密
openssl genrsa -out client.key 2048
# 生成 client 证书签名请求 (client.csr)
openssl req -new -key client.key -out client.csr # 注意:输入内容和上面的保持一致
# 使用 ca 证书签署客户端 csr 以生成客户端证书 (client.crt)
openssl ca -days 36500 -in client.csr -out client.crt -cert ca.pem -keyfile ca.key
# 注意:这条命令运行后会出现 2 个错误,是因为之前签署了服务端证书,前面自己创建的两个文件已存在,所以,需要把 /etc/pki/CA 下的 index.txt 和 serial 文件删除,再重新执行该命令,再解决前面提到的错误就可以了
# 解决完问题后,再执行上面的命令对证书进行签名(后面输入区域,都输入 y 即可)
openssl ca -days 36500 -in client.csr -out client.crt -cert ca.pem -keyfile ca.key
# 删掉 client.crt 中的 certificate 信息,保留加密证书内容即可
# 合并证书和私钥成 PEM 文件, 构建命令如下:
cat client.key client.crt > client.pem
# 再验证自签名证书是否成功(返回,OK 代表成功)
openssl verify -CAfile ca.pem client.pem
(4)、生成服务端成员证书 cluster.pem(流程和客户端一样,只是稍微改了下名),这里就不写了。
二、创建 MongoDB 集群的核心文件并配置
1、将前面生成的所有 PEM 证书 copy 到各 mongo 下 certs 目录中
# 执行如下命令
cp 文件路径 /mongodb-cluster/mongo01/certs
cp 文件路径 /mongodb-cluster/mongo02/certs
cp 文件路径 /mongodb-cluster/mongo03/certs
# 注意:确认包含如下 4 个文件
ca.pem client.pem cluster.pem server.pem
2、创建各示例的 mongod.conf 核心文件并配置
# 创建 mongod.conf 配置文件
touch /mongodb-cluster/mongo01/configdb/mongod.conf
touch /mongodb-cluster/mongo02/configdb/mongod.conf
touch /mongodb-cluster/mongo03/configdb/mongod.conf
在各个示例 mongod.conf 文件里面写入如下内容:
注意:每个示例配置的端口号 27017 可不改,因为这是容器里面的端口,也可以根据需求更改
net:
bindIp: 0.0.0.0 # 配置允许所有主机连接,默认只能本地连接
port: 27017 # 因为在一台主机中,三个示例的端口对应 27017、27018、27019 或者 其它不冲突的端口也可以
tls:
CAFile: /data/certs/ca.pem # 容器内映射路径,在本配置文件下,所有配置的路径都是容器路径,作为宿主机的映射路径
certificateKeyFile: /data/certs/server.pem
clusterFile: /data/certs/cluster.pem
allowInvalidCertificates: true
allowInvalidHostnames: true
allowConnectionsWithoutCertificates: true
mode: requireTLS # MongoDB 认证模型,使用 TLS, 默认不使用 TLS
processManagement:
fork: false # 是否开启以守护进程模式在后台运行
replication:
replSetName: rs0 # 副本集名称
security:
clusterAuthMode: x509 # 集群安全认证模型使用 x509
authorization: enabled # 启用基于 RBAC 权限访问操作
storage:
engine: wiredTiger # 6.0 以上版本默认使用 wiredTiger 引擎, 从 4.2 版本移除了 MMAPv1 存储引擎
# 具体参数配置可参考:https://www.mongodb.com/docs/manual/reference/configuration-options/#storage-options
wiredTiger:
engineConfig:
cacheSizeGB: 0.5
journalCompressor: zstd # 默认使用 snappy 方式压缩,这里使用 zstd 高效压缩方式
zstdCompressionLevel: 6 # 设置 zstd 方式压缩级别,默认为 6, 范围:1-22, 级别越高,压缩率也越高
collectionConfig:
blockCompressor: zstd
indexConfig:
prefixCompression: true # 开启索引前缀压缩
systemLog:
destination: file # 日志存储方式为 file, 默认为 system_log 输出方式
logAppend: true # 是否开启日志追加,默认为 false
path: /data/logs/mongod.log # 日志存储路径
3、创建 docker-compose.yaml 部署文件
# 创建 docker-compose.yaml 文件
touch /mongodb-cluster/docker-compose.yaml
在 docker-compose.yaml 文件里面写入如下内容:
version: "3.9"
services:
mongo01:
container_name: mongo01 # 容器名
image: mongo # 拉取的镜像,未指定版本,默认 latest 最新版
ports:
- 27017:27017 # 容器内端口号映射,如:宿主机端口:容器端口
environment:
TZ: Asia/Shanghai # 配置时区信息
volumes:
- /etc/localtime:/etc/localtime
- ./mongo01/db:/data/db # 挂载数据 db 目录, 默认容器内是 /data/db, 因为前面没配置 storage.dbPath 路径
- ./mongo01/configdb:/data/configdb # 挂载前面的 mongod.conf 配置文件, 默认容器内 /data/configdb
- ./mongo01/certs:/data/certs # 这个是自定义挂载的 TLS 相关配置
- ./mongo01/logs:/data/logs # 挂载容器内日志目录
restart: always
command: mongod --replSet rs0 -f /data/configdb/mongod.conf # 容器启动后,需要执行的命令
networks:
- mongo_network # 使用自定义网络
mongo02:
container_name: mongo02
image: mongo
ports:
- 27018:27018
environment:
TZ: Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime
- ./mongo02/db:/data/db
- ./mongo02/configdb:/data/configdb
- ./mongo02/certs:/data/certs
- ./mongo02/logs:/data/logs
restart: always
command: mongod --replSet rs0 -f /data/configdb/mongod.conf
networks:
- mongo_network
mongo03:
container_name: mongo03
image: mongo
ports:
- 27019:27019
environment:
TZ: Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime
- ./mongo03/db:/data/db
- ./mongo03/configdb:/data/configdb
- ./mongo03/certs:/data/certs
- ./mongo03/logs:/data/logs
restart: always
command: mongod --replSet rs0 -f /data/configdb/mongod.conf
networks:
- mongo_network
# 创建容器网络
networks:
mongo_network:
name: mongo_network
三、启动 MongoDB 集群并初始化及配置客户端认证
1、启动 MongoDB 集群
# 使用如下命令启动
docker-compose up -d
# 查看容器日志(未输出错误信息或没有日志信息则成功)
docker logs -f mongo01 -n 100
# 需要查看具体容器输出日志,可在前面配置的日志目录中查看
2、进入其中一个容器,进行集群初始化(在 3 个示例中随便进入哪个都行,但只要对一个容器内做初始化即可)
# 进入容器
docker exec -it mongo01 /bin/bash
# 进入 mongo 服务端, 需要指定客户端签名证书(注意:MongoDB 6.0 以上版本, 客户端命令为 mongosh)
# --host 是你前面生成签名证书时填的 Common Name 的值, 简称:CN, 可根据需求填写, 配置写错了, 会导致连接有问题
# 具体可参考:https://www.mongodb.com/docs/manual/core/security-x.509
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27017
# 进入 mongo 服务端后,需要先创建一个用户并赋予权限,不然,操作会出错
# 执行如下命令, 创建用户(注意:在执行命令之前,需要先 use admin 切换到 admin 库)
# 具体可参考:https://www.mongodb.com/docs/manual/reference/built-in-roles
db.createUser(
{
user:"root", # 用户名
pwd:"123456", # 密码
roles:[{role:"root",db:"admin"}] # 可配置多个角色, root 角色表示拥有超级用户角色, 作用于 admin 数据库
}
);
# 使用刚创建的 root 用户登录认证, 进行执行接下来的操作
# root 用户登录认证(注意:需要在 admin 库执行, use admin 切换命令)
db.auth("root", "123456")
# 执行如下命令,初始化 mongo 副本集, 返回 ok 代表已初始化成功
rs.initiate( {
_id : "rs0", # 副本集名称
members: [
{ _id: 0, host: "172.21.0.3:27017" }, # mongo 实例 IP, 配置成宿主机 IP 即可
{ _id: 1, host: "172.21.0.4:27018" },
{ _id: 2, host: "172.21.0.2:27019" }
]
})
# 再执行 rs.status() 命令查看集群状态, 也可通过 rs.conf() 命令查看集群节点配置信息
3、使用 x509 证书对客户端进行身份验证
将 x.509 证书添加 subject 为用户要使用客户端证书进行身份验证,您必须首先将来自客户端证书的值 subject 作为 MongoDB 用户添加到 $external 数据库中。每个唯一的 x.509 客户端证书对应一个 MongoDB 用户。您不能使用单个客户端证书对多个 MongoDB 用户进行身份验证。具体可参考:https://www.mongodb.com/docs/manual/tutorial/configure-x509-client-authentication
# 在宿主机上执行如下命令(注意:client.pem 是前面生成的客户端签名证书)
# 使用以下命令从客户端证书中检索 RFC2253 格式化:subject
openssl x509 -in client.pem -inform PEM -subject -nameopt RFC2253
# 该命令会返回 subject 字符串和证书:
subject= CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry
-----BEGIN CERTIFICATE-----
# ...
-----END CERTIFICATE-----
# 添加作为用户 RFC2253 的合规值, subject 根据需要省略空格。
# 首先, 需要进入主节点容器内 mongo 服务端, 执行如下操作:
# 进入容器
docker exec -it mongo01 /bin/bash
# 进入 mongo 服务端
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27017
# 需要切换为 admin 库, use admin 命令, 再执行 root 用户认证命令
db.auth("root", "123456")
# 以下添加一个用户并授予该用户 readWrite 在数据库中的角色 test 和 root 角色:
db.getSiblingDB("$external").runCommand(
{
createUser: "1491140482@qq.com,CN=localhost,OU=BND,O=BN,ST=JS,C=CN",
roles: [
{ role: "readWrite", db: "test" },
{ role: "root", db: "admin" }
],
writeConcern: { w: "majority" , wtimeout: 5000 }
}
)
# 客户端连接后认证, 使用如下命令:
db.getSiblingDB("$external").auth(
{
mechanism: "MONGODB-X509"
}
)
# 客户端连接期间进行身份认证, 执行如下命令:
mongosh --tls --tlsCertificateKeyFile client.pem \
--tlsCAFile ca.pem \
--authenticationDatabase '$external' \
--authenticationMechanism MONGODB-X509
四、测试 MongoDB 副本集数据同步
1、在主容器里面写入数据,再进入从容器里面查询
# 进入主容器
docker exec -it mongo01 /bin/bash
# 进入 mongo 服务端
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27017
# 切换 admin 库, use admin 命令
use admin
# db 认证
db.auth("root", "123456")
# 插入一条数据在 test 库中, 文档不存在会自动创建
db.test.insertOne( { x: 1 } )
# 查询数据
db.test.find()
# 然后, 下面再从容器里面查看是否有数据
docker exec -it mongo02 /bin/bash # 进入从容器
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27018 # 进入 mongo 服务端
use admin # 切换 admin
db.auth("root", "123456") # db 认证
db.test.find() # 查询数据, 这里会出错, 默认是强制读主的, 不允许从节点进行读操作, 只能作为备份和高可用, 需要显示开启读操作
# 解决方案, 如下:
rs.secondaryOk() or db.getMongo().setReadPref("primaryPreferred")
# 这两条命令执行其中一条即可, 再次执行 db.test.find() 查询数据就能读到了
# 注意:rs.secondaryOk() 命令在新版本被弃用了, 建议使用:db.getMongo().setReadPref("primaryPreferred") 命令进行操作
五、使用 Navicat 客户端连接 MongoDB 副本集
1、将服务器中的客户端签名证书和 CA 证书导出
包含:ca.pem 和 client.pem 两个文件
2、进行使用 Navicat 连接,如下图:
继续配置启用 SSL 模式进行连接,如下图:
然后,点击测试连接,即可连接成功。
六、其它
1、添加副本,在登录到主节点下输入
rs.add("ip:port")
2、删除副本
rs.remove("ip:port")
3、新增仲裁节点
rs.addArb("ip:port")
4、修改副本集里面的节点 IP
# 声明 cfg 变量
cfg = rs.conf()
# 执行修改各节点的 IP
cfg.members[0].host = "192.xxx.xxx.121:27017"
cfg.members[1].host = "192.xxx.xxx.121:27018"
cfg.members[2].host = "192.xxx.xxx.121:27019"
# 重新加载配置
rs.reconfig(cfg)
# 查询副本集状态
rs.status()
# 查询副本集配置
rs.conf()