闲谈KubeBlocks For MongoDB设计实现
引言
MongoDB 是目前最受欢迎的 NoSQL 数据库之一,这跟它的多种特性有关,如面向文档、高性能、易使用等等,业务使用较为友好。不过由于它本身的复杂性,如节点多状态、多拓扑结构等,使得 MongoDB 的运维管理较为困难。在 K8S 环境下,由于资源的灵活编排,网络的隔离限制,进一步增加了 MongoDB 云原生化的难度,本文将从以下几点阐述 KubeBlocks For MongoDB 在设计实现过程中遇到的难点及解决方案,让大家有一个基本了解及能更好的使用KB。
多拓扑结构的灵活定义
MongoDB 支持单节点、副本集、分片集群等部署形态,以满足不同的业务需求。不同的拓扑形态部署方式差异较大,比如副本集支持多节点部署,每个节点除了状态不一样,数据是完全一致的(仲裁节点不包含数据)。分片包含多个不同组件 mongos、configServer、Shard,不同组件的管理方式不一样,而且组件管理有顺序依赖。为了能够定义和实现不同引擎的不同拓扑形态(KubeBlocks是多引擎的通用管理平台),KubeBlocks 在 ClusterDefinition 中支持了 topology 的自定义功能,MongoDB 实现如下:
YAML
apiVersion: apps.kubeblocks.io/v1
kind: ClusterDefinition
metadata:
name: mongodb
spec:
topologies:
- components:
- compDef: mongodb-
name: mongodb
default: true
name: replicaset
- components:
- compDef: mongo-mongos-
name: mongos
- compDef: mongo-config-server-
name: config-server
name: sharding
orders:
update:
- mongos
- config-server
- shard
shardings:
- name: shard
shardingDef: mongo-shard-
这里定义了两种拓扑形态:replicaset 和 sharding。顾名思义 replicaset 用于副本集实现,sharding 用定义分片集群。而单节点则认为是副本数为1的 replicaset。其中分片集群中不同组件的依赖关系通过 orders 定义,确保分片集群在运维操作时按照指定顺序执行。不同组件的具体实现可以参考 compDef 指定的 componentDefinition,代码在kubeblocks-addons 仓库中。
创建 Cluster 时,通过 topology 指定所需的拓扑形态:
C++
apiVersion: apps.kubeblocks.io/v1
kind: Cluster
spec:
terminationPolicy: {{ .Values.extra.terminationPolicy }}
clusterDef: mongodb
{{- if or (eq .Values.mode "replicaset") (eq .Values.mode "standalone") }}
topology: replicaset
{{- else if eq .Values.mode "sharding" }}
topology: sharding
{{- end }}
...
无法避免的数据倾斜
MongoDB 分片集群模式下,数据倾斜是最常见的问题之一。可能有多种原因导致,如分片键的选择、业务的数据属性等等,总之数据倾斜基本无法避免。KubeBlocks For MongoDB 支持单个或多个 shard 的独立变更,当数据出现倾斜时,可通过 shardTemplates 对指定的 shard 执行按需变配:
PlainText
apiVersion: apps.kubeblocks.io/v1
kind: Cluster
spec:
clusterDef: mongodb
topology: sharding
shardTemplates:
- shardIDs:
- shard-xxx #指定变配的shard ID
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: 100m
memory: 204Mi
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
如何提供K8S外部访问
在默认情况下,MongoDB 副本集使用 Pod 的 headless service 对外提供访问,副本之间也是通过 headless service进行通信,这主要应用于业务在同一 K8S 集群内部的场景。当业务在 K8S 集群之外时,headless service 无法访问,由于 MongoDB 的集群发现协议,简单的对 headless service 进行路由转发,只能提供 MongoDB 的直接访问,无法实现 MongoDB 副本集的高可用链接。因此 KubeBlocks For MongoDB 支持了主机网络模式,用于提供 K8S 集群外部业务访问 MongoDB 的能力。
创建 Cluster 时,可通过 annotation 指定实例为主机网络模式:
YAML
apiVersion: apps.kubeblocks.io/v1
kind: Cluster
metadata:
annotations:
{{- if ne .Values.mode "sharding" }}
kubeblocks.io/host-network: mongodb
{{- end }}
...
实现逻辑是:当 Cluster 指定为主机网络模式时,每个副本运行的 Pod都 采用主机网络模式,副本集初始化所使用的 Host 也采用 Node IP,确保对外和副本之间都使用主机网络。
使用主机网络后带来一个问题是:当 Pod 发生漂移后,它的 Node IP也发生变化,导致副本集中节点无法重新加入集群,出现异常状态。为了解决 Pod 漂移问题,KubeBlocks For MongoDB 实现了一个动态适配逻辑,当 Pod 的 Node IP 发生变化时,它会自动修正副本集中的节点配置,确保节点状态和集群状态正常。
缺点:
- 只能用于业务可联通主机网络的场景;
- 在同一时刻,只能存在少数节点不可用。
建议,如果需要K8S集群外部访问,尽量使用 MongoDB 分片集群形态,mongos 的链接地址可以进行路由转发。
是否需要高可用服务
MongoDB 副本集和分片集群一般采用三副本架构,当一个副本出现时,它会自动进行 Failover,以正常服务。乍一看,MongoDB 已经具备了高可用能力,加上Pod的自动重启机制,就无需额外的高可用服务了。所以现有的 MongoDB Operator 基本只提供了 MongoDB 多副本形态,没有高可用服务。但这真的高可用,可以上生产吗?
其实这是不够的,三副本场景下,一个副本异常,虽然可以继续服务,但实例已处于亚健康状态了。如果再出现副本异常,实例就不可服务。所以三副本提供了一定的高可用冗余能力,但并不能保证一定不出问题。
Pod自动重启机制加上livenessProbe,可以在异常场景下,实现服务自动重启。这个在stateless场景下很好用,但在数据库这种重状态的场景下使用,会一种灾难,它会导致一些不可预期的问题,比如不间断重启、进程非预期强杀等等。
KubeBlocks For MongoDB 提供了额外的高可用服务,在 Failover 时,会探测到异常情况,发生告警事件,对于部分异常状态会尝试进行自动修复,不能自动修复的场景,交由人工处理,确保服务正常。
社区版缺少的企业级备份恢复
社区版 MongoDB 只支持逻辑备份,物理备份需要文件系统支持快照能力时才能通过快照实现,而目前基本只有云盘才支持,其它大部分环境是不支持的。导致 MongoDB 的备份非常受限,在数据量大、负载高的情况下甚至无法实现有效备份(没有热备能力,逻辑备份无法保证数据的一致性)。如果需要完全的备份能力需要购买 MongoDB 企业版或者 Atlas 实例才行。
显然这对于数据安全要求较高的 DB 服务来说是难以接受的,因此 KubeBlocks For MongoDB 引进了 Percona 的扩展能力,实现了包括物理备份在内的多种备份能力,以满足企业级服务要求。
逻辑备份
适用于数量小,负载低的实例,优点是灵活,可以指定 db 或 collection 进行备份或恢复。缺点是速度较慢,在负载高时,可能会备份失败
物理备份
物理备份时采用了 Wiredtiger 支持的 backupCursor 能力,确保备份过程中,业务读写不受影响。适用数量大、负载高的实例,优点是速度快,它的备份速度至少是逻辑备份的 5 倍以上。缺点是不够灵活,只能实现整体备份和恢复
PITR
支持副本集和分片集群的日志备份,及按时间点恢复的能力。确保实例数据可以快速精准恢复。
必不可少的审计日志
审计是企业级服务中必不可少的能力,这个在 MongoDB 社区版中也是没有,需要购买。KubeBlocks For MongoDB 引入了 Percona 扩展能力,支持 JSON 格式,方便审计排查:
Bash
{ "atype" : "clientMetadata", "ts" : { "$date" : "2025-10-14T09:26:26.261+00:00" }, "local" : { "ip" : "10.13.33.216", "port" : 27017 }, "remote" : { "ip" : "10.13.34.28", "port" : 45592 }, "users" : [], "roles" : [], "param" : {}, "result" : 0 }
{ "atype" : "clientMetadata", "ts" : { "$date" : "2025-10-14T09:26:26.267+00:00" }, "local" : { "ip" : "10.13.33.216", "port" : 27017 }, "remote" : { "ip" : "10.13.34.28", "port" : 45620 }, "users" : [], "roles" : [], "param" : {}, "result" : 0 }
{ "atype" : "clientMetadata", "ts" : { "$date" : "2025-10-14T09:26:26.267+00:00" }, "local" : { "ip" : "10.13.33.216", "port" : 27017 }, "remote" : { "ip" : "10.13.34.28", "port" : 45608 }, "users" : [], "roles" : [], "param" : {}, "result" : 0 }
结束语
这不是一篇整体性介绍 KubeBlocks For MongoDB 实现的文章,只是想分享一下在实现过程中遇到的几个问题。顺便感慨一下:随着 MongoDB 商业化的成功,用户越来越多,其开源社区的运营却每况愈下。官网好多技术博客都下架了, MongoDB 中文社区也基本没有更新了。对于后来者而言,难度越来越高了。
KubeBlocks For MongoDB 将继续专注 MongoDB 在云原生方向的发展,提供方便、易用、敏捷的 MongoDB 服务,欢迎大家使用、交流、贡献。