
一、先说那个绕不过去的断层
容器化改造做到最后,数据库往往是唯一一块还留在原地的东西。原因不难理解:微服务、网关、消息队列这些组件天生"无状态",Pod 挂了拉一个新的、流量大了多跑几个副本,K8s 处理起来得心应手。数据库不一样------数据要落盘、节点之间分主备、启停有严格的先后顺序。把它塞进 K8s 之后,事情并没有变简单,反而多出一层尴尬:资源是 K8s 在管,业务状态还是人在管。
于是出现了几个非常具体的麻烦:
- 拉起一套集群要协调的资源太多。 StatefulSet、Service、Headless Service、ConfigMap、Secret、PV/PVC 得逐个配好,少一个引用或写错一个端口,Pod 就是起不来,而报错信息通常并不告诉你错在哪。
- 两套操作体系来回切。 K8s 一侧用 kubectl,数据库一侧还得 exec 进容器敲命令,同一次运维动作被拆成两半,出问题时难以还原现场。
- 规模一上去就开始失控。 三套集群靠人盯得住,三十套就只能靠记忆和文档,而文档总是会过期。
问题的根子在于:K8s 擅长回答"实例怎么跑起来",回答不了"集群该怎么管"。主库不可达时谁该被提升、从库的日志位点追到哪里算安全、一次升级按什么顺序滚动------这些是数据库领域的判断,K8s 原生对象里没有对应的位置去表达。
金仓给出的答案是 KES-Operator:把这些判断从人的脑子里搬到代码里,让 KES 集群以 K8s 原生对象的形式存在,运维动作从"执行命令"变成"声明目标"。
二、先把一件事讲清楚:StatefulSet 不等于数据库运维
StatefulSet 解决的是有状态实例的编排问题,它给了三样东西:稳定的网络标识(pod-0、pod-1 不会跳号)、稳定的存储绑定(重建后还是挂原来那块盘)、有序的创建与删除。这三样对数据库来说是必要条件,但远远不够。缺的那些,才是数据库运维真正的着力点:
| 运维动作 | StatefulSet 能做到吗 | 说明 |
|---|---|---|
| 保证 Pod 有固定名字和存储 | 能 | 这是它的本职 |
| 按顺序启动 / 停止 | 能 | OrderedReady 策略 |
| 判断主库是否真的不可用 | 不能 | 网络抖动和进程崩溃表现不同,需要结合复制状态判断 |
| 决定把哪个从库提升为新主 | 不能 | 要先看谁的数据最新 |
| 主备切换后把其余从库重新指向新主 | 不能 | 涉及复制链路重建 |
| 升级前的数据校验与回滚准备 | 不能 | 属于数据库专属逻辑 |
| 备份任务的调度与保留策略 | 不能 | 需要额外的控制器 |
一句话概括:StatefulSet 让数据库能跑在 K8s 上,Operator 让数据库能被 K8s 管住。前者是基础设施能力,后者是领域知识的封装,两者是叠加关系而不是替代关系。理解了这一点,KES-Operator 的设计思路就顺了------它并不重新发明调度和存储,而是站在 StatefulSet 之上,专门补上数据库那半边的逻辑。
三、拆开看:KES-Operator 的两个零件
KES-Operator 的骨架可以简洁地概括为 CRD + 控制器,分别承担"描述"和"收敛"两件事。
3.1 CRD:把"一套集群"变成一种 K8s 资源
CRD(Custom Resource Definition)的作用是给 K8s 的 API 新增一种资源类型。在 KES-Operator 的场景里,新增的就是"一个 KES 集群"这个概念。
这件事听起来抽象,实际意义很直接:过去集群的样貌散落在部署脚本、参数文件、值班文档和人脑里;现在它是一份可以被提交、被版本管理、被 diff 的 YAML。
yaml
apiVersion: kdb.kingbase.com/v1
kind: KingbaseES
metadata:
name: kes-prod
namespace: kdb-es
spec:
version: "V9"
image: registry.example.com/kingbase/kes-server:V9R1
imagePullSecrets:
- name: reg-cred
mode: primary-standby # 一主多备形态
replicas: 3 # 1 个主节点 + 2 个备节点
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 8Gi
storage:
size: 100Gi
storageClassName: ceph-rbd
accessModes: ["ReadWriteOnce"]
ha:
failoverTimeout: 30s
targetRPO: 0 # 期望零数据丢失
backup:
enabled: true
schedule: "0 3 * * *" # 每天凌晨 3 点
retentionDays: 14
说明:以上是一份演示用的示意配置,字段名与取值请以你所使用版本随包附带的 CRD 定义为准。最稳妥的验证方式是在提交前先用 kubectl explain 查一遍:
bash
# 确认 CRD 已经注册进集群
kubectl get crd | grep -i kingbase
# 逐层查看字段定义,避免凭记忆写 YAML
kubectl explain kingbasees.spec
kubectl explain kingbasees.spec.storage
kubectl explain kingbasees.spec.ha
这里体现的是声明式运维最核心的一点:你描述的是"集群应该长什么样",而不是"你要执行哪些步骤"。前者是目标,后者是实现路径。目标可以反复提交,路径一旦执行过一次就不该再来一次------这个区别在扩缩容和自愈场景里会被放大得非常明显。
3.2 控制器:声明式的关键是"比对",不是"执行"
有了 CRD,集群的期望形态就存在了。真正让它落地的,是控制器里跑着的那个循环,每一步都很朴素:
- 读取期望状态------从 KingbaseES 对象里拿到用户声明的副本数、存储大小、版本号;
- 采集实际状态------查 K8s 里现存的 Pod、PVC、Service,再向 KES 实例查询主备角色与复制延迟;
- 比对并修正差异------只对不一致的部分动手,缺什么补什么;
- 回写状态------把观测结果写进对象的状态字段,供人查看;
- 回到第一步------等待下一次事件或周期性触发。
关键在第 3 步的措辞:它不记录"我执行过什么",只关心"现在和目标的差距是什么"。这个特性带来两个直接好处。
一是幂等 。同一份 YAML 反复提交,结果一致,不会出现"多跑一遍就多建一套"的事故。二是自愈。因为差距被持续计算,所以无论集群是因为 Pod 被驱逐、节点下线还是被人误删而偏离目标,下一次循环都会自动把它拉回来------不需要有人记得去修。
3.3 与 KES 内核的咬合:高可用不能只靠 K8s
有一个容易被忽略的细节,值得单独拿出来说。K8s 的探针机制判断"这个容器还活着吗",靠的是探活接口。但数据库的高可用要比这精细得多:一个主库的 Pod 还在正常响应探针,不代表它还能正常承载写入;一个从库进程没挂,不代表它的日志位点没有落后。
所以 Operator 不能只盯着 K8s 层面的存活状态,必须向 KES 内核取数------看主节点的 WAL 写入状态、看从节点的同步延迟。当主节点被判定为不可达时,也不能立刻切换,而是要先确认候选新主的数据同步到了什么位置,只有延迟落在允许范围内才发起提升,目标是把 RPO 压到 0。
这一段逻辑无法用 K8s 原生对象表达,也不该硬编码进数据库内核,放在 Operator 里是位置最合适的选择:它既是集群的管理者,也是数据库内部状态的观察者。
四、动手:走一遍完整的声明式生命周期
原理讲完,落到操作上其实只有几步。以下流程基于单集群实验环境整理,生产环境的冗余要求会更高。
4.1 前置条件核对
| 项目 | 要求 | 备注 |
|---|---|---|
| K8s 版本 | v1.24 及以上 | 生产建议直接上高可用控制面 |
| StorageClass | 支持动态供给 | NFS、Ceph RBD 或云厂商块存储均可,注意 RWO/RWX 能力 |
| 权限 | cluster-admin 或等效 RBAC | 需要创建 CRD 和 ClusterRole |
| 镜像 | 已推送到可达的私有仓库 | 预先配好 imagePullSecrets |
| 客户端 | kubectl 版本与集群匹配 | 版本差过大时部分字段会提示 Unknown |
4.2 安装 Operator
bash
# 1. 创建独立命名空间,把 Operator 和业务集群放一起便于排查
kubectl create namespace kdb-es
# 2. 先装 CRD,再装 Operator(顺序不能反,否则控制器找不到资源类型)
kubectl apply -f config/crd/bases/
kubectl apply -f deploy/operator.yaml -n kdb-es
# 3. 盯住 Operator 自身是否就绪
kubectl -n kdb-es get pods -l app=kes-operator -w
Operator 自身也是一个跑在 K8s 里的普通工作负载,把它当成一个"24 小时值班的运维机器人"来看待会更容易理解------它挂了,集群就没人负责调和了。所以这一步之后建议顺手加上 PodDisruptionBudget 和资源限额。
4.3 建集群:一次提交,剩下交给循环
bash
# 提交前面那份 kes-prod.yaml
kubectl apply -f kes-prod.yaml
# 看集群对象本身的状态字段
kubectl -n kdb-es get kingbasees kes-prod
# 观察底层 Pod 逐个就绪
kubectl -n kdb-es get pods -w
# 顺便确认 Operator 到底替我们建了哪些东西
kubectl -n kdb-es get statefulset,svc,pvc
最后一条命令的输出往往最有说服力:一份十几行的 YAML,换来了 StatefulSet、Headless Service、若干 PVC 和 ConfigMap 一整套资源。这些如果手工编排,是一轮不短的复制粘贴加排错。
4.4 扩容:改一个字段的含义
传统流程下扩容需要:申请资源、改配置、重启实例、验证状态、更新台账。声明式流程下,动作只有一个------把期望状态改掉。
bash
# 方式一:一行 patch 直接改副本数
kubectl -n kdb-es patch kingbasees kes-prod \
--type=merge -p '{"spec":{"replicas":5}}'
# 方式二:kubectl edit,改完保存退出即可
# kubectl -n kdb-es edit kingbasees kes-prod
# 观察 Operator 自动补齐新节点并加入复制链路
kubectl -n kdb-es get pods -w
值得注意的是,扩容过程中不需要人工判断"新节点该从哪里拉数据、IP 是什么、复制怎么建"。这些是主备架构的通用问题,Operator 已经内置了处理逻辑。人只需要为容量决策负责,不再为执行路径负责,这是声明式运维最有价值的一点。
4.5 自愈:删掉一个 Pod 看看会发生什么
验证自愈能力最直接的办法就是搞点破坏(仅限实验环境):
bash
# 随机删掉一个从节点
kubectl -n kdb-es delete pod kes-prod-2
# 观察重建过程
kubectl -n kdb-es get pods -w
# 查看集群对象记录的状态条件,确认角色与健康度
kubectl -n kdb-es get kingbasees kes-prod \
-o jsonpath='{.status.conditions}' | head -c 800
你会看到被删掉的 Pod 被重新拉起来、PVC 重新挂回原来的卷、节点重新以从库身份加入,整个过程没有人工介入。
这里有个容易混淆的地方需要说明:Pod 被重建这件事,StatefulSet 自己也能做到。Operator 在自愈上真正多做的事情,是判断这个节点回来之后该扮演什么角色、需不需要重建复制关系、主备拓扑有没有因此发生变化。前者是"把实例拉起来",后者是"让集群回到健康状态",差别就在这。
4.6 备份与监控:收进同一套体系
备份过去通常是独立于 K8s 之外的一套流程:单独的备份脚本、单独的定时任务、单独的存储路径。KES-Operator 把这部分也做成了可声明的对象。
yaml
apiVersion: kdb.kingbase.com/v1
kind: KESBackup
metadata:
name: kes-prod-20260918
namespace: kdb-es
spec:
clusterRef: kes-prod # 指向要备份的集群
type: physical # 物理备份
retentionDays: 14
bash
kubectl -n kdb-es apply -f kes-backup.yaml
kubectl -n kdb-es get kesbackup
kubectl -n kdb-es describe kesbackup kes-prod-20260918
监控侧则通过纳管 KMonitor 组件,把 KES 集群的运行指标纳入 K8s 侧的观测体系。这意味着备份是否成功、集群是否健康,都可以在同一条 kubectl get 的视野里看到,而不用再登录到某台机器上去翻日志目录。
五、体验小结:顺手的地方,以及要留心的地方
用下来的整体感受是:KES-Operator 解决的是一致性问题,而不是某个单点痛点------集群怎么建、状态怎么维持、容量怎么调整、备份怎么做,第一次有了同一个入口。
比较顺手的三点:
- 统一入口。 所有操作收敛到 kubectl 和 YAML,运维动作可审计、可回放,排查问题时不用在不同体系之间来回跳。
- 幂等带来的安全感。 配置即真相,脚本越跑越乱的问题被从根上避免了,同一份 YAML 交给任何人执行结果都一样。
- 天然的 GitOps 适配。 YAML 能进版本库,就意味着集群变更可以走代码评审流程,变更历史与业务代码放在一起,追溯成本大幅降低。
同时也有几处需要留个心眼:
- 存储是真正的天花板。 Operator 能把 Pod 调起来,但调不出 IOPS。扩缩容的耗时和稳定性,很大程度上取决于底层 StorageClass 的能力,落地前建议先单独压一压存储。
- CRD 版本演进要提前做预案。 CRD 字段一旦发生不兼容变更,存量对象在升级时会很棘手。建议在集群规模还很小的阶段就把升级路径验证一遍。
- 有状态升级依然是高风险动作。 滚动更新对无状态服务是常规操作,对数据库则意味着数据风险。上线前务必在预发环境完整演练一次版本滚动。
- Operator 的权限收敛要跟上。 它需要创建 StatefulSet、PVC 等资源,RBAC 授权范围偏大,生产环境建议按命名空间隔离而不是直接给集群级权限。
- 备份要和存储策略对齐。 物理备份解决了"有备份",但还需要配套的恢复演练和快照策略,才谈得上"能恢复"。
六、写在最后
围绕数据库上 K8s,一直有种担心是"DBA 是不是要没事干了"。用下来的判断恰恰相反:活没有变少,只是变了性质。
过去 DBA 的价值大量体现在"执行得对、执行得快"------记得住参数、敲得准命令、半夜能爬起来。这一类能力现在被编码进了 Operator。而新出现的位置是:定义集群应该长什么样的人,以及判断这套定义是否合理的人。副本数给几个、故障切换的容忍窗口设多长、备份保留多少天------这些决策没有标准答案,依赖对业务的理解,也依赖对数据库的认知,代码替代不了。
从这个角度看,KES-Operator 这类工具带来的变化,与其说是岗位的收缩,不如说是职责的上移。它把确定性的事情自动化掉了,把原本被琐碎操作占满的注意力,还给了真正需要判断的地方。这大概也是声明式运维最本质的意思:人负责表达意图,系统负责抵达意图。