1. 先搞清楚集群到底指什么否则第一步就走偏很多人搜docker 部署 mongodb 集群脑子里想的是一套东西实际动手时才发现 MongoDB 的集群至少分三种形态部署方式完全不同。我见过太多人兴冲冲拉起三个容器结果连副本集都跑不起来问题就出在没先分清目标。第一种是副本集Replica Set一份数据存在多个节点上主节点写、从节点读主节点挂了自动选举。这是生产环境最常见的高可用方案也是绝大多数人真正需要的东西。第二种是分片集群Sharded Cluster数据按分片键切分到不同分片上前面有 mongos 做路由后面有 config server 存元数据。这个适合单机装不下、写入量极大的场景组件多、部署复杂。第三种是很多人误以为的集群——其实就是同一台机器上跑多个独立实例互不相干根本不是集群。这篇内容主要围绕副本集展开因为它是性价比最高、使用面最广的方案同时也把分片集群的组件关系和部署要点讲清楚你按自己的业务量选。如果你只是想在本地做开发测试单节点副本集一个容器但配置成副本集模式就够用了因为很多驱动和事务特性要求必须连到副本集单机模式会报错。先把结论说在前面用 Docker 部署 MongoDB 副本集核心难点不在容器编排而在节点之间怎么互相发现和初始化只能做一次这两个坑上。容器内的 MongoDB 默认只监听本地回环配置文件要改副本集初始化时的 host 名必须用容器网络里的可解析名称不能用 127.0.0.1否则选举和同步全乱。这两点后面会详细拆。提示本文所有操作基于 MongoDB 6.x/7.x 与 Docker 24 验证低版本命令基本兼容但rs.initiate的返回格式和部分参数有差异实操时留意。适合读这篇的人有基本 Docker 使用经验会写 compose 文件、会看容器日志了解 MongoDB 基本增删改查但没在生产环境搭过副本集或者搭过但踩了坑没搞明白为什么。完全没碰过 Docker 的建议先把镜像、容器、网络、卷这四个概念过一遍再回来。2. 容器化 MongoDB 的三条硬约束为什么不能照搬物理机经验在物理机上装 MongoDB 副本集大家习惯把配置文件写在/etc/mongod.conf用 systemd 管进程节点间用主机名或 IP 通信。搬到容器里这套经验会碎一地。原因不是 Docker 不好用而是容器的运行模型和物理机有本质差异。理解这三条约束后面的配置你就不用死记硬背。2.1 约束一容器内进程必须前台运行且不能自杀Docker 容器的生命周期绑定在主进程上主进程一退容器就停。MongoDB 官方镜像默认就是前台运行mongod这点没问题。但有个细节容易翻车如果你在启动命令里加了--fork后台守护容器会立刻退出日志里什么都看不到docker ps -a显示Exited (0)看着像成功其实是失败。物理机上用--fork是常规操作容器里绝对不行。另一个相关问题是信号处理。docker stop默认发 SIGTERM等 10 秒后发 SIGKILL 强杀。MongoDB 收到 SIGTERM 会走正常关闭流程刷盘、释放锁。如果你在 compose 里设置了很短的健康检查间隔或者用docker kill可能打断写入导致数据文件损坏。我一般会给stop_grace_period显式设成 30 秒给足刷盘时间services: mongo1: image: mongo:7.0 stop_grace_period: 30s这个参数不起眼但在频繁重启的调试阶段救过我好几次尤其是数据量大了以后刷盘时间会明显变长。2.2 约束二监听地址必须从 127.0.0.1 改成 0.0.0.0官方镜像的默认配置里bindIp是127.0.0.1。容器内部这个地址只指向容器自己别的容器根本连不进来。你必须通过配置文件或命令行参数把它改成0.0.0.0让 MongoDB 监听所有网卡。这里有个安全层面的顾虑需要说清楚改成0.0.0.0意味着容器网络内所有能访问到该端口的进程都能连。所以必须配合认证机制也就是启用--auth或者配置文件里的security.authorization: enabled并且创建管理员账号。只在开发环境图省事不开认证上线前一定要补上。我的做法是首次启动不开认证用 localhost 例外机制创建好管理员用户MongoDB 有个本地连接不受认证限制的特性然后重启开启认证这个流程后面会给完整脚本。2.3 约束三数据必须落在命名卷或绑定挂载上容器删了数据就没了这是常识。但这里有个更隐蔽的问题副本集的 local 数据库。MongoDB 在local库里存了 oplog 和节点身份信息如果用匿名卷容器重建后 local 库丢失节点会认为自己是个全新节点需要重新加入副本集甚至可能触发全量同步。数据量大的时候这个全量同步能跑几个小时。所以数据目录必须用命名卷named volume持久化并且同一个节点的卷不能和其他节点共用。我见过有人为了省事三个节点挂同一个宿主机目录结果三个 mongod 抢同一批文件数据直接损坏。这种错误在容器里特别容易犯因为挂载路径写错一个字符就撞上了。约束点物理机做法容器里的正确做法踩坑后果进程管理systemd --fork前台运行不加 fork容器秒退误判成功监听地址bindIp 127.0.0.1bindIp 0.0.0.0 认证节点间无法通信数据持久化本地磁盘路径每节点独立命名卷数据丢失/文件损坏关闭方式systemctl stop预留足够 stop_grace_period写中断、文件损坏这张表建议存下来排查问题时对照看能省不少时间。3. 动手前的地基工程网络、密钥与目录规划真正开始写 compose 之前有三件事必须先定好否则后面改起来很痛苦。这三件事是容器网络怎么划、副本集密钥怎么生成、宿主机目录或卷怎么分配。很多人上来就抄一份 compose 文件跑起来发现节点互相连不上再回头改网络前面配的东西全要重来。3.1 自定义 bridge 网络是必须的默认网络不够用Docker 的默认 bridge 网络有个坑容器之间只能用 IP 互相访问不支持通过容器名做 DNS 解析。而 MongoDB 副本集的配置里成员地址最好写可解析的主机名否则主节点换了 IP 之后从节点就找不到它了。虽然 MongoDB 支持用 IP 配成员但 IP 变化容器重启会变会导致副本集配置过时需要手动rs.reconfig运维成本很高。所以第一步是建一个自定义网络docker network create mongo-cluster --subnet 172.20.0.0/16指定 subnet 不是必须的但建议指定。理由有两个一是避免和宿主机上已有的网段冲突默认分配的网段有时会撞二是固定网段后排查网络问题时心里有数docker network inspect一看就知道地址段。子网大小/16够用了副本集加监控组件也就十来个容器。建好网络后所有节点都加进去这样节点之间可以用服务名互相解析。在 compose 里体现就是每个服务加networks: - mongo-cluster并且给每个服务起一个固定名字比如mongo1、mongo2、mongo3。3.2 副本集密钥文件权限和一致性都不能马虎副本集内部节点之间要做认证靠的是 keyfile。这个文件有两个硬性要求所有节点的密钥内容必须完全一致文件权限必须是 400。少一个条件mongod启动时直接报错退出日志里写permissions on /data/keyfile are too open一看就懂。生成方式很简单openssl rand -base64 756 mongo-keyfile chmod 400 mongo-keyfile756 字节的 base64 是个经验值MongoDB 要求密钥长度在 6 到 1024 字符之间756 够长且不会因为太长影响性能。生成一次三个节点挂同一个宿主机文件用绑定挂载只读挂进去。注意这里是同一个文件挂三次不是每个节点复制一份——虽然复制一份内容一致也行但同步维护容易忘改了一次没改另外两次就是灾难。注意keyfile 里绝对不能有多余的换行或者空格。用openssl rand生成一般没问题但如果你手动编辑过最好用wc -c确认一下长度cat -A看一眼有没有^M这种 Windows 换行符混进去。挂载到容器里之后权限问题还有个补充容器内 mongod 默认以mongodb用户uid 999运行宿主机文件属主如果不是这个 uid可能读不了。保险做法是把 keyfile 的属主改成 999sudo chown 999:999 mongo-keyfile不同镜像版本 uid 可能不一样mongo:7.0是 999更早的版本可能是 999 或者 1000用docker run --rm mongo:7.0 id mongodb可以确认。3.3 目录规划一个节点一套配置但可以复用模板三个节点的配置文件内容高度相似只有少数几项不同。我不建议为每个节点手写一份完整配置容易抄漏。推荐做法是写一份模板用环境变量或者 compose 里的 command 覆盖关键项。MongoDB 的配置文件是 YAML 格式关键配置项有这些storage: dbPath: /data/db wiredTiger: engineConfig: cacheSizeGB: 1 systemLog: destination: file path: /data/db/mongod.log logAppend: true net: port: 27017 bindIp: 0.0.0.0 replication: replSetName: rs0 security: keyFile: /data/keyfile几个值的取法值得说说。cacheSizeGB是 WiredTiger 缓存大小默认取(物理内存 - 1GB) / 2但容器里 MongoDB 读到的是宿主机内存容易把缓存开得过大导致被 OOM Killer 干掉。所以容器化部署建议显式设置一般给容器内存上限的 50% 到 60%。如果你给容器分配 2GB 内存cacheSizeGB设 1 就合理。replSetName三个节点必须一致我习惯用rs0你也可以用业务相关的名字但一旦初始化后就不能改改名意味着重建副本集。systemLog.destination: file是可有可无的选择。设成 file 之后日志写在容器内的文件里docker logs就看不到内容了排查时要去卷里翻文件。我个人的偏好是设成stdout或者干脆不配这一段用默认让日志走标准输出docker logs -f mongo1就能实时看容器场景下这个更顺手。日志文件写多了还会占满卷空间需要额外做轮转配置反而增加复杂度。4. 一份能直接跑的 compose 文件逐段拆给你看前面铺垫完了这里是完整的 compose 文件。我把它拆成几个段落讲每段说明为什么这么写你可以整体复制后按需修改。services: mongo1: image: mongo:7.0 container_name: mongo1 hostname: mongo1 restart: unless-stopped stop_grace_period: 30s ports: - 27017:27017 volumes: - mongo1-data:/data/db - ./mongo-keyfile:/data/keyfile:ro - ./mongod.conf:/etc/mongod.conf:ro command: [mongod, --config, /etc/mongod.conf] networks: - mongo-cluster ulimits: nofile: soft: 64000 hard: 64000 deploy: resources: limits: memory: 2g mongo2: image: mongo:7.0 container_name: mongo2 hostname: mongo2 restart: unless-stopped stop_grace_period: 30s ports: - 27018:27017 volumes: - mongo2-data:/data/db - ./mongo-keyfile:/data/keyfile:ro - ./mongod.conf:/etc/mongod.conf:ro command: [mongod, --config, /etc/mongod.conf] networks: - mongo-cluster deploy: resources: limits: memory: 2g mongo3: image: mongo:7.0 container_name: mongo3 hostname: mongo3 restart: unless-stopped stop_grace_period: 30s ports: - 27019:27017 volumes: - mongo3-data:/data/db - ./mongo-keyfile:/data/keyfile:ro - ./mongod.conf:/etc/mongod.conf:ro command: [mongod, --config, /etc/mongod.conf] networks: - mongo-cluster deploy: resources: limits: memory: 2g volumes: mongo1-data: mongo2-data: mongo3-data: networks: mongo-cluster: external: true4.1 为什么要写 hostname它和 container_name 有什么区别container_name决定容器在宿主机上的名字docker ps看到的是它。hostname决定容器内部hostname命令返回什么以及在自定义网络里 DNS 解析用的名字。这两个我都设成一样的值是为了避免自己看混。关键在于MongoDB 副本集初始化时成员地址用的是mongo1:27017这种形式这个mongo1必须能在容器网络里解析到。自定义 bridge 网络下Docker 内置 DNS 会把容器的 hostname 和 container_name 都注册进去不同版本行为略有差异所以两个都设成相同值最保险。如果你只设 container_name 不设 hostname绝大多数情况也能解析但个别 Docker 版本或者用了 swarm 模式时会有差异索性都写上。端口映射这块三个节点都映射到宿主机不同端口27017/27018/27019。这么做的目的是方便你从宿主机上的 MongoDB Compass 或者 mongosh 直连调试。生产环境如果不打算从宿主机直接访问可以不映射只让容器网络内部通信安全性更好。这里保留映射是为了演示方便。4.2 ulimits 为什么必须调默认值会出什么事MongoDB 对文件描述符数量有要求官方建议 64000 以上。Docker 默认的nofile限制是 1024继承自宿主机 systemd 的默认值在连接数一多或者并发写入大的时候会出现Too many open files错误日志里能看到然后连接被拒。这个错误有个特点低负载测试时完全不出现压测或者上线后才爆发。我一开始搭测试环境没设 ulimits本地跑得好好的接入真实流量后半夜告警排查了半天才定位到。所以别省这一行配置。deploy.resources.limits也是同理容器不限制内存的话MongoDB 会按宿主机总内存来计算 WiredTiger 缓存很可能把整台机器拖垮。这里要提醒一点deploy段在docker compose非 swarm 模式下的支持情况随版本变化较新的 Compose V2 是支持的。如果你用的版本不生效可以改用mem_limit: 2g这种顶层写法效果一样。4.3 首次启动后必须做的初始化以及为什么只能做一次三个容器起来之后副本集还没形成需要手动初始化。这一步是新手最容易出问题的地方。先进入任意一个节点docker exec -it mongo1 mongosh然后执行初始化rs.initiate({ _id: rs0, members: [ { _id: 0, host: mongo1:27017, priority: 2 }, { _id: 1, host: mongo2:27017, priority: 1 }, { _id: 2, host: mongo3:27017, priority: 1 } ] })返回{ ok: 1 }表示成功。等几秒提示符会从变成rs0 [primary] 说明这个节点当选为主节点。为什么说初始化只能做一次rs.initiate在一个已经有配置的节点上再次执行会报错already initialized。更麻烦的情况是你在一个节点上初始化了又在另一个节点上执行可能导致配置冲突需要rs.reconfig甚至清空数据重来。所以流程是只在一个节点上初始化其他节点什么都不用做等它们自动同步配置。还有个隐蔽的坑初始化时 members 里的 host 写的是mongo1:27017这个地址会被写进副本集配置所有节点都按这个地址去连。如果你写成了127.0.0.1:27017主节点自己是能连上自己但从节点连127.0.0.1连的是它自己发现这个地址上的节点 ID 不对同步就失败了。这是最经典的错误日志里表现为replica set member not reachable或者心跳超时。priority字段的作用是控制选举优先级值大的优先当选主节点。我给 mongo1 设了 2另外两个是 1这样在主节点选举时 mongo1 更可能胜出行为可预测。生产环境里通常不这么设让它们平等都是 1避免某个节点长期承担写压力。这里这么配主要是演示这个字段的用法。5. 认证、初始化数据与连接串上线前必须补齐的三件事副本集跑起来、rs.status()显示三个节点都健康很多人到这就以为完事了。实际上还差三步才能算可用创建用户并开启认证、初始化业务数据、把连接串配好。这三件事顺序有讲究搞反了要回滚重来。5.1 先建用户再开认证顺序不能反MongoDB 有个特性叫localhost exception本地连接例外在没有创建任何用户的情况下从本机127.0.0.1连接可以执行创建用户的命令不需要认证。这个机制就是给初始化用的。所以正确流程是副本集初始化完成主节点就绪此时还没开认证配置文件里security.authorization没写或者注释掉从主节点容器内部连进去创建管理员用户修改配置文件加上security.authorization: enabled重启三个节点用管理员账号登录创建业务库的专用用户创建管理员的命令use admin db.createUser({ user: admin, pwd: 换成强密码, roles: [{ role: root, db: admin }] })密码别用弱口令也别硬编码在脚本里提交到代码仓库。我的习惯是放到环境变量文件里compose 用env_file引入.env加进.gitignore。这里有个顺序陷阱如果你先开了认证再想创建用户会发现连不上——因为没有用户能通过认证。这时候只能把配置改回去重启或者用--noauth临时启动。我踩过一次配置文件改完直接docker compose restart然后所有操作都被拒折腾了十几分钟才反应过来。注意副本集模式下创建用户命令必须在主节点执行。如果你连的是从节点会报not master错误。rs.status()里看myState为 1 的就是主节点。5.2 业务用户与权限最小化别图省事全用 root管理员建好后业务侧要单独建用户只给必要的权限use appdb db.createUser({ user: appuser, pwd: 另一个强密码, roles: [ { role: readWrite, db: appdb } ] })权限最小化不是形式主义。用 root 账号跑业务代码一旦应用被入侵或者代码有注入漏洞攻击者拿到的是整个数据库实例的控制权能删库、能改其他库的数据。给readWrite只限单库破坏范围可控。如果业务需要读其他库的集合单独加对应的 role别嫌麻烦。还有个细节副本集里用户信息是存在admin库的system.users集合里会通过 oplog 同步到所有节点。所以你在主节点建的用户从节点也能用来认证不需要在每个节点重复建。5.3 连接串的写法以及 readPreference 怎么选应用侧连接副本集连接串要写全所有成员或者至少写多个让驱动能做故障转移mongodb://appuser:passwordmongo1:27017,mongo2:27017,mongo3:27017/appdb?replicaSetrs0authSourceadmin几个参数值得展开说。replicaSetrs0告诉驱动这是个副本集驱动会做拓扑发现自动识别主从。authSourceadmin指定认证用的库用户建在 admin 库就必须带上这个参数否则驱动会去appdb里找用户认证失败。这个参数漏掉是高频错误报错信息也不直白只说认证失败不告诉你去哪找用户。readPreference决定了读操作打到哪。默认是primary所有读都走主节点数据最新但主节点压力大。改成secondaryPreferred可以让读请求优先走从节点分担主节点压力代价是可能读到稍微旧的数据复制延迟通常在毫秒级但网络抖动时可能到秒级。业务能接受轻微延迟就用这个对读多写少的场景提升明显。如果要强一致就保持primary。从外部宿主机访问的话注意连接串里的主机名。容器网络里的mongo1在宿主机上解析不了要用localhost:27017,localhost:27018,localhost:27019。但这里有个坑副本集配置里存的是容器内的地址从宿主机连的时候驱动做拓扑发现会拿到mongo1:27017这些地址宿主机解析不了导致连接失败。解决办法有两个一是在宿主机/etc/hosts里加上容器 IP 的映射二是通过 SSH 隧道或者进容器内部操作。开发阶段我一般直接docker exec -it mongo1 mongosh进去操作省去这些麻烦。场景连接串主机部分额外参数应用在容器网络内mongo1:27017,mongo2:27017,mongo3:27017replicaSetrs0authSourceadmin应用在宿主机localhost:27017,localhost:27018,localhost:27019需配 hosts 映射才能拓扑发现生产环境各节点内网域名加 tlstrue 与超时参数6. 验证副本集真的健康比 rs.status 更细的几个检查点rs.status()返回ok: 1不代表一切正常。我见过状态显示正常但实际同步卡住的情况也见过节点显示SECONDARY但其实延迟了几个小时。这里给几个更细的检查维度都是实战里攒下来的。6.1 逐个看成员状态和同步延迟rs.status().members.forEach(m { print(m.name, m.stateStr, m.optimeDate, m.health) })关注三个字段stateStr应该是PRIMARY一个、SECONDARY两个或相反数量取决于节点数health为 1 表示健康0 表示不可达optimeDate各节点之间差距应该在秒级以内。如果某个从节点的optimeDate落后主节点很多说明同步有问题可能是网络慢、磁盘 IO 瓶颈或者从节点在做全量同步。全量同步有个识别特征从节点状态是STARTUP2而不是SECONDARYrs.status()里能看到syncingTo字段。这个阶段数据量大的话可能持续很久期间从节点不对外服务。如果你不知道这个状态会误以为节点挂了。6.2 写一条数据验证端到端链路光看状态不够实际写一条验证use testdb db.testcoll.insertOne({ msg: hello, ts: new Date() }) db.testcoll.countDocuments()然后在从节点上读需要先在从节点执行rs.secondaryOk()允许从节点读rs.secondaryOk() use testdb db.testcoll.find()能看到刚才写的数据说明复制链路通了。这个验证比看状态更实在因为状态可能因为配置问题显示正常但实际不复制——虽然少见但确实遇到过keyfile 权限对了、网络通了、配置也一致但某个节点的replSetName拼写差了一个字符状态显示在集合里但一直不同步。这种问题只能靠实际读写发现。6.3 故障转移演练主动停掉主节点看会不会自动选主这一步很多人跳过但它是副本集存在的意义必须验证。操作很简单docker stop mongo1然后等 10 到 30 秒默认 electionTimeoutMillis 是 10 秒再连 mongo2 看状态rs.status().members.forEach(m print(m.name, m.stateStr))正常情况下mongo2 或 mongo3 会变成PRIMARY。这时候往新主节点写入应该成功。然后把 mongo1 启回来docker start mongo1它会以SECONDARY身份重新加入并同步停掉期间缺失的数据。这个演练有几个观察点选主耗时多长正常 10 到 30 秒、期间应用有没有正确重试、旧主节点回来后能不能追上数据。如果旧主节点回来一直停在STARTUP2可能是它的 oplog 里缺失的数据太多需要全量同步数据量大的话恢复时间很长——这也是为什么生产环境要把 oplog 调大让从节点能通过增量同步追上而不是每次重启都全量同步。oplog 大小在配置文件里设replication: replSetName: rs0 oplogSizeMB: 2048默认 oplog 大小跟磁盘和引擎有关通常偏小。按经验oplog 至少要能覆盖一次全量备份的时间窗口加上业务高峰的写入量。给 2GB 起步写入量大的给到 10GB 以上。调整 oplog 大小需要重启节点并用replSetResizeOplog命令不是改配置就立刻生效这点要注意。7. 分片集群什么时候真的需要以及多出来的组件怎么摆副本集解决了高可用但没解决容量和写入吞吐的横向扩展。当单机磁盘装不下、或者单节点写入成为瓶颈时才需要上分片集群。我先把话说清楚分片集群的运维复杂度是副本集的数倍能不分片就不分片。很多业务量用副本集加读写分离、索引优化就能撑住盲目上分片反而增加故障点。真到了要分片的时候组件关系是这样的mongos路由接收客户端请求根据分片键把请求转发到对应的分片config server配置服务器存集群元数据必须是副本集形式3 个节点shard分片存实际数据每个分片本身也是一个副本集。所以一个最小可用的分片集群容器数量是mongos 3 个 config 3 个 shard 节点起至少 7 个容器。7.1 config server 的部署要点config server 必须以副本集模式启动配置里加--configsvr参数副本集名字通常用cfgservices: cfg1: image: mongo:7.0 hostname: cfg1 command: [mongod, --configsvr, --replSet, cfg, --bind_ip_all, --port, 27019] volumes: - cfg1-data:/data/configdb networks: - mongo-cluster注意数据目录是/data/configdb而不是/data/db这是官方约定。三个 config server 初始化成一个叫cfg的副本集方法跟前面副本集初始化一样进去执行rs.initiate。config server 数量必须是奇数3 个是标配。少于 3 个没有冗余多于 3 个增加通信开销收益不大。数据量很小只存元数据内存配置不用太高。7.2 shard 与 mongos 的启动参数差异shard 的启动参数里没有--configsvr但通常加上--shardsvrmongod --shardsvr --replSet shard1 --bind_ip_all每个 shard 自己是个副本集先把它初始化好用rs.initiate然后在 mongos 上执行sh.addShard(shard1/shard1a:27018,shard1b:27018)把它加入集群。mongos 的启动方式不同它不存数据不用--dbpathmongos --configdb cfg/cfg1:27019,cfg2:27019,cfg3:27019 --bind_ip_all --port 27017--configdb指向 config server 副本集的名称和成员地址。这个参数写错了 mongos 起不来日志里会明确说连不上 config server还算好排查。7.3 分片键的选择比部署更容易出错的地方集群搭起来只是开始真正决定性能的是分片键。这里给几条实战经验都是从慢查询和热点问题里总结的。分片键必须有高基数和写分散两个特性。比如用户 ID 做分片键基数够高但如果是自增 ID比如时间戳拼接写入会全部落到最后一个分片上形成热点分片失去意义。哈希分片键{_id: hashed}能解决写分散问题但范围查询会打到所有分片性能不如范围分片。所以选择要看查询模式读多且以范围查询为主用范围分片键写多且查询以等值为主用哈希分片键。分片键一旦选定就不能改MongoDB 5.0 之后支持reshardCollection在线重分片但代价很大所以要在建集合时就规划好。我见过项目上线后才想分片发现分片键选错只能新建集合迁数据业务停机好几个小时。集合还需要显式开启分片sh.enableSharding(appdb) sh.shardCollection(appdb.orders, { userId: hashed })enableSharding是按库开启shardCollection是按集合指定分片键。没执行这两条数据还是全落在一个分片上白搭。提示分片集群下连接串指向 mongos不要直连 shard。直连 shard 写入的数据可能不在正确的分片上导致查询查不到。mongos 地址写多个做冗余格式和副本集类似但不需要写replicaSet参数。8. 运维阶段的高频故障与我的排查顺序部署完只是开始线上跑起来会遇到各种问题。这部分列几个高频故障以及我自己的排查顺序都是实际处理过的。8.1 节点一直处于 STARTUP2 或 RECOVERING先看日志docker logs --tail 200 mongo2。重点找关键词sync、oplog、network。如果是网络问题日志里会有connection refused或者host unreachable检查docker network inspect mongo-cluster看节点是否都在网络里ping mongo1在容器内能否通。如果是 oplog 缺口问题日志里有we need to resync或者oplog source not found说明从节点的 oplog 太旧追不上主节点了只能全量同步。加速的办法是临时把syncSource指定为某个同步较快的节点或者直接让节点重新同步删掉数据目录重启代价大。8.2 主节点选举频繁发生业务频繁抖动rs.status()里看electionDate如果短时间内多次选举说明主节点不稳定。常见原因有三个主节点资源不足CPU 打满或内存吃紧被 OOM、网络抖动导致心跳超时、priority配置导致某个弱节点反复被选中。排查顺序先看docker stats看资源占用再看docker events看有没有容器重启记录最后检查网络延迟。如果是资源问题加内存或者调低cacheSizeGB如果是网络问题检查宿主机网卡和 Docker 网络配置。有个经验值可以参考选举超时默认 10 秒心跳间隔 2 秒。如果宿主机负载很高比如跑了很多其他容器心跳可能延迟导致误判主节点失联。这种环境下可以把electionTimeoutMillis调大到 20 秒代价是故障转移变慢。8.3 磁盘空间被日志和 oplog 吃掉容器里最容易忽视磁盘增长。三个地方会涨数据文件、oplog、日志文件。数据文件是正常的oplog 有上限设了oplogSizeMB就不会无限涨日志文件如果设成写文件且没做轮转会一直涨。如果发现磁盘突然满了先docker system df -v看各卷的占用再进容器du -sh /data/db/*看具体是哪个文件大。mongod.log 涨到几个 G 的情况我遇到过因为开了 verbose 日志级别忘了改回来。预防措施日志走 stdout不落文件oplog 显式设大小给卷所在磁盘留 20% 以上余量加一个简单的磁盘监控。这些不是高深技巧但能避免半夜被磁盘告警叫醒。8.4 备份与恢复副本集不等于备份这一点必须强调副本集提供的是高可用不是备份。三个节点同时被误删数据比如误执行dropDatabase这个删除操作会同步到所有节点高可用救不了你。备份用mongodump在从节点上执行减少主节点压力docker exec mongo2 mongodump --host localhost --port 27017 \ --username admin --password xxx --authenticationDatabase admin \ --out /tmp/backup然后把备份文件从容器里拷出来docker cp mongo2:/tmp/backup ./backup-$(date %F)。生产环境建议用卷挂载备份目录或者直接备份到对象存储。恢复用mongorestore注意要恢复到主节点或者通过 mongos。恢复前最好先在一个独立实例上验证备份文件完整性别直接往生产库灌。故障现象优先排查常见根因节点 STARTUP2 不转 SECONDARY日志 sync 关键词oplog 缺口需全量同步频繁选主docker stats、events资源不足或网络抖动写入报 not masterrs.status 看主节点连到了从节点认证失败连接串 authSource用户建在 admin 库未指定磁盘满docker system df -v日志未轮转、oplog 过大最后分享一个我在调试期常用的小技巧给三个节点都加一个只读的mongosh别名脚本封装了rs.status()的格式化输出排查时一条命令看全所有节点状态比一个个进容器敲命令快得多。这种小工具不值钱但在故障处理的紧要关头能省下不少来回切换的时间。另外一个体会是容器化部署 MongoDB 集群配置文件版本管理特别重要。mongod.conf用 git 管起来每次改动都记一笔出问题时能快速回滚到上一个正常版本。我吃过一次亏调试认证问题时改了三处配置没记清楚改了什么结果新问题比原问题还难查最后靠对比 git diff 才理清。这种流程上的习惯比任何技巧都值钱。
阅读完成 · 觉得有帮助?