4530 字
23 分钟

MariaDB Operator 升到 26.10.1:Webhook、CRD 与备份改造

2026-09-27 凌晨,完成了 MariaDB Operator 的一轮整理和升级:控制面从 26.3.0 升到 26.10.1,Webhook 改成跨节点双副本,独立 CRD release 从 default 迁到 mariadb-operator,五套数据库的 agent 和 init 容器也更新到了同一版本。

MariaDB Server 镜像保持 11.8.5-noble。五套库完成主备切换后恢复 Ready,写作时再次查询,主库可写、备库只读,五个备库的 IO/SQL 线程正常,采样延迟均为 0。

但整轮改造还有一项没有完成:升级前的逻辑备份做过隔离恢复验证,后来已清理;随后尝试的 PhysicalBackup → Rook RGW 卡在上传阶段,PITR 也尚未接入数据库。这篇会把升级结果与备份缺口分别记清楚。

本文根据当晚 Cursor 操作记录、Helm 历史、备份校验报告和 05:00—05:04 的只读复核整理。时间均为 UTC+8,示例域名、实例名和路径已泛化,不包含凭据或备份内容。

为什么除了升级,还要先整理部署方式#

前一篇 MariaDB 复制正常,备库却没有只读保护记录了两个备库的 read_only 漂移。当时只在线恢复了参数,Operator 仍是 26.3.0,重启后的持续纠偏没有闭环。这次继续沿着那条线检查,也发现控制面的部署有几处欠账。

升级前五套 MariaDB 都是两副本,数据面已经显式使用 Harbor 镜像;控制面则有三个组件:operator、webhook、cert-controller,各一副本,最初集中在同一台节点。

项目改造前本次最终结果
Operator 版本26.3.026.10.1
Webhook单副本,无 PDB、无反亲和两副本,hostname 硬反亲和,PDB maxUnavailable: 1
Webhook 证书自带 cert-controller 管理继续保留这条签发路径
Helm 环境覆盖现网有覆盖,本地缺少独立文件落到 values-instance.yaml
默认 MariaDB/exporter 镜像仍是公网引用改为 Harbor 引用,并固定版本
CRD release位于 default迁到 mariadb-operator
agent/init显式固定 26.3.0升到 26.10.1,完成后关闭自动跟随更新
备份没有 Operator 管理的备份资源做过临时逻辑备份与恢复验证;长期物理备份仍待修复

Webhook 当时使用 failurePolicy: Fail。它不可用时,数据库进程可能继续运行,但涉及这些资源的 API 写入和控制器调谐会受阻。因此,数据库 Ready 并不能覆盖 Webhook 单点问题。

这轮还清理了六块经确认不再被使用的旧 Bitnami Galera PVC,以及未部署过的 production-replication 草稿目录。它们与当前五套 MariaDB 是不同的资源,不能按名称相似就混在一起处理。现有十块数据库 PVC 保留下来,后面又做了 UID 和 PV 绑定对比。

先把现网配置写回文件,再逐项加固#

最初本地没有 values-instance.yaml,现网却通过 Helm user values 把 operator、webhook、cert-controller 的镜像仓库改到了 Harbor。只看本地默认 values,无法重现线上部署。

这次先把已有覆盖原样整理出来,再分别修改默认镜像和 Webhook 高可用,避免把“记录当前状态”和“改变当前状态”混成一次操作。

三个控制面组件的 image.repository,决定的是它们自己从哪里拉镜像。

mariadb-operator-env ConfigMap 里的 RELATED_IMAGE_MARIADB、RELATED_IMAGE_EXPORTER 则提供代管资源的默认镜像。五套现有数据库已经在 CR 中显式固定了镜像,所以把这两个默认值改成 Harbor,没有让它们滚动;当晚只重启 controller,使它重新读取环境变量。

03:19:15,这项改动成为 Helm revision 5。它补上的是后续新建资源漏写镜像时的默认行为,并不代表所有已有 CR 的镜像被统一重写。

