MongoDB Kubernetes Operator 部署与运维

讲解 MongoDB Community Operator 与 MongoDB Controllers for Kubernetes 的架构差异,给出 Helm 部署、MongoDBCommunity 资源定义、副本集与分片拓扑、TLS 与 SCRAM 认证、S3 备份和滚动升级的完整运维实践

在 Kubernetes 上跑有状态数据库,最难的从来不是"让它跑起来",而是让它跑得对:副本集要按顺序初始化并互相发现、成员故障要能自动重新配置、备份要能在不阻塞业务的前提下完成、升级要能滚动进行且保证选举安全。如果全靠手写 StatefulSet + 脚本,这些逻辑会散落在十几份 YAML 与运维文档里。

Operator 模式把这些运维知识编码进控制器(Controller):你声明一个"期望状态"(比如"3 成员的副本集,开启 TLS"),控制器负责把现实收敛到这个状态。本文以 MongoDB 官方的 Community Operator 为主线,讲清它的架构、部署方式与日常运维要点。

1. Operator 解决了什么问题

1.1 手写 StatefulSet 的痛点

用裸 StatefulSet 部署 MongoDB 副本集,至少需要人工处理以下五件事:

  • 成员发现:每个 Pod 的 DNS 名要写进 rs.initiate() 的配置里,Pod 重建后主机名变化需重新配置。
  • 初始化顺序:必须等第一个 Pod 就绪并发起副本集初始化,其余成员才能加入。
  • 认证引导:首个成员要创建管理员用户,密钥要在 Pod 之间共享。
  • 滚动升级:必须按"先升从节点、再 rs.stepDown() 主节点"的顺序,否则会引发不必要的选举。
  • 备份:要保证快照或 dump 在主从一致的时间点进行。

每一项单独看都不难,难的是它们之间有顺序依赖,且任一步失败都可能让副本集进入需要人工介入的状态。

1.2 能力对比

Operator 把上述每一项都变成了控制循环里的一个步骤。对比:

维度手写 StatefulSetOperator
副本集初始化人工脚本控制器自动 initiate
成员增减手工 rs.add/rs.remove改 replicas 字段即可
TLS 证书手工签发与挂载cert-manager 集成,自动轮换
备份外部 cron + 脚本CRD 声明式备份到 S3
升级手工编排顺序自动按安全顺序滚动
状态可见性kubectl get podCR 的 status 子资源
故障自愈需自行实现控制器持续调和

1.3 代价与适用边界

引入 Operator 不是没有成本。出问题时排查路径变成"CR 状态 → 控制器日志 → Pod 日志"三层,需要熟悉这套模型。此外,Operator 封装了绝大部分运维决策,遇到需要非常规操作(比如临时手工 rs.reconfig)时,可能与控制器的调和逻辑冲突——控制器会试图把状态拉回它认为正确的样子。

因此适用边界是:标准的副本集/分片部署用 Operator,非常规拓扑或需要深度定制的场景仍可能需要手写 StatefulSet。对绝大多数团队而言,Operator 带来的收益远大于成本。

2. 两种官方发行版

2.1 MongoDB Community Operator

MongoDB 提供两套 Kubernetes 方案,选型前必须分清:

方案核心 CRD适用场景许可
MongoDB Community OperatorMongoDBCommunity社区版、副本集/分片、自建开源
MongoDB Controllers for Kubernetes (MCK)MongoDB、MongoDBOpsManager、MongoDBAtlasProject企业版、Ops Manager 纳管、Atlas 项目企业许可

Community Operator 适合绝大多数自建场景,部署轻量、依赖少。它的 CRD 只有一个 MongoDBCommunity,学习曲线平缓。

2.2 MongoDB Controllers for Kubernetes

MCK 面向企业版特性(如 LDAP、审计、Kerberos)以及希望通过 Ops Manager 统一纳管的团队。其核心 CR 是 MongoDB,可指定 type: ReplicaSet 或 type: ShardedCluster,由 Ops Manager 驱动实际的部署与监控。MCK 还提供 MongoDBAtlasProject,用于从 Kubernetes 侧声明式管理 Atlas 项目与集群。

2.3 共同机制

