VPS 教程
VPS 搭建 MongoDB 8(2026):Docker Compose、Replica Set、TLS、认证、备份与性能调优
在 Ubuntu 24.04 VPS 上使用 Docker Compose 部署 MongoDB Community 8.0.32,配置单节点 Replica Set、TLS、认证、最小权限、oplog 备份、恢复、升级和性能调优。

MongoDB 常被当成“装完就能用”的文档数据库,但生产环境真正容易出问题的地方并不是插入一条 JSON,而是公网暴露、没有认证、Replica Set 未初始化、TLS 主机名不匹配、WiredTiger 吃满内存,以及备份文件从未验证过恢复。
本文以 Ubuntu 24.04、MongoDB Community 8.0.32、Docker Compose 和单节点 Replica Set 为基准,搭建一套适合中小项目的自托管 MongoDB。方案启用 SCRAM 用户认证、成员 keyfile、强制 TLS、最小权限账号、回环端口、oplog 一致性备份和容器资源限制,并讲清何时必须迁移到三节点架构。
MongoDB 官方文档在 2026 年 9 月把 8.3 列为当前 Stable Release,同时继续维护 8.0 系列;本文固定到 2026 年 9 月 11 日发布的 8.0.32 补丁,而不是使用会自动跨小版本的 latest 标签。上线前仍应再次核对安全公告和镜像标签。
MongoDB 的核心优势是灵活文档模型、嵌套对象、数组查询、聚合管道、地理空间查询和成熟的驱动生态。它比较适合:
- 商品属性、内容元数据、用户配置等结构变化较快的数据;
- 需要按文档整体读写、字段层级较深的业务;
- 事件、设备、目录或内容管理系统;
- 需要 Change Streams、事务或应用侧水平扩展预留的项目。
它不是“任何 SQL 都应该换掉”的理由。强关系、复杂多表约束、财务一致性和大量跨实体联查通常仍是 PostgreSQL、MySQL 的强项;全文检索和相关性排序则更适合 Elasticsearch 这类搜索引擎。先从查询模式、事务边界和数据生命周期出发,不要只因为数据长得像 JSON 就选 MongoDB。
本文不是直接运行 standalone,而是把一个 mongod 初始化为单节点 Replica Set,原因有三个:
- Change Streams 依赖复制日志 oplog;
- 多文档事务需要 Replica Set 或分片集群;
mongodump --oplog可以捕获备份期间发生的写入,形成更一致的逻辑备份。
但单节点 Replica Set 不等于高可用。只有一个数据副本,VPS、磁盘或进程故障仍会让服务中断。
| 架构 | 数据冗余 | 自动故障转移 | 适合场景 |
|---|---|---|---|
| Standalone | 无 | 无 | 临时测试,不建议新生产项目使用 |
| 单节点 Replica Set | 无 | 无 | 小型生产、可接受维护窗口、需要事务或 Change Streams |
| 3 个独立数据节点 | 有 | 有 | 关键业务,需要多数派选举与滚动维护 |
| 同一 VPS 跑 3 个节点 | 表面有三成员 | 主机故障时全部消失 | 仅用于学习,不算高可用 |
三节点必须位于独立 VPS 或故障域,并评估节点间延迟、选举、多数派写关注和跨区流量。异地备份仍然不可省略:Replica Set 会复制误删除和错误更新,它不是备份。
MongoDB 的 WiredTiger 同时使用内部缓存和 Linux 文件系统缓存。容器只有 4GB 内存时,不能按宿主机总内存让数据库自由扩张。
| 使用规模 | VPS 建议 | 本文 WiredTiger Cache | 说明 |
|---|---|---|---|
| 开发验证 | 2 vCPU、4GB 内存、40GB SSD | 1GB | 数据可重建,低并发 |
| 小型生产 | 4 vCPU、8GB 内存、100GB NVMe | 1.5–2GB | 给页缓存、Docker 和监控留余量 |
| 关键业务 | 3 台起,每台 4–8 vCPU、16GB 以上内存 | 按压测规划 | 独立故障域、复制、监控和异地备份 |
本文给容器限制 4GB,并把 WiredTiger Cache 设为 1.5GB。其余内存用于连接、聚合、索引构建、压缩和文件系统缓存。不要把缓存设到容器上限附近,否则内核可能直接 OOM Kill mongod。
数据盘优先选择 NVMe,并持续观察数据库数据、索引、journal 和 oplog 的增长。MongoDB 官方对 WiredTiger 数据节点强烈推荐 XFS;EXT4 也受支持,但高写入场景应通过真实压测判断。MongoDB 8.0 的生产说明要求启用 Transparent Huge Pages,这一点与很多针对 MongoDB 7.0 及更早版本的旧教程相反。
| 端口 | 用途 | 本文处理方式 |
|---|---|---|
27017/TCP | MongoDB 客户端连接 | 只映射到 127.0.0.1 |
Docker 内部 27017 | 应用与 MongoDB 通信 | 仅限 Compose 网络 |
22/TCP | SSH 管理或临时隧道 | 限制来源并使用密钥登录 |
不要把 27017:27017 暴露给全网。数据库即使启用了密码,也仍应放在 Docker 私网、WireGuard 或云厂商私网内。远程运维可使用 SSH 隧道,并继续验证 TLS 证书。更完整的边界检查可参考 VPS 数据库不要直接暴露公网。
以下命令假设 Docker Engine 和 Compose v2 已按官方方式安装:
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl jq openssl
docker --version
docker compose version
uname -r
MongoDB 8.0.32 官方发行说明指出,Linux Kernel 6.19 到 7.0.13 存在 TCMalloc 不兼容问题,MongoDB 会拒绝启动;7.0.14 及以上已解决。Ubuntu 24.04 常见的 6.8 内核不在该区间,但仍应以实际 uname -r 输出和最新发行说明为准。
生产容器的健康检查、日志和资源限制还可以结合 Docker Compose 生产环境清单统一管理。
sudo install -d -m 0750 -o "$USER" -g "$USER" \
/opt/mongodb/{config,secrets,tls,backups}
cd /opt/mongodb
umask 077
MONGO_ADMIN_PASSWORD="$(openssl rand -hex 32)"
MONGO_APP_PASSWORD="$(openssl rand -hex 32)"
MONGO_BACKUP_PASSWORD="$(openssl rand -hex 32)"
{
printf 'MONGODB_VERSION=8.0.32-noble\n'
printf 'MONGO_ADMIN_USER=mongoAdmin\n'
printf 'MONGO_ADMIN_PASSWORD=%s\n' "$MONGO_ADMIN_PASSWORD"
printf 'MONGO_APP_USER=appUser\n'
printf 'MONGO_APP_PASSWORD=%s\n' "$MONGO_APP_PASSWORD"
printf 'MONGO_BACKUP_USER=backupUser\n'
printf 'MONGO_BACKUP_PASSWORD=%s\n' "$MONGO_BACKUP_PASSWORD"
} > .env
chmod 600 .env
随机密码使用十六进制,放进 MongoDB URI 时不需要额外处理常见保留字符。实际生产更适合把凭据放进 Secret Manager;至少不要提交 .env,也不要把 docker compose config 的完整输出贴到工单。
keyfile 用于 Replica Set 成员之间的内部认证。即使当前只有一个成员,也应按未来扩容的结构配置:
cd /opt/mongodb
openssl rand -base64 756 > secrets/mongodb-keyfile
chmod 400 secrets/mongodb-keyfile
sudo chown 999:999 secrets/mongodb-keyfile
官方要求 key 长度为 6–1024 个 base64 字符,并且 UNIX 上不能开放给组或其他用户。未来增加节点时,每个成员必须安全持有至少一个共同 key,不能通过公开仓库或普通聊天工具分发。
下面创建项目自有 CA,再签发同时覆盖 mongo、localhost 和 127.0.0.1 的服务器证书:
cd /opt/mongodb/tls
openssl genrsa -out ca.key 4096
openssl req -x509 -new -sha256 -days 3650 \
-key ca.key \
-subj '/CN=VPS MongoDB Internal CA' \
-out ca.crt
openssl genrsa -out server.key 4096
openssl req -new -sha256 \
-key server.key \
-subj '/CN=mongo' \
-out server.csr
{
printf 'subjectAltName=DNS:mongo,DNS:localhost,IP:127.0.0.1\n'
printf 'extendedKeyUsage=serverAuth\n'
printf 'keyUsage=digitalSignature,keyEncipherment\n'
} > server.ext
openssl x509 -req -sha256 -days 825 \
-in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-extfile server.ext \
-out server.crt
cat server.crt server.key > server.pem
chmod 600 ca.key server.key server.pem
chmod 644 ca.crt server.crt
sudo chown 999:999 server.pem
openssl verify -CAfile ca.crt server.crt
openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName
server.pem 必须同时包含证书和对应私钥。公网、多 VPS Replica Set 或团队生产环境应使用正式内部 PKI,并让每个节点证书包含其可解析主机名;不要为了省事长期加 tlsAllowInvalidCertificates 或 tlsAllowInvalidHostnames。
创建 /opt/mongodb/config/mongod.conf:
storage:
dbPath: /data/db
wiredTiger:
engineConfig:
cacheSizeGB: 1.5
net:
port: 27017
bindIpAll: true
tls:
mode: requireTLS
certificateKeyFile: /etc/mongo/tls/server.pem
CAFile: /etc/mongo/tls/ca.crt
replication:
replSetName: rs0
security:
authorization: enabled
keyFile: /etc/mongo/secrets/mongodb-keyfile
setParameter:
diagnosticDataCollectionEnabled: true
bindIpAll 只决定容器内部监听地址;真正的公网边界由 Compose 的 127.0.0.1:27017:27017 和防火墙共同控制。不要删除回环绑定后又把端口映射到 0.0.0.0。
创建 /opt/mongodb/compose.yml:
services:
mongo:
image: mongo:${MONGODB_VERSION}
container_name: mongodb
restart: unless-stopped
command: ["mongod", "--config", "/etc/mongo/mongod.conf"]
ports:
- "127.0.0.1:27017:27017"
environment:
MONGO_ADMIN_USER: ${MONGO_ADMIN_USER}
MONGO_ADMIN_PASSWORD: ${MONGO_ADMIN_PASSWORD}
MONGO_APP_USER: ${MONGO_APP_USER}
MONGO_APP_PASSWORD: ${MONGO_APP_PASSWORD}
MONGO_BACKUP_USER: ${MONGO_BACKUP_USER}
MONGO_BACKUP_PASSWORD: ${MONGO_BACKUP_PASSWORD}
volumes:
- mongo-data:/data/db
- ./config/mongod.conf:/etc/mongo/mongod.conf:ro
- ./secrets:/etc/mongo/secrets:ro
- ./tls:/etc/mongo/tls:ro
- ./backups:/backups
mem_limit: 4g
ulimits:
nofile:
soft: 64000
hard: 64000
healthcheck:
test:
- CMD-SHELL
- >-
mongosh --quiet --host localhost --port 27017 --tls
--tlsCAFile /etc/mongo/tls/ca.crt
-u "$${MONGO_ADMIN_USER}" -p "$${MONGO_ADMIN_PASSWORD}"
--authenticationDatabase admin
--eval 'quit(db.adminCommand({ping:1}).ok ? 0 : 2)'
interval: 15s
timeout: 10s
retries: 20
start_period: 90s
logging:
driver: json-file
options:
max-size: 10m
max-file: "5"
networks:
- mongo-net
volumes:
mongo-data:
networks:
mongo-net:
name: mongo-net
固定 8.0.32-noble 可以避免重建容器时意外切换到 8.3 或后续大版本。Compose 中出现环境变量不代表镜像入口会自动创建用户;本文故意采用显式初始化流程,以便在 Replica Set、keyfile 和 TLS 同时启用时清楚知道每一步发生了什么。
cd /opt/mongodb
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose logs --tail=100 mongo
管理员还没创建时,健康检查会暂时显示 starting 或 unhealthy,这是初始化阶段的预期现象。先从容器内通过 localhost exception 初始化 Replica Set:
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
--eval '
rs.initiate({
_id: "rs0",
members: [{ _id: 0, host: "mongo:27017" }]
})
'
until docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
--eval 'quit(db.hello().isWritablePrimary ? 0 : 2)'
do
sleep 2
done
members[0].host 使用 Docker 网络中可解析的 mongo:27017。与 MongoDB 同机运行的应用应加入 mongo-net;如果应用位于另一台 VPS,就要使用私网可解析的固定主机名重新规划成员地址和 TLS SAN,而不是继续沿用容器短名称。
利用首个用户创建前的 localhost exception,一次性建立账号:
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
--eval '
const admin = db.getSiblingDB("admin");
admin.createUser({
user: process.env.MONGO_ADMIN_USER,
pwd: process.env.MONGO_ADMIN_PASSWORD,
roles: [{ role: "root", db: "admin" }]
});
const app = db.getSiblingDB("appdb");
app.createUser({
user: process.env.MONGO_APP_USER,
pwd: process.env.MONGO_APP_PASSWORD,
roles: [{ role: "readWrite", db: "appdb" }]
});
admin.createUser({
user: process.env.MONGO_BACKUP_USER,
pwd: process.env.MONGO_BACKUP_PASSWORD,
roles: [
{ role: "backup", db: "admin" },
{ role: "restore", db: "admin" }
]
});
'
docker compose ps
这一步完成后,localhost exception 关闭,后续请求都必须认证。应用只使用 appUser,不要把 mongoAdmin 写进业务配置。备份与恢复最好进一步拆成不同账号;本文合并是为了让小型部署的命令更易操作。
cd /opt/mongodb
set -a
. ./.env
set +a
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
-u "$MONGO_ADMIN_USER" -p "$MONGO_ADMIN_PASSWORD" \
--authenticationDatabase admin \
--eval '
printjson(db.hello());
printjson(rs.status().members.map(m => ({name:m.name,stateStr:m.stateStr})));
'
应看到 setName: "rs0"、isWritablePrimary: true 和成员状态 PRIMARY。再验证应用用户只能操作 appdb:
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
-u "$MONGO_APP_USER" -p "$MONGO_APP_PASSWORD" \
--authenticationDatabase appdb \
--eval '
const app = db.getSiblingDB("appdb");
app.healthcheck.insertOne({checkedAt:new Date(),source:"deploy"});
printjson(app.healthcheck.findOne({source:"deploy"}));
'
业务容器的连接字符串可采用:
mongodb://appUser:PASSWORD@mongo:27017/appdb?authSource=appdb&replicaSet=rs0&tls=true&tlsCAFile=/run/secrets/mongodb-ca.crt
不要在 Git 中保存真实 URI。密码若包含特殊字符必须进行 percent-encoding;本文使用 hex 密码只是降低初次配置出错概率。
另一个 Compose 项目可以把 MongoDB 网络声明为 external:
services:
app:
image: your-app:stable
networks:
- app-net
- mongo-net
secrets:
- mongodb_ca
networks:
app-net:
mongo-net:
external: true
secrets:
mongodb_ca:
file: ./secrets/mongodb-ca.crt
把 /opt/mongodb/tls/ca.crt 安全复制到应用项目的 secret 路径,让驱动验证 MongoDB 证书。应用无需访问服务器私钥、Replica Set keyfile 或管理员密码。
先建立一个手动备份。--oplog 会把备份期间发生的写入记录到归档中,恢复时通过 --oplogReplay 重放:
cd /opt/mongodb
docker compose exec -T mongo sh -lc '
set -eu
ts="$(date -u +%Y%m%dT%H%M%SZ)"
mongodump \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
-u "$MONGO_BACKUP_USER" -p "$MONGO_BACKUP_PASSWORD" \
--authenticationDatabase admin \
--oplog --gzip \
--archive="/backups/mongodb-${ts}.archive.gz"
sha256sum "/backups/mongodb-${ts}.archive.gz" \
> "/backups/mongodb-${ts}.archive.gz.sha256"
'
find backups -maxdepth 1 -type f -printf '%TY-%Tm-%Td %TH:%TM %s %f\n' | sort
本机 /opt/mongodb/backups 仍和数据库位于同一台 VPS,不能抵御整机或账户故障。任务完成后应把归档和 SHA-256 上传到独立对象存储,启用服务端加密、版本控制、生命周期规则和独立凭据。
大数据量下,mongodump 会消耗 CPU、磁盘和网络,并不是所有工作负载的最佳方案。关键业务应评估文件系统快照、MongoDB Ops Manager/Cloud Manager 或托管 Atlas,并在副本节点执行备份,避免拖慢 Primary。
不要直接对生产库执行 --drop。先在隔离 MongoDB 实例中复制归档、CA 和最小恢复凭据,然后运行:
mongorestore \
--host localhost --port 27017 \
--tls --tlsCAFile /path/to/ca.crt \
-u restoreUser -p \
--authenticationDatabase admin \
--drop --gzip \
--archive=/path/to/mongodb-20260916T120000Z.archive.gz \
--oplogReplay
恢复后至少核对数据库与集合数量、关键文档抽样、索引、用户权限和应用读写。mongodump 不是整台服务器的镜像,也不会替你备份 .env、CA 私钥、mongod 配置、监控规则和恢复手册;这些要使用独立加密备份。
先建立指标基线,再调参数。至少观察:
opcounters、操作延迟和慢查询;- 当前连接数、可用连接与连接创建速率;
- WiredTiger Cache 使用、dirty bytes、eviction 和 checkpoint;
- page fault、宿主机内存、swap、OOM 事件;
- 数据、索引、journal、oplog 大小和磁盘 I/O 延迟;
- Replica Set 状态、复制延迟、选举和 majority 写入失败;
- 备份时长、归档大小、异地上传和恢复演练结果。
可以把宿主机与容器指标接入 Prometheus + Grafana 监控方案。MongoDB 自身的 serverStatus 与 dbStats 也能提供基础信号:
db.serverStatus().connections
db.serverStatus().wiredTiger.cache
db.serverStatus().opcounters
db.getSiblingDB("appdb").stats(1024 * 1024)
rs.printReplicationInfo()
常见优化顺序是:修正查询和索引,限制无界结果集,检查工作集是否超出内存,再考虑扩容。不要看到 CPU 高就盲目加索引;索引会占磁盘和内存,并增加每次写入成本。上线索引前用 explain("executionStats") 对真实查询验证。
同一 8.0 系列补丁升级前,先阅读 release notes、完成异地备份和恢复抽查:
cd /opt/mongodb
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
-u "$MONGO_ADMIN_USER" -p "$MONGO_ADMIN_PASSWORD" \
--authenticationDatabase admin \
--eval 'printjson(db.adminCommand({getParameter:1,featureCompatibilityVersion:1}))'
docker compose pull mongo
docker compose up -d mongo
docker compose ps
docker compose logs --tail=150 mongo
升级后检查版本、Replica Set、业务读写、慢查询和备份。不要只改成 mongo:8:这个标签可能跨到不同稳定系列。跨大版本必须按照官方支持路径逐级升级,并在确认所有节点和驱动兼容后再调整 FCV;FCV 一旦提升,回退会受到限制。
单节点升级必然有停机窗口。三节点生产集群应按 Secondary、Primary 的官方滚动顺序处理,而不是同时重建所有容器。
cd /opt/mongodb
docker compose ps
docker compose logs --since=15m --tail=300 mongo
docker inspect mongodb --format '{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}}'
重点检查 keyfile 与 server.pem 的权限、配置 YAML、数据卷写权限、内存限制和内核版本。也可以按 Docker 容器 Restarting 排查清单查看退出码与 OOM。
ls -l /opt/mongodb/secrets/mongodb-keyfile
sudo chown 999:999 /opt/mongodb/secrets/mongodb-keyfile
sudo chmod 400 /opt/mongodb/secrets/mongodb-keyfile
docker compose restart mongo
不要改成 chmod 777。MongoDB 会拒绝读取对组或其他用户开放的 keyfile。
先确认客户端使用正确 CA,并且连接主机名存在于证书 SAN:
openssl verify -CAfile /opt/mongodb/tls/ca.crt \
/opt/mongodb/tls/server.crt
openssl x509 -in /opt/mongodb/tls/server.crt \
-noout -ext subjectAltName
连接 mongo 时证书要有 DNS:mongo,连接 127.0.0.1 时要有对应 IP SAN。不要用跳过校验作为永久修复。
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
-u "$MONGO_ADMIN_USER" -p "$MONGO_ADMIN_PASSWORD" \
--authenticationDatabase admin \
--eval 'printjson(rs.status())'
单节点应确认成员地址仍为 mongo:27017、容器网络名称未被误改、数据卷未换成空卷。三节点则要检查 DNS、时钟、keyfile、TLS SAN、网络延迟和多数派是否存活。
先在宿主机验证回环端口,再检查应用是否加入 mongo-net:
ss -lntp | grep 27017
docker network inspect mongo-net
docker compose exec -T mongo mongosh --quiet \
--host localhost --port 27017 \
--tls --tlsCAFile /etc/mongo/tls/ca.crt \
-u "$MONGO_ADMIN_USER" -p "$MONGO_ADMIN_PASSWORD" \
--authenticationDatabase admin \
--eval 'db.adminCommand({ping:1})'
如果需要从另一台机器访问,不要直接把端口改成 0.0.0.0;先建立 WireGuard、私网或 SSH 隧道,再处理证书主机名。通用网络定位步骤见 VPS 端口、防火墙与安全组排查。
检查 docker inspect、dmesg、WiredTiger Cache 与并发聚合。降低 cacheSizeGB 只能解决一部分问题;无界 $group、大排序、一次返回大量文档和索引构建也会消耗额外内存。应同时修复查询、设置资源余量或升级 VPS。
仅开发验证可从 2 核、4GB 内存开始;小型生产更建议 4 核、8GB 内存和 NVMe。数据量、索引、聚合与并发差异很大,最终应根据 WiredTiger Cache、文件系统缓存、磁盘延迟和真实查询压测确定。
没有。它提供 oplog、事务和 Change Streams 能力,但只有一份在线数据。高可用至少需要三个位于独立故障域的投票成员,并配合副本、监控和异地备份。
不建议。本文只绑定 127.0.0.1,业务容器通过 Docker 私网连接。跨机器访问应使用 WireGuard、云私网或 SSH 隧道,同时保留 TLS、认证和最小权限。
浮动标签可能在容器重建时引入新小版本、Stable 系列或兼容性变化。本文固定 8.0.32-noble,升级时先看发行说明、做备份和恢复验证,再主动修改标签。
不能。它适合中小数据量的逻辑备份;大库可能耗时并影响性能。还需要异地保存归档、备份配置和密钥,并定期在隔离环境恢复。关键业务应评估文件系统快照、Ops Manager、Cloud Manager 或 Atlas。
Replica Set 主要解决冗余和故障转移;分片把数据分布到多个 shard,用于容量与吞吐水平扩展。每个 shard 本身通常又是 Replica Set。数据尚未达到单副本集容量瓶颈时,不要过早引入分片复杂度。