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.0 | 26.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 高可用,避免把“记录当前状态”和“改变当前状态”混成一次操作。
控制器镜像与 RELATED 默认镜像是两层
三个控制面组件的 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: true03: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 文件约大小 | 业务表数 | 校验结果 |
|---|---|---|---|
| 业务库 A | 533 KiB | 3 | 3 表校验和一致 |
| 业务库 B | 1.34 MiB | 115 | 115 表校验和一致 |
| 业务库 C | 591 KiB | 9 | 9 表校验和一致 |
| 远程桌面库 | 543 KiB | 16 | 15 表一致;1 表的在线时间在源端继续更新 |
| 监控库 | 844 KiB | 28 | 24 表一致;心跳及三张统计表在源端继续变化 |
五套实例都成功恢复并启动,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 接管已有资源:
# ./mariadb-operator-crds 此时仍为已核对的 26.3.0 Charthelm 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 仓库按固定版本获取:
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、后控制器:
# 两个本地 Chart 此时均已更新并核对为 26.10.1helm upgrade mariadb-operator-crds ./mariadb-operator-crds \ -n mariadb-operator
helm upgrade mariadb-operator ./mariadb-operator \ -n mariadb-operator \ -f ./mariadb-operator/values-instance.yaml04: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 子命令上传 RGWAccess 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: Completestatus: "True"reason: JobFailedmessage: Failed如果只读 Complete=True,会把一次失败结束误判成备份成功。写作时实际状态是:
- 逻辑
Backup数量为 0,那五份临时备份 PVC 也已不存在。 - 只有一条试验性的
PhysicalBackup,最近一轮是JobFailed。 - 有一条 PITR 对象,但 status 为空,没有可恢复时间记录。
- 五套 MariaDB 都没有设置
pointInTimeRecoveryRef,归档尚未接入生产实例。 - 备份 OBC 为 Bound,只能证明桶已分配,不能证明备份已上传并可恢复。
所以这轮升级前的恢复验证确实做过,但对应文件已清理;新链路又没有成功接替。截至本文复核时,本轮 Operator 备份方案没有可用的已验证恢复点。 不能用先前报告里的“恢复通过”掩盖这个当前状态。
这也留下了很具体的改进:替换备份方案时,旧恢复点应保留到新方案完成“备份成功、远端文件可读、隔离恢复通过”之后,再按保留策略清理。
这轮改造的终点
控制面配置已经可以由文件重现,Webhook 拆开了节点故障点,CRD 的 Helm 管理位置与 Operator 对齐,26.10.1 的控制器和配套容器已落到五套实例。数据库版本没有顺带跨大版本,十块生产数据卷的身份也保持不变。
备份则停在另一条边界:逻辑恢复演练完成过,长期 RGW 上传尚未打通,PITR 尚未生效。把这两条结果同时记下来,才是这次升级和改造的完整状态。