两者都是典型的 Operator 实现(自定义资源 + 控制器),与 Kubernetes Operator 与 CRD 模式 的通用机制一致:控制器 watch CR 的变化,调和(reconcile)出实际的 StatefulSet、Service、Secret 等原生对象。理解这套"声明式 + 控制循环"模型是使用任何 Operator 的前提。

3. 用 Helm 部署 Operator

3.1 安装

以 Community Operator 为例,用 Helm 安装:

helm repo add mongodb https://mongodb.github.io/helm-charts
helm repo update

# 安装 operator(含 CRD)
helm install community-operator mongodb/community-operator \
  --namespace mongodb \
  --create-namespace \
  --set operator.watchNamespace="mongodb"

# 校验
kubectl -n mongodb get pods
kubectl get crd | grep mongodb

安装后会得到一个 Deployment(控制器)与 MongoDBCommunity 这个 CRD。

3.2 命名空间隔离与 leader election

watchNamespace 决定控制器能管理哪些命名空间下的 CR。生产环境建议按命名空间隔离,避免一个控制器误操作全部 CR——若留空,控制器会 watch 所有命名空间,任何一处 CR 写错都可能波及全局。

控制器本身是无状态的,可多副本部署并开启 leader election,保证同一时刻只有一个实例在调和,避免多个控制器并发修改同一副本集导致状态抖动。

3.3 常见安装问题

  • CRD 未安装:helm install 时若跳过 CRD(--skip-crds),后续创建 CR 会报 no matches for kind。
  • RBAC 不足:控制器需要创建 StatefulSet、Service、Secret、PVC 的权限,自定义 ServiceAccount 时要补全 Role。
  • 镜像拉取失败:私有集群需要配置 imagePullSecret 或镜像代理。

3.4 升级 Operator 自身

Operator 也需要升级,且Operator 版本与 CRD 版本必须匹配。升级顺序是:

# 1. 先升级 CRD(新字段可能依赖它)
kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes-operator/master/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml

# 2. 再升级 Helm release
helm upgrade community-operator mongodb/community-operator -n mongodb

先升 CRD 再升控制器这个顺序很重要:反过来会导致新控制器期望的字段在旧 CRD 里不存在,调和报错。升级前应先在测试环境验证 CR 兼容性——新版 Operator 有时会修改 CR 的默认值,可能触发生产环境的非预期滚动。

4. 定义副本集资源

4.1 最小可用 CR

一份可用的 MongoDBCommunity 资源:

apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
  name: rs-prod
  namespace: mongodb
spec:
  members: 3
  type: ReplicaSet
  version: "7.0.5"
  security:
    authentication:
      modes: ["SCRAM"]
  users:
    - name: app
      db: admin
      passwordSecretRef:
        name: app-password
      roles:
        - name: readWrite
          db: appdb
      scramCredentialsSecretName: app-scram
  statefulSet:
    spec:
      volumeClaimTemplates:
        - metadata:
            name: data-volume
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: fast-ssd
            resources:
              requests:
                storage: 200Gi
      template:
        spec:
          containers:
            - name: mongod
              resources:
                requests:
                  cpu: "2"
                  memory: 8Gi
                limits:
                  cpu: "4"
                  memory: 16Gi

4.2 关键字段解读

  • members: 3 是副本集成员数,改成 5 会触发控制器自动 rs.add,无需人工干预。副本集的选举与一致性语义可参考 https://plumephp.com/mongodb-replica-set/。
  • security.authentication.modes 设为 ["SCRAM"] 时,控制器会生成 SCRAM 凭据的 Secret,并把连接串所需信息写进 CR 的 status。
  • statefulSet.spec 是"透传"字段,可以塞入任何 StatefulSet 的合法配置,包括资源限制、亲和性、卷模板。这意味着 Operator 并没有封闭能力,需要细粒度控制时依然可以下探。
  • volumeClaimTemplates 决定数据卷,storageClassName 必须指向集群里真实存在的 StorageClass。

4.3 分片集群

分片集群则用 type: ShardedCluster 并额外声明 shardCount、mongosCount 与 config server 配置:

spec:
  type: ShardedCluster
  shardCount: 3
  mongosCount: 2
  mongodsPerShardCount: 3
  configServerCount: 3

