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。数据尚未达到单副本集容量瓶颈时,不要过早引入分片复杂度。