MaxScale 当时没有部署,这次没有给它添加 Harbor 覆盖。最终 ConfigMap 里的 MaxScale 默认项仍随上游 Chart 走,不能把结果写成“所有 RELATED 镜像都已代理”。

Webhook 两副本,继续保留 cert-controller#

早期方案考虑过改用集群里的 cert-manager,随后明确决定保留自带 cert-controller。最终配置是:

webhook:
ha:
enabled: true
replicas: 2
cert:
certManager:
enabled: false
pdb:
enabled: true
maxUnavailable: 1
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values: [mariadb-operator-webhook]
- key: app.kubernetes.io/instance
operator: In
values: [mariadb-operator]
topologyKey: kubernetes.io/hostname
certController:
enabled: true

03:24:20,Helm revision 6 完成。Webhook 两个副本落在不同节点,PDB 创建成功,cert-controller 仍为 1/1;这一步没有滚动五套数据库。

这里的目标是拆掉 Webhook 的单节点故障点。PDB 保护通过 Eviction API 发起的维护驱逐,不能保证节点故障或 Deployment 滚动期间一定没有中断。Kubernetes 中断与 PDB 说明

operator 和 cert-controller 本次仍各保留一个副本,没有把早期画布中的所有资源限额、指标等建议都算成已经实施。

动 CRD 之前,先证明逻辑备份能灌回去#

03:40 开始,给五套数据库创建一次性 Backup。使用的是 MariaDB 自带的 mariadb-dump,由 Operator 调度执行,导出 --all-databases,gzip 压缩,每套写入独立的 4Gi ceph-block PVC。

这轮是升级窗口的临时恢复点,没有配置定时任务。只看到 Backup 完成还不够,于是每套又创建一个隔离的、单副本且不启用复制的 MariaDB,通过 bootstrapFrom.backupRef 恢复。

恢复方法可以概括为下面这个片段;它属于新建的验证实例,不能补到正在运行的生产库上:

spec:
replicas: 1
bootstrapFrom:
backupRef:
name: example-mariadb-logical

恢复后对业务表比较 CHECKSUM TABLE 和 COUNT(*)。报告保留了差异,而不是把所有实例笼统写成“完全一致”:

验证实例gzip 文件约大小业务表数校验结果
业务库 A533 KiB33 表校验和一致
业务库 B1.34 MiB115115 表校验和一致
业务库 C591 KiB99 表校验和一致
远程桌面库543 KiB1615 表一致;1 表的在线时间在源端继续更新
监控库844 KiB2824 表一致;心跳及三张统计表在源端继续变化

五套实例都成功恢复并启动,171 张业务表没有缺失或多出,166 张的校验和与源端采样一致。余下五张属于仍在写入的业务表:远程桌面记录的行数和身份一致,时间字段更新;监控心跳继续增加,统计表原地更新。

这足以支持“这些 dump 实际灌进了隔离实例,业务表可以读取”的结论。它不是冻结生产写入后的同一时点全量一致性证明,也没有替代应用登录、权限和全部业务流程验收。

期间还出现一个等待脚本的误判:恢复 Job 已完成,Operator 清掉了临时 Restore CR,脚本却继续等那个 CR。最后改为结合恢复 Job 和数据库里的实际内容判断结果。验证完成后,隔离实例及其 PVC 被清理。

CRD 迁移的是 Helm 管理记录#

CRD 本身是集群级资源,没有命名空间。原来放在 default 的,是 mariadb-operator-crds 这个 Helm release 的管理记录,以及 CRD 上对应的 Helm 所属命名空间注解。

更关键的是,这份独立 Chart 把十二个 CRD 放在 templates/crds.yaml 中。它们按普通 Helm 资源管理,不能套用“Helm 不会删 crds/ 下 CRD”的印象。

如果先卸载旧 release,会删除 CRD,继而删除相应的自定义资源;由这些 CR 管理的工作负载也可能被级联清理。PVC 是否保留还受具体策略影响,不能拿数据盘可能仍在作为迁移手段。Kubernetes 删除 CRD 的行为