分片键的选择决定了数据分布是否均匀,这部分设计见 https://plumephp.com/mongodb-sharding-key-design/。需要注意的是,分片集群的组件数会迅速放大:3 分片 × 3 副本 + 3 config + 2 mongos = 14 个 Pod,资源规划要提前算清。

4.4 用户与角色管理

users 字段声明应用用户与角色。角色遵循 MongoDB 内建角色体系,最常用的几个:

角色权限范围典型用途
read只读指定库报表、只读副本查询
readWrite读写指定库业务应用
dbAdmin库内管理(索引、统计)运维工具
clusterMonitor读集群状态监控采集
root超级权限仅在应急时使用

实践要点:应用用户绝不用 root,按最小权限分配;监控采集单独用一个只有 clusterMonitor 的账号;每个环境(dev/staging/prod)用独立的 Secret,避免密钥跨环境泄漏。控制器会在 CR 变化时同步更新用户,但修改已有用户的密码不会自动生效,需要显式删除并重建对应的 Secret,或由控制器触发用户更新。

5. 认证、TLS 与证书

5.1 SCRAM 认证

modes: ["SCRAM"] 是最常用的认证方式。控制器会在首次部署时自动创建管理员用户并写入 Secret,应用用户通过 users 字段声明。用户密码以 Secret 引用方式传入,不要把明文密码写在 CR 里:

kubectl -n mongodb create secret generic app-password \
  --from-literal=password="$(openssl rand -base64 24)"

5.2 TLS 与 cert-manager

生产环境必须开启 TLS。Community Operator 支持与 cert-manager 集成,由 cert-manager 签发并自动轮换证书:

spec:
  security:
    tls:
      enabled: true
      caCertificateSecretRef:
        name: mdb-ca
      certificateKeySecretRef:
        name: mdb-cert

更推荐的方式是让 cert-manager 作为 Issuer 自动管理证书,Operator 在 Pod 启动时把证书挂载进容器。

5.3 证书 SAN 陷阱

TLS 配置最容易踩的坑是证书的 SAN(Subject Alternative Name)。证书的 SAN 必须包含所有成员的服务 DNS 名(含 -0、-1、-2 后缀以及 headless service 名),否则副本集成员之间会因主机名校验失败而无法互联,表现为 rs.status() 里成员一直处于 STARTUP 或反复重连。

典型需要覆盖的名字:

rs-prod-0.rs-prod-svc.mongodb.svc.cluster.local
rs-prod-1.rs-prod-svc.mongodb.svc.cluster.local
rs-prod-2.rs-prod-svc.mongodb.svc.cluster.local
*.rs-prod-svc.mongodb.svc.cluster.local

此外,证书轮换后需要滚动重启才能生效,Operator 会触发,但要确认滚动过程不会同时重启多数成员。客户端侧,TLS 开启后连接串需要带上 tls=true&tlsCAFile=/path/ca.pem。证书与密钥的存放、轮换与审计属于安全运维范畴,可结合 https://plumephp.com/mongodb-security-backup/ 一起规划。

6. 备份与滚动升级

6.1 声明式备份

Community Operator 支持通过 MongoDBCommunity 的备份配置,或独立使用 MongoDB Backup 控制器把快照推送到 S3 兼容存储。核心思路是用副本集一致性读快照保证备份的一致性点,避免备份到跨成员不一致的状态。

备份策略应与恢复演练配套。仅备份不演练等于没有备份——真正出事时才发现备份不可用是最糟的情况。完整的恢复流程与 RPO/RTO 设计见 https://plumephp.com/mongodb-security-backup/。

6.2 滚动升级顺序

修改 spec.version 即可触发升级。控制器的升级顺序是:

  1. 先逐个升级从节点(secondary),每次只动一个,等其重新同步并追平。
  2. 所有从节点升级完成后,对主节点执行 rs.stepDown(),触发选举,新主产生后升级旧主。

这个顺序保证了任一时刻多数成员可用,不会因升级丢主。

6.3 oplog 与初始同步

升级前务必确认 oplog 足够大,否则从节点重启后可能因落后太多而触发全量重同步(initial sync),代价极高——initial sync 会重新拉取全部数据并重建索引,对一个大集合可能耗时数小时。

