第 8/15 天
在前面的篇章里,我们已经把 MySQL(关系型)、Redis Cluster / Sentinel(缓存)、MinIO(对象存储)三种典型中间件搬上了 Ceph + K8s 的底座。今天的主角是文档型数据库 MongoDB —— 它介于关系型与 KV 之间,凭借灵活的 Schema、丰富的聚合管道、原生的水平扩展能力,在内容管理、日志分析、物联网时序数据等场景中被广泛采用。
本篇将使用 Bitnami 官方维护的 bitnami/mongodb Helm Chart,在 K8s 上部署一个 3 节点副本集(Replica Set),通过 Ceph RBD StorageClass 为每个成员提供独立持久化卷,并覆盖架构设计、values 配置、部署验证、登录连接与日常巡检的完整闭环。
一、MongoDB 副本集设计架构
1.1 为什么选择副本集(Replica Set)
MongoDB 在生产环境中通常不以单实例运行,而是以副本集形态部署。副本集是一组维护相同数据集的 MongoDB 进程,其中只有一个成员担任 Primary 负责写入,其余成员作为 Secondary 异步同步 Primary 的 oplog(操作日志)。这种架构同时解决了高可用与数据冗余两个核心诉求。
副本集的核心特性:
| 特性 | 说明 |
|---|---|
| 自动故障转移 | Primary 宕机后,Secondary 通过选举自动提升为新 Primary |
| 多数派写入 | Write Concern w:majority 保证写入不丢失 |
| 读分离 | Secondary 可承担只读查询,分担 Primary 压力 |
| Oplog 同步 | Primary 的操作写入 oplog,Secondary 持续拉取回放 |
| 弹性扩展 | 可随时新增成员扩容,无需停机 |
1.2 组件拓扑
本次部署的 MongoDB 副本集拓扑如下:
┌─────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────────────────────┐ │
│ │ mongodb-headless Service │ │
│ │ (ClusterIP: None – Headless)│ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────┴──────────────────┐ │
│ │ │ │
│ mongodb-0 mongodb-1 mongodb-2│
│ (Primary) (Secondary)(Secondary)│
│ │ │ │ │ │
│ └──────────┴──────────┘ │ │
│ │ │
└──────────────┼──────────────────────┘
│ PVC (ReadWriteOnce)
▼
┌──────────────────┐
│ Ceph RBD │
│ StorageClass │
│ (ceph-rbd-sc) │
└──────────────────┘
部署规模:3 个 Pod 组成副本集(满足多数派选举的最低要求),每个 Pod 绑定一个独立的 Ceph RBD PVC,使用 Headless Service 提供稳定的 DNS 名(mongodb-0.mongodb-headless.middleware.svc.cluster.local),这是 StatefulSet 实现副本集稳定网络标识的关键。