当晚先保存了这些基线:

  • 十二个 CRD 的 UID、spec 摘要和 Helm 所属信息。
  • 五个 MariaDB CR 的 UID、Ready 状态和当前主库。
  • 十块数据 PVC 的 UID 与绑定的 PV。

迁移时仍使用 26.3.0,先做 server dry-run,再由新命名空间的 release 接管已有资源:

Terminal window
# ./mariadb-operator-crds 此时仍为已核对的 26.3.0 Chart
helm upgrade --install mariadb-operator-crds ./mariadb-operator-crds \
-n mariadb-operator \
--take-ownership --dry-run=server
# 核对渲染与现网差异后,才执行接管
helm upgrade --install mariadb-operator-crds ./mariadb-operator-crds \
-n mariadb-operator \
--take-ownership

本机 Helm 是 3.21.4,支持 --take-ownership。这个参数接管 Helm 所有权检查;它与采用资源替换策略的 --force 是两回事,本次没有使用后者。Helm upgrade 参数说明

03:55:14,目标命名空间生成 CRD release revision 1。核对十二个 CRD 的 UID、spec 摘要及新所有权通过后,才删除 default 中那一条旧 Helm release Secret:sh.helm.release.v1.mariadb-operator-crds.v1。这一步清理的是旧管理记录,没有执行 helm uninstall。

这个精确 Secret 名只对应当时旧 release 只有 revision 1 的现场,不能照搬到有多条历史记录的环境。接管并不会自动消除旧 release 的历史,若保留旧记录又误执行卸载,仍可能按旧 manifest 删除资源。

迁移阶段十二个 CRD 的 spec 没有变化,五个 MariaDB CR、十块数据 PVC 的身份和绑定也保持。随后才单独进入版本升级:原计划曾建议另开窗口,实际记录是在同一晚完成迁移验收后继续,没有把接管与 schema 升级合并成一次操作。

升级到 26.10.1,数据库版本继续固定#

26.10.1 升级指南给出了从早于 26.10.x 版本更新的步骤。本次直接从 26.3.0 → 26.10.1,没有先停在旧画布里的 26.6.0,也没有部署 26.10.0。

这轮版本包含与前面故障有关的改进:备库只读漂移纠正,以及按角色设置 rpl_semi_sync_master_enabled。同时,上游默认 MariaDB 镜像已经变为 12.3.3,所以必须把 Operator 升级与 Server 升级分清。26.10.0 系列变更

现有五套 CR 的数据库镜像继续显式固定为 11.8.5-noble,环境覆盖也保留相同默认值:

config:
mariadbImage:
repository: harbor.example.com/dockerhub/library/mariadb
tag: 11.8.5-noble
mariadbImageName: harbor.example.com/dockerhub/library/mariadb
exporterImage:
repository: harbor.example.com/dockerhub/prom/mysqld-exporter
tag: v0.15.1

新 Chart 从官方 OCI 仓库按固定版本获取:

Terminal window
helm pull oci://ghcr.io/mariadb-operator/charts/mariadb-operator \
--version 26.10.1
helm pull oci://ghcr.io/mariadb-operator/charts/mariadb-operator-crds \
--version 26.10.1

更新本地官方文件时保留 values-instance.yaml 和五套 examples/,重新渲染确认数据库默认值仍是 11.8.5。Operator 新镜像也先通过 Harbor 代理拉取,确认可获得后再继续。

autoUpdateDataPlane 管的是 agent 和 init#

一个 MariaDB Pod 除了数据库容器,还带着 Operator 的 init 和 agent。init 负责生成实例配置,agent 提供远程管理能力;它们使用 Operator 镜像,与 spec.image 指定的数据库镜像属于不同的版本面。上游 data-plane 说明

原先五套 CR 把这两处显式固定为 26.3.0。升级前按指南临时打开:

