Ceph+K8s 中间件实战 | 第 8 天:K8s 部署 MongoDB 副本集集群(Ceph RBD + 架构设计 + 登录)

第 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 实现副本集稳定网络标识的关键。

K8s Logo

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 个工作节点。

K8s Logo

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. 架构设计:副本集通过 1 Primary + N Secondary + oplog 异步复制实现高可用与数据冗余;类 Raft 选举协议保证多数派写入不丢失
  2. 存储规划:每个成员绑定独立 Ceph RBD PVC(RWO),存放 WiredTiger 数据文件与 oplog,利用块设备低延迟特性
  3. 部署方式:architecture: replicaset 一键开启副本集模式,Chart 自动完成 rs.initiate() 与成员加入;storageClass 指向 Ceph RBD 实现开箱持久化
  4. 验证巡检:rs.status() 查成员健康度、rs.printReplicationInfo() 看 oplog 窗口、逐节点 serverStatus 巡检连接数与内存

MongoDB 副本集适合写入集中、需要高可用与读分离的文档型业务场景。下篇我们将转向服务注册与配置中心领域。

下期预告

第 9 天:K8s 部署 Nacos 注册配置中心(集群架构 + 登录巡检) —— Nacos 是阿里开源的服务注册与动态配置中心,我们将使用社区维护的 nacos-group/nacos Chart,部署 3 节点集群,并探讨它如何与 MySQL(本系列第 4 天已部署)联动作为外置存储。

系列目录

微信二维码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像 - 恒星
欢迎您留下宝贵的见解!
提交
头像 - 恒星

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容