// 查看 oplog 大小与时间窗口
db.getReplicationInfo()
// 关注 timeDiffHours,应大于预期的最长维护窗口

滚动升级期间还应确保 PodDisruptionBudget 配置合理,避免与其他运维操作(节点驱逐、集群升级)叠加导致多数成员同时不可用。

7. 存储与资源规划

7.1 StorageClass 选择

storageClassName 应选支持 allowVolumeExpansion 的 CSI 驱动,便于后续在线扩容:

kubectl get storageclass
# 确认 ALLOWVOLUMEEXPANSION 为 true

若选用的 StorageClass 不支持扩容,后续磁盘不足时只能重建 PVC 并迁移数据,代价极大。

7.2 内存与 WiredTiger

资源限制要给足。MongoDB 的 WiredTiger 缓存默认取 (内存 - 1GB) / 2,limits.memory 过低会让缓存过小、频繁刷盘,更严重的是触发 cgroup OOMKill,Pod 被杀后重启又会引发重同步。经验值:limits.memory 至少是工作集的 1.5 倍,且 requests 与 limits 不宜相差过大,否则调度器难以保证内存。

7.3 反亲和与拓扑分布

生产环境应配置反亲和(anti-affinity),让副本集成员尽量落在不同节点甚至不同可用区:

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: rs-prod-svc
        topologyKey: kubernetes.io/hostname

requiredDuringScheduling 保证硬性分散,但节点不足时会导致 Pod 无法调度;preferredDuringScheduling 更灵活但可能全部落在同一节点。3 成员的副本集通常用 preferred + 跨可用区拓扑键。

7.4 网络暴露与客户端接入

Operator 默认创建两种 Service:

  • Headless Service(<name>-svc):无 ClusterIP,用于副本集成员之间的稳定 DNS 发现,成员之间靠它互联。
  • ClusterIP Service:供集群内客户端接入,自动指向当前主节点。

集群内应用连接串应使用带 replicaSet 参数的多主机形式:

mongodb://app:pass@rs-prod-0.rs-prod-svc.mongodb.svc.cluster.local:27017,rs-prod-1...:27017,rs-prod-2...:27017/appdb?replicaSet=rs-prod&authSource=admin

若需要从集群外访问,通常用 kubectl port-forward 做临时调试,生产环境则通过 LoadBalancer Service 或 Ingress(需 TCP 转发)暴露。不要把 MongoDB 直接暴露到公网——即使有认证,暴露 27017 端口仍会招来大量扫描与暴力破解。

7.5 常见故障速查

现象常见根因排查入口
Pod 一直 PendingPVC 无法绑定 / 资源不足 / 反亲和冲突kubectl describe pod 的 Events
成员一直 STARTUPTLS SAN 不匹配 / 主机名解析失败rs.status()、成员日志
反复重同步oplog 太小 / 磁盘抖动db.getReplicationInfo()
写入变慢WiredTiger 缓存不足 / OOMKill容器 limits.memory、dmesg
控制器不调和CRD 版本不匹配 / RBAC 不足控制器 Pod 日志

排查时记住三层路径:CR 状态 → 控制器日志 → Pod 日志。大多数"Operator 不干活"的问题最终都落在 CRD 版本或 RBAC 上。

8. 实践建议

  1. 按命名空间隔离控制器,用 watchNamespace 限制其管理范围,降低误操作半径。
  2. 证书 SAN 要覆盖全部成员 DNS 名,这是 TLS 副本集最常见的初始化失败原因。
  3. 升级前检查 oplog 大小,并确认 PodDisruptionBudget 不会允许多数成员同时下线。
  4. 备份必须配套恢复演练,并明确 RPO/RTO 目标。
  5. 资源限制要给足,WiredTiger 缓存依赖内存,limits.memory 过低会引发 OOMKill 与频繁重同步。
  6. 善用 statefulSet.spec 透传,需要亲和性、拓扑分布约束时不必绕开 Operator。
  7. 分片集群提前算清组件数,资源与网络开销随组件数成倍放大。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「mongodb」更多文章

  1. 数据生命周期、TTL 与冷热归档
  2. $graphLookup 与层次结构建模
  3. GridFS 与大文件存储实践