spec:
updateStrategy:
type: ReplicasFirstPrimaryLast
autoUpdateDataPlane: true

控制器更新后,agent/init 跟随到 26.10.1,引发数据库 Pod 滚动。ReplicasFirstPrimaryLast 按角色先更新副本、再处理主库;这是每套数据库内部的更新顺序,不代表五套实例会被全局逐套串行升级。实际记录是在控制面升级前给五套都打开了开关。更新策略说明

发布顺序为先 CRD、后控制器:

Terminal window
# 两个本地 Chart 此时均已更新并核对为 26.10.1
helm upgrade mariadb-operator-crds ./mariadb-operator-crds \
-n mariadb-operator
helm upgrade mariadb-operator ./mariadb-operator \
-n mariadb-operator \
-f ./mariadb-operator/values-instance.yaml

04:02:09,CRD release 更新为 revision 2;04:02:21,Operator 更新为 revision 7。随后检查三个控制面 Deployment 的 rollout,并等待五套数据库更新完成。

五套都发生了主备切换。原来的主库在四套中是 -1,更新后为 -0;另一套从 -0 变为 -1。实例编号并不固定代表主库,验收始终跟随 CR 的当前主库和数据库实际状态。

约 04:04,五套都恢复 Ready,agent/init 已为 26.10.1。确认复制正常后,把五套 autoUpdateDataPlane 关回 false,并同步更新本地实例文件中的镜像版本,避免下一次升级控制器时未经安排再滚一遍数据面。

这与 mariadbAutoUpgradeEnabled 是不同开关。后者用于数据库跨主版本时运行系统表迁移,本次没有升级 MariaDB Server。26.10.1 关于 Server 升级的说明

验收不只看 Helm deployed#

写作时再次核对控制面、CR、Pod、PVC,并在十个数据库实例内执行只读查询:

SELECT @@global.read_only,
@@global.rpl_semi_sync_master_enabled,
@@global.rpl_semi_sync_slave_enabled;
SHOW ALL SLAVES STATUS;

05:00—05:04 的结果如下:

检查项结果
控制面operator 1/1、webhook 2/2、cert-controller 1/1,镜像均为 26.10.1
Webhook 调度两个 Pod 在不同节点,PDB 显示 2 个健康副本
CRD十二个 UID 与迁移前相同,Helm 所属命名空间均为 mariadb-operator
数据盘十块 PVC 的 UID、PV 绑定与迁移前一致
数据库与配套容器五套 CR Ready;十个数据库 Pod 的 MariaDB、agent 容器均 Ready;agent/init 均为 26.10.1
MariaDB 镜像五套仍是 11.8.5-noble
自动更新五套 autoUpdateDataPlane=false
只读状态五个主库为 0,五个备库为 1
半同步主端开关rpl_semi_sync_master_enabled:主库为 1,备库为 0
复制五个备库 IO/SQL 均为 Yes,延迟为 0,IO/SQL 错误编号为 0

这里“备库关闭半同步主端”指的是 rpl_semi_sync_master_enabled=0,不是关闭备库的半同步接收能力。本次各实例的 rpl_semi_sync_slave_enabled 仍为 1。

新版本也具备根据当前角色调谐 read_only 的代码路径,补上了前一篇指出的能力缺口。26.10.1 只读调谐源码

不过,本次复核没有另行制造只读漂移或做故障注入,也没有测量每个应用在切主期间的请求失败率。可以确认当前角色、参数和复制正常,不能把它延伸成“零停机”或所有故障情形都已经验证。

长期备份改造停在了 S3 上传#

升级与 CRD 迁移验收后,04:07 按当时的决定清理了五份临时逻辑备份:Backup CR、对应 4Gi PVC,以及那批一次性配置文件。现网数据库卷没有删除。

随后才着手长期备份,目标是 PhysicalBackup 调用 MariaDB 原生 mariadb-backup,把副本数据压缩后上传到 Rook RGW,再用 PointInTimeRecovery 归档 binlog,建立可恢复时间线。这属于上游支持的物理备份和 PITR 路径。物理备份文档、PITR 文档