1.3 数据流向
副本集内部的数据复制流向:
客户端写请求
│
▼
Primary (mongodb-0) 写入数据 + 记录 oplog
│
├─→ Secondary (mongodb-1) 拉取 oplog 回放 → 数据一致
│
└─→ Secondary (mongodb-2) 拉取 oplog 回放 → 数据一致
客户端读请求
│
├─→ ReadPreference=primary → 始终读 Primary(默认)
├─→ ReadPreference=secondary → 路由到 Secondary
└─→ ReadPreference=nearest → 路由到延迟最低的节点
oplog 是一个固定大小(capped)的集合,位于 local 数据库,按写入顺序记录所有改变数据的操作。Secondary 通过 find 命令持续从 Primary 拉取 oplog 并在本地回放,从而实现异步复制。当 oplog 窗口耗尽(Secondary 离线过久),需要执行初始同步(initial sync)重新全量拷贝。
1.4 高可用与选举机制
MongoDB 副本集的高可用依赖一套类 Raft 的选举协议:
- 心跳检测:成员间每 2 秒互发心跳,超过 10 秒(默认
electionTimeoutMillis)未响应则标记为疑似失联 - 选举触发:Primary 失联后,具备选举权的 Secondary 发起选举
- 多数派投票:获得过半数(n/2 + 1)选票的成员成为新 Primary。3 节点副本集需要 2 票,因此容忍 1 个节点故障
- 优先级与选举规则:优先级高的成员优先当选 Primary;还可通过隐藏节点、仲裁节点(Arbiter)控制选举拓扑
这就是为什么生产副本集成员数通常为奇数(3、5、7)——偶数成员在脑裂场景下无法形成多数派,会导致无法选举。
1.5 存储规划
MongoDB 使用 WiredTiger 存储引擎,数据文件落盘到 Ceph RBD 卷:
| 存储项 | 类型 | 大小 | StorageClass | 说明 |
|---|---|---|---|---|
| 数据卷 | RBD (RWO) | 20Gi/节点 | ceph-rbd-sc | 存储 WiredTiger 数据文件 + oplog |
| 备份快照 | Ceph snapshot | 按需 | – | 利用 RBD 快照做定时备份 |
选择 RBD 而非 CephFS 的原因与 Redis/MySQL 一致:MongoDB 每个成员只需单 Pod 读写(RWO),WiredTiger 引擎对块设备的随机写、压缩、检查点刷盘操作,RBD 的块设备语义与本地 SSD 体验最接近,避免了 CephFS 元数据开销与文件锁语义的不确定性。
二、部署实战(Bitnami Helm Chart)
2.1 添加 Bitnami Helm 仓库
# 添加 Bitnami 官方 Chart 仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# 验证 mongodb chart 可用
helm search repo bitnami/mongodb
# 预期输出:
# NAME CHART VERSION APP VERSION DESCRIPTION
# bitnami/mongodb 16.x.x 8.x.x MongoDB(R) is a … database …
# 查看支持的 values 参数(可选,用于对照字段名)
helm show values bitnami/mongodb > /tmp/mongodb-default-values.yaml
2.2 编写 values.yaml 自定义配置
核心配置要点:开启副本集架构、指定 3 副本、指向 Ceph RBD StorageClass、配置 root 密码与业务库账号、调整资源限制。
# /tmp/mongodb-values.yaml
# MongoDB Helm Chart 自定义配置(副本集模式)
# 架构选择:replicaset(副本集),而非 standalone
architecture: replicaset
# 副本集成员数(奇数,满足多数派选举)
replicaCount: 3
# 全局镜像配置
global:
imageRegistry: ""
imagePullSecrets: []
image:
registry: docker.io
repository: bitnami/mongodb
tag: 8.0.4-debian-12-r1
pullPolicy: IfNotPresent
# 认证配置
auth:
enabled: true
# root 管理员密码(务必修改)
rootPassword: "MongoDB#Root@2026"
# 业务库账号(Chart 自动创建用户与库)
usernames: ["appuser"]
passwords: ["App#User@2026"]
databases: ["appdb"]
# 副本集内部互信密钥(成员间鉴权)
replicaSetKey: "ReplSet#Key@2026Secret"
# 副本集名称
replicaSetName: "rs0"
# 持久化配置 —— 关键:指向 Ceph RBD StorageClass
persistence:
enabled: true
storageClass: "ceph-rbd-sc" # 第 2 天创建的 Ceph RBD StorageClass
accessModes:
– ReadWriteOnce
size: 20Gi
# 数据挂载子目录
mountPath: /bitnami/mongodb
# 存储子路径(StatefulSet 每个卷独立)
subPath: ""
# 每个节点的资源限制
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
# 网络配置 —— Headless Service 提供稳定 DNS
service:
name: "mongodb-headless"
type: ClusterIP
ports:
mongodb: 27017
# 仲裁节点(3 全功能成员已满足多数派,无需 Arbiter)
arbiter:
enabled: false
# Pod 反亲和——让 3 个副本分散到不同节点
podAntiAffinityPreset: hard
# ServiceAccount
serviceAccount:
create: true
name: ""
关键字段说明:
–architecture: replicaset是切换到副本集模式的开关,Bitnami Chart 会自动初始化rs.initiate()与添加成员。
–replicaSetKey必须设置且各成员一致,用于内部节点间鉴权,否则副本集无法建立。
–podAntiAffinityPreset: hard让 3 个 Pod 强制分散到不同 K8s 节点,避免单节点故障带走整个副本集——前提是集群至少有 3 个工作节点。

