最近把自己的一个小项目迁移了服务器, 结果发现接口响应速度慢了很多, 排查了不少时间后突然想起来是因为新服务器和 MongoDB Atlas 不在同一个地区, 光是二者之间的网络延迟就很高.
而 MongoDB Atlas 提供的免费实例要备份数据和迁移比较麻烦, 又因为 MongoDB 单实例不支持事务, 所以决定干脆自建一个 MongoDB 副本集 😎.
环境
本文环境本文环境
- OS: Debian Bookworm
- MongoDB: 7.0.0
- 使用 Docker 安装 MongoDB
我手上有几台不同服务商不同地区的服务器, 下文都以 Server A
, Server B
, Server C
代表.
为了申请证书, 还需要至少一个域名.
安装 MongoDB
首先要在每台服务器上都安装 MongoDB, 这里使用 Docker Compose.
# docker-compose.yml
services:
mongo:
image: mongo
# restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password
ports:
- 27017:27017
volumes:
- ./data:/data/db
- ./config:/etc/mongo
command: ["--config", "/etc/mongo/mongod.conf"]
然后 ./config
目录下新建我们的配置文件 mongod.conf
:
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: /data/db
# engine:
# wiredTiger:
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo
# where to write logging data.
# systemLog:
# destination: file
# logAppend: true
# path: /var/log/mongodb/mongod.log
暂时只需要上面这一点配置就可以啦, 此时可以先启动容器, 接下来需要配置副本集之间认证用的 keyfile.
配置节点 keyfile
生成 keyfile
keyFile 用于 MongoDB 副本集节点之间的认证, 每一个节点需要有相同的 keyFile 才能加入副本集.
keyFile 的内容就相当于密码啦, 用什么软件生成一个都行, 这里就使用 openssl
:
openssl rand -base64 756 > keyfile
然后需要把 keyfile
分发到每一个节点上, 并且仅为文件所有者提供读取权限.
修改 keyfile 权限
如果你是在本机直接安装的 MongoDB, 只需要使用 chmod
修改权限:
chmod 400 keyfile
但是如果使用 Docker 安装, 不仅要把 keyfile
挂载到容器内, 还要进入容器来修改权限, 因为容器内部的文件权限和外面是不一样的!
这里我把 keyfile 挂载到了 /etc/mongo/keyfile
然后进入容器修改权限:
docker exec -it <container_id> /bin/bash
chmod 400 /etc/mongo/keyfile
chown 999:999 /etc/mongo/keyfile
修改完权限后把容器停掉, 因为我们还要修改配置文件.
TIP提示
每一个节点都要使用相同的 keyfile 进行上面相同的操作.
配置副本集认证
配置完了所有节点的 keyfile, 现在来修改配置文件启用副本集, 依然是所有节点的配置文件都要修改.
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: /data/db
# engine:
# wiredTiger:
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo
# where to write logging data.
# systemLog:
# destination: file
# logAppend: true
# path: /var/log/mongodb/mongod.log
security:
clusterAuthMode: keyFile
authorization: enabled
keyFile: /etc/mongo/keyfile
replication:
replSetName: "rs0" # 副本集名称
如果是同机房所有节点使用内部网络的话, 这样就可以直接启动副本集了, 但是我的节点是分布在不同地区的, 所以还需要开放外部连接.
# mongod.conf
net:
port: 27017
bindIp: 0.0.0.0
既然开放了外部连接, 那么就需要对传输使用 TLS 加密.
配置 TLS
准备证书
使用 TLS 就需要证书, 这里使用 Let's Encrypt
的证书.
申请证书的方式和为网站申请证书一样, 这里不再赘述, 可以参考:
TIP提示
也可以使用自签名证书, 但其实自己维护一个 CA 比申请第三方 CA 的证书麻烦多了, 可以参考:
而且自签名证书也不够安全.
一般来说, 申请成功证书会得到两个文件: fullchain.pem
和 privkey.pem
, 分别是证书链和私钥.
但是 MongoDB 还需要 CA 证书, 可以在 Let’s Encrypt ISRG Root X1 下载.
接下来需要把 fullchain.pem
, privkey.pem
'合并' 为一个文件 (MongoDB 要求的!)
cat fullchain.pem privkey.pem > mongod.pem
其实就是把 fullchain.pem
和 privkey.pem
的内容拼接到一起, 得到的 mongod.pem
就是 MongoDB 需要的证书文件.
可以使用泛域名证书可以使用泛域名证书
如果你打算把所有服务器解析到一个域名上, 那么可以使用泛域名证书, 这样就不用为每一个节点都单独申请证书了.
配置证书
准备好每个节点自己的 mongod.pem
, 把 CA 根证书 isrgrootx1.pem
拷贝给每一个节点. 我这里仍是直接挂载在了容器的 /etc/mongo/
目录.
然后修改它们的配置文件:
net:
port: 27017
bindIp: 0.0.0.0
tls:
mode: requireTLS
certificateKeyFile: /etc/mongo/mongod.pem
CAFile: /etc/mongo/isrgrootx1.pem
allowConnectionsWithoutCertificates: true
关于 net.tls
的配置详情参考:
配置完证书后可以准备启动副本集了.
完整 mongod.conf
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: /data/db
net:
port: 27017
bindIp: 0.0.0.0
tls:
mode: requireTLS
certificateKeyFile: /etc/mongo/mongodb.pem
CAFile: /etc/mongo/isrgrootx1.pem
allowConnectionsWithoutCertificates: true
# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo
security:
clusterAuthMode: keyFile
authorization: enabled
keyFile: /etc/mongo/keyfile
replication:
replSetName: "rs0"
启动副本集
示例节点名 | 域名 |
---|---|
Server A | servera.mongodb.example.com |
Server B | serverb.mongodb.example.com |
Server C | serverc.mongodb.example.com |
启动所有节点, 然后使用支持的客户端连接你打算作为主节点的节点, 使用 rs.initiate()
初始化副本集, 以 Server A
为例:
WARNING注意
只在一个节点上执行 rs.initiate()
!
rs.initiate( {
_id : "rs0",
members: [
{ _id: 0, host: "servera.mongodb.example.com:27017" },
{ _id: 1, host: "serverb.mongodb.example.com:27017" },
{ _id: 2, host: "serverc.mongodb.example.com:27017" }
]
})
或者, 也可以不向 rs.initiate()
传入配置, 而是初始化后再添加节点:
rs.initiate()
rs.add("serverb.mongodb.example.com:27017")
rs.add("serverc.mongodb.example.com:27017")
参考:
如果你的操作都正确, 那这里应该已经启动成功了 😇. 使用 rs.status()
查看副本集状态:
PRIMARY
就是主节点, SECONDARY
是从节点.
如果你发现有其他状态的节点, 可以查看 副本集成员状态 排查问题.
连接副本集
在上文, 我们连接到了主节点来初始化副本集, 它的连接字符串通常是像这样的:
mongodb://user:password@servera.mongodb.example.com:27017
但这样并不是连接到了 "整个" 副本集. 显然如果主节点挂了, 连接就会断开.
其实 MongoDB 可以使用多服务器连接字符串, 例如:
mongodb://user:pssword@servera.mongodb.example.com:27017,serverb.mongodb.example.com:27017,serverc.mongodb.example.com:27017/?replicaSet=rs0
服务器之间用逗号分隔, 这样就可以连接到整个副本集了, 如果主节点挂了, 客户端会自动切换到其他节点.
但是这样的连接字符串太长了, 十分不方便, 这时可以使用 SRV 记录来连接.
SRV 记录
SRV 记录(Service Record)是一种 DNS 记录, 用于指定特定服务在域中的主机名和端口号. 可以用于负载均衡和服务发现, 使客户端可以动态地查找提供特定服务的服务器.
SRV 记录的设置需要在 DNS 服务商处设置, 以 Cloudflare 为例:
选项 | 值 | 说明 |
---|---|---|
Name | _mongodb._tcp.mongodbsrv | SRV 记录的 Name 由服务名和域名组成, _mongodb._tcp 为服务名, 这里 mongodbsrv 是二级域名 |
Priority | 0 | 优先级, 对于 MongoDB 副本集不需要特别设置 |
Weight | 1 | 权重, 对于 MongoDB 副本集不需要特别设置 |
Port | 27017 | 端口号 |
Target | servera.mongodb.example.com | 主机名, 填写解析到节点的域名 |
要为每一个节点都设置 Name 相同的 SRV 记录哦.
TIP提示
设置 SRV 记录的域名和解析到节点使用的域名不一定要相同
假设我们设置 SRV 记录的域名是 example.com
, 接下来就可以使用 mongodb+srv
的字符串格式来连接副本集了:
mongodb+srv://user:password@mongodbsrv.example.com/?replicaSet=rs0
🎉
FAQ
一些其他的自问自答:
为什么集群内认证不使用 X.509 证书?
太麻烦了, 使用 X.509 在节点间认证实际上是使用了基于角色的访问控制, 要在
$external
数据库中为节点配置角色, 还要自己维护 CA.配置中的
tls.mode
:requireTLS
和allowConnectionsWithoutCertificates
:true
是不是冲突的?并不,
requireTLS
是要求客户端使用 TLS 连接,allowConnectionsWithoutCertificates
是允许客户端不使用证书进行身份验证.为什么不配置
net.tls.clusterFile
?因为我们对集群内使用的是密钥文件 (keyfile) 验证.
clusterFile
用于集群内使用 X.509 证书进行身份验证.即使集群内使用 X.509 证书验证, 不配置
clusterFile
, MongoDB 也会自动使用certificateKeyFile
的证书进行验证.
Q.E.D.