这套 RGW 与数据库卷仍属于同一个 Ceph 集群。把备份从 SSD 块池搬到 HDD 对象池改变了存储介质和访问方式,尚不能当成独立集群或异地恢复副本。

本轮先在一套数据库试做,实际创建的物理备份使用 target: Replica、compression: bzip2、maxRetention: 720h,配置定时和首次立即执行。PITR 对象另建,binlog 压缩为 gzip。其余四套没有继续铺开。

凭据来自 Rook 创建 ObjectBucketClaim 后生成的 Secret,再复制到运行备份 Job 的业务命名空间,由 CR 的 Secret 引用注入。这里不需要人工把 Access Key 或 Secret Key 贴进操作对话。

失败发生在两阶段之间:

副本数据目录
↓ mariadb-backup
本地物理备份生成成功
↓ Operator backup 子命令上传 RGW
Access Denied,Job 失败

历史对照测试记录了以下结果:

路径观察结果
Operator 26.10.1 的 backup 上传RGW 返回 403,访问日志中的用户为 -
同一对凭据、同一桶、同一 Service、boto3 path-style PUT返回 200,RGW 识别到对应用户
改用字面量环境变量注入后再次运行 Operator backup仍返回 Access Denied
virtual-host 寻址试验遇到桶名前缀域名解析问题,与上述 path-style 403 分开处理

因此,问题已收窄到 Operator 这条上传路径的认证行为,不能仅凭 403 断言桶不存在或密钥错误。但“为什么该进程没有完成预期认证”还没有最终定位。

源码显示,控制器从 S3 配置构建客户端时,可以把 Secret 转成静态凭据;Job 中的客户端则会使用环境变量、IAM 等凭据提供链。该版本的 S3 客户端

RGW 把失败请求记录为匿名,是当时重点调查的线索。它本身还不能替代进程内凭据解析结果或请求头取证。把客户端改成 StaticV4 只是当时提出的修复方向,未完成补丁验证;S3 恢复和 PITR 使用相关客户端路径,存在受影响的可能,也没有据此宣称已逐项复现。

这部分最终留下了英文 issue 草稿和复现记录,没有把提出修复方向写成修复成功。

Complete=True 也可能是失败结束#

现网 PhysicalBackup 的 condition 是:

type: Complete
status: "True"
reason: JobFailed
message: Failed

如果只读 Complete=True,会把一次失败结束误判成备份成功。写作时实际状态是:

  • 逻辑 Backup 数量为 0,那五份临时备份 PVC 也已不存在。
  • 只有一条试验性的 PhysicalBackup,最近一轮是 JobFailed。
  • 有一条 PITR 对象,但 status 为空,没有可恢复时间记录。
  • 五套 MariaDB 都没有设置 pointInTimeRecoveryRef,归档尚未接入生产实例。
  • 备份 OBC 为 Bound,只能证明桶已分配,不能证明备份已上传并可恢复。

所以这轮升级前的恢复验证确实做过,但对应文件已清理;新链路又没有成功接替。截至本文复核时,本轮 Operator 备份方案没有可用的已验证恢复点。 不能用先前报告里的“恢复通过”掩盖这个当前状态。

这也留下了很具体的改进:替换备份方案时,旧恢复点应保留到新方案完成“备份成功、远端文件可读、隔离恢复通过”之后,再按保留策略清理。

这轮改造的终点#

控制面配置已经可以由文件重现,Webhook 拆开了节点故障点,CRD 的 Helm 管理位置与 Operator 对齐,26.10.1 的控制器和配套容器已落到五套实例。数据库版本没有顺带跨大版本,十块生产数据卷的身份也保持不变。

备份则停在另一条边界:逻辑恢复演练完成过,长期 RGW 上传尚未打通,PITR 尚未生效。把这两条结果同时记下来,才是这次升级和改造的完整状态。