2.3 helm install 部署
# 创建命名空间(若已有 middleware 命名空间可跳过)
kubectl create namespace middleware –dry-run=client -o yaml | kubectl apply -f –
# 部署 MongoDB 副本集
helm install mongodb bitnami/mongodb
–namespace middleware
–version 16.x.x
–values /tmp/mongodb-values.yaml
–timeout 10m
# 预期输出:
# NAME: mongodb
# LAST DEPLOYED: …
# NAMESPACE: middleware
# STATUS: deployed
# REVISION: 1
# NOTES: … MongoDB can be accessed on the following DNS names from within your cluster:
# mongodb-0.mongodb-headless.middleware.svc.cluster.local:27017
# mongodb-1.mongodb-headless.middleware.svc.cluster.local:27017
# mongodb-2.mongodb-headless.middleware.svc.cluster.local:27017
2.4 kubectl 验证 Pod / Service / PVC 状态
# 查看 StatefulSet Pod 状态(3 个 Pod 依次启动)
kubectl get pods -n middleware -l app.kubernetes.io/instance=mongodb -w
# 预期输出:
# NAME READY STATUS RESTARTS AGE
# mongodb-0 1/1 Running 0 3m
# mongodb-1 1/1 Running 0 3m
# mongodb-2 1/1 Running 0 3m
# 查看 Service
kubectl get svc -n middleware -l app.kubernetes.io/instance=mongodb
# 预期输出:
# NAME TYPE CLUSTER-IP PORT(S) AGE
# mongodb-headless ClusterIP None 27017/TCP 4m
# 查看 PVC(3 个 PVC,均绑定 Ceph RBD)
kubectl get pvc -n middleware -l app.kubernetes.io/instance=mongodb
# 预期输出:
# NAME STATUS VOLUME CAPACITY STORAGECLASS AGE
# data-mongodb-0 Bound pvc-xxxx-xxxx 20Gi ceph-rbd-sc 4m
# data-mongodb-1 Bound pvc-yyyy-yyyy 20Gi ceph-rbd-sc 4m
# data-mongodb-2 Bound pvc-zzzz-zzzz 20Gi ceph-rbd-sc 4m
三、登录验证
3.1 提取密码并连接副本集
# 提取 root 密码
export MONGODB_ROOT_PASSWORD=$(kubectl get secret –namespace middleware mongodb
-o jsonpath="{.data.mongodb-root-password}" | base64 -d)
echo "Root Password: $MONGODB_ROOT_PASSWORD"
# 进入 Primary Pod,使用 mongosh 连接 admin 库
kubectl exec -it -n middleware mongodb-0 — mongosh
–host rs0/mongodb-0.mongodb-headless.middleware.svc.cluster.local:27017
–authenticationDatabase admin -u root -p "$MONGODB_ROOT_PASSWORD"
3.2 查看副本集状态
连接成功后,在 mongosh 中执行:
// 查看副本集状态(成员、状态、健康度)
rs.status()
// 关键字段:
// "set" : "rs0"
// "myState" : 1 <- 1=Primary, 2=Secondary
// "members" : [
// { "name":"mongodb-0…", "stateStr":"PRIMARY", "health":1 },
// { "name":"mongodb-1…", "stateStr":"SECONDARY", "health":1 },
// { "name":"mongodb-2…", "stateStr":"SECONDARY", "health":1 }
// ]
// 查看副本集配置(成员、优先级、仲裁节点)
rs.conf()
// 查看复制拓扑与延迟
rs.printReplicationInfo()
// 预期输出:
// configured oplog size: 2048.0MB
// log length start to end: 1234.0 secs (0.34hrs)
// oplog first event time: …
// oplog last event time: …
// now: …
3.3 验证数据读写与复制
// 切换到业务库
use appdb
// 验证业务账号
db.auth("appuser", "App#User@2026")
// 写入测试文档(带多数派写入确认)
db.events.insertOne(
{ source: "ceph-k8s-test", ts: new Date(), msg: "hello mongodb replica set" },
{ writeConcern: { w: "majority", j: true } }
)
// 查询验证
db.events.find().pretty()
// 预期输出:
// [{ _id: …, source: "ceph-k8s-test", ts: …, msg: "hello mongodb replica set" }]
// 在 Secondary 上验证数据已同步(需先执行 rs.secondaryOk())
db.getMongo().setReadPref("secondary")
db.events.find().count()
四、日常巡检命令
4.1 副本集健康检查
# 一键查看副本集状态(Pod 外执行)
kubectl exec -n middleware mongodb-0 — mongosh
–host rs0/mongodb-0.mongodb-headless.middleware.svc.cluster.local:27017
–authenticationDatabase admin -u root -p "$MONGODB_ROOT_PASSWORD"
–eval "rs.status().members.map(m => ({name:m.name, state:m.stateStr, health:m.health, lag:m.optimeDate}))"
# 预期输出 3 个成员均为 health:1,state 为 PRIMARY/SECONDARY
4.2 节点状态与内存巡检
# 逐节点查看 serverStatus 关键指标
for i in 0 1 2; do
echo "=== mongodb-$i ==="
kubectl exec -n middleware mongodb-$i — mongosh
–authenticationDatabase admin -u root -p "$MONGODB_ROOT_PASSWORD"
–quiet –eval "const s=db.serverStatus();printjson({conns:s.connections.current, mem:s.mem.residentMb, uptime:s.uptime, version:s.version})"
done
# 预期输出:
# === mongodb-0 ===
# { "conns": 12, "mem": 320, "uptime": 3600, "version": "8.0.4" }
4.3 Pod 与 PVC 巡检
# Pod 状态巡检(含所在节点)
kubectl get pods -n middleware -l app.kubernetes.io/instance=mongodb -o wide
# PVC 使用率(Ceph RBD 卷)
kubectl get pvc -n middleware -l app.kubernetes.io/instance=mongodb
# 查看最近事件(排查 Pod 异常)
kubectl get events -n middleware –sort-by='.lastTimestamp' | tail -20
# 查看 WiredTiger 数据文件
kubectl exec -n middleware mongodb-0 — ls -lh /bitnami/mongodb/data
# 预期输出:
# drwxr-xr-x … collection-xxx–xxx.wt
# -rw-r–r– … _mdb_catalog.wt
# -rw-r–r– … sizeStorer.wt
# -rw-r–r– … WiredTiger.wt
4.4 日志查看
# 查看单个节点日志
kubectl logs -n middleware mongodb-0 –tail=50
# 查看所有节点最近日志
for i in 0 1 2; do
echo "=== mongodb-$i ==="
kubectl logs -n middleware mongodb-$i –tail=10
done
# 在 mongosh 内查看复制相关日志
kubectl exec -n middleware mongodb-0 — mongosh
–authenticationDatabase admin -u root -p "$MONGODB_ROOT_PASSWORD"
–quiet –eval "db.adminCommand({getLog:'rs'}).log.slice(-10).join('n')"
4.5 容量与 oplog 巡检
# 查看各库大小与文档数
kubectl exec -n middleware mongodb-0 — mongosh
–authenticationDatabase admin -u root -p "$MONGODB_ROOT_PASSWORD"
–quiet –eval "db.adminCommand('listDatabases').databases.forEach(d=>print(d.name+' '+(d.sizeOnDisk/1024/1024).toFixed(1)+'MB'))"
# 查看 oplog 窗口大小(决定 Secondary 能离线多久)
kubectl exec -n middleware mongodb-0 — mongosh
–authenticationDatabase admin -u root -p "$MONGODB_ROOT_PASSWORD"
–quiet –eval "rs.printReplicationInfo()"
五、常见问题
Q1:PVC 一直 Pending,StorageClass 名称写什么?
请执行 kubectl get sc 查看集群中实际可用的 StorageClass,将 values.yaml 中 persistence.storageClass 改为实际名称(如 ceph-rbd-sc 或 csi-ceph-rbd)。Ceph RBD 默认只支持 ReadWriteOnce,切勿使用 ReadWriteMany。若 Pod 反亲和导致无法调度(3 个 Pod 抢不到 3 个节点),可临时改为 podAntiAffinityPreset: soft。
Q2:Pod 启动后副本集始终只有一个 PRIMARY,其余为 STARTUP / RECOVERING?
通常是因为 auth.replicaSetKey 各成员不一致或未设置,导致内部鉴权失败、Secondary 无法同步。确认所有成员使用相同 key 后重启 Pod;也可手动在 PRIMARY 上执行 rs.add("mongodb-1.mongodb-headless.middleware.svc.cluster.local:27017") 逐个添加成员观察。
Q3:写入报错 NotWritableError: not primary 或读报错 not master and slaveOk=false?
副本集发生故障转移,原 Primary 已变为 Secondary。写操作请连接副本集 URL(rs0/host0,host1,host2)让驱动自动路由到新 Primary;只读查询访问 Secondary 时需在会话中设置 readPreference=secondary 或执行 rs.secondaryOk()。
六、总结
本篇使用 Bitnami 官方 bitnami/mongodb Helm Chart,在 K8s 上完成了 3 节点 MongoDB 副本集的部署,核心要点回顾:
- 架构设计:副本集通过 1 Primary + N Secondary + oplog 异步复制实现高可用与数据冗余;类 Raft 选举协议保证多数派写入不丢失
- 存储规划:每个成员绑定独立 Ceph RBD PVC(RWO),存放 WiredTiger 数据文件与 oplog,利用块设备低延迟特性
- 部署方式:
architecture: replicaset一键开启副本集模式,Chart 自动完成rs.initiate()与成员加入;storageClass 指向 Ceph RBD 实现开箱持久化 - 验证巡检:
rs.status()查成员健康度、rs.printReplicationInfo()看 oplog 窗口、逐节点serverStatus巡检连接数与内存
MongoDB 副本集适合写入集中、需要高可用与读分离的文档型业务场景。下篇我们将转向服务注册与配置中心领域。
下期预告
第 9 天:K8s 部署 Nacos 注册配置中心(集群架构 + 登录巡检) —— Nacos 是阿里开源的服务注册与动态配置中心,我们将使用社区维护的 nacos-group/nacos Chart,部署 3 节点集群,并探讨它如何与 MySQL(本系列第 4 天已部署)联动作为外置存储。
系列目录
- ✅ 第 1 天:Ceph 分布式存储回顾与 K8s 存储体系概述
- ✅ 第 2 天:K8s 接入 Ceph RBD 块存储(CSI Driver + StorageClass 配置实战)
- ✅ 第 3 天:K8s 接入 CephFS 共享文件存储(多读场景与性能调优)
- ✅ 第 4 天:K8s 部署 MySQL 高可用集群(Ceph RBD 持久化 + 架构设计 + 登录巡检)
- ✅ 第 5 天:K8s 部署 Redis Cluster 集群(Ceph RBD + 架构设计 + 巡检命令)
- ✅ 第 6 天:K8s 部署 Redis Sentinel 哨兵模式(高可用架构 + 登录验证)
- ✅ 第 7 天:K8s 部署 MinIO 对象存储集群(分布式架构 + 运维巡检)
- 🔥 第 8 天:K8s 部署 MongoDB 副本集集群(Ceph RBD + 架构设计 + 登录)(本文)
- ⏳ 第 9 天:K8s 部署 Nacos 注册配置中心(集群架构 + 登录巡检)
- ⏳ 第 10 天:K8s 部署 Zookeeper 集群(分布式协调架构 + 运维命令)
- ⏳ 第 11 天:K8s 部署 Elasticsearch 集群(Ceph RBD + 架构设计 + 巡检)
- ⏳ 第 12 天:K8s 部署 Kafka 集群(Ceph RBD + 消息队列架构 + 运维)
- ⏳ 第 13 天:K8s 部署 GitLab 代码托管平台(Ceph RBD 持久化 + 架构 + 巡检)
- ⏳ 第 14 天:K8s 部署 ClickHouse 列式数据库集群(Ceph RBD + 架构设计)
- ⏳ 第 15 天:K8s 中间件统一监控与运维巡检总结(Prometheus + Grafana 全景)

















暂无评论内容