双集群单写者切换:数据库强制隔离与回切
验证:未记录
- 适用版本
- 未记录
- 验证范围
- 尚未登记验证记录。
本篇落地 MultiCluster 中的 fencing(阻断旧写者):两个集群共享同一可达的 PostgreSQL 权威数据库,由数据库触发器强制单写者。这解决应用集群切换,不把单数据库当成跨区域数据库容灾。 数据库本身的跨区域切换另见 Aurora Global 演练。
1. 部署两个站点#
完成参考工程迁移,已有 orders_api_a/b、orders_worker_a/b 四个独立登录角色。A 的 Chart 使用 a 角色 DSN(数据库连接字符串)、writerEpoch=1;B 使用 b 角色,初始副本仍为 3,但 readiness 为 503、数据库拒绝写入。两个集群密钥不可互读,迁移 owner 只在受控运维机。
kubectl --context="$CTX_A" -n orders get deployment orders-api orders-worker
kubectl --context="$CTX_B" -n orders get deployment orders-api orders-worker
# 从受控运维机用四个角色的独立连接验证;不要打印 DSN。触发器从数据库 session_user 查站点;API 不能通过改请求里的 site 冒充 B。每个写事务对控制行持共享锁,切换更新等待旧事务完成,再原子递增 epoch(代次)。旧实例即使保有长连接也不能继续写;数据库失联时两边都停止写,选择一致性优先。
2. A → B#
- 暂停两边 GitOps 自动写入,停止合成请求。记录入口权重、四个 Deployment 镜像、控制表状态。
- A 保留 worker 直到 outbox 清空;使用
dr/reconcile.sql保存数量、金额;设置 READONLY_DSN_FILE 为只读连接文件,执行python3 dr/reconcile.py > evidence-before.json获取流式 SHA-256 数据摘要,inconsistent 必须为 0。然后将 A API/worker 缩到 0;这一步减少错误窗口,安全边界仍由触发器实现。 - 从保险库读取 owner 连接到权限 600 文件,运行比较并交换脚本:
export OWNER_DSN_FILE=/secure/owner-dsn
python3 dr/switch_writer.py --from-site a --to-site b --expected-epoch 1 --change-id CHG-REPLACE > evidence-switch.json结果必须为 b、epoch=2。超时、预期不符或连接失败均停止;禁止用忽略异常重试掩盖并发切换。
- 更新 B 的受审查 values 为 writerEpoch=“2”,部署相同已批准摘要,等待 API/worker Ready。先在 B 私有入口执行
tests/acceptance.py,账本/投影验收通过才切业务入口到 B。 - 临时启动一个 A 旧 epoch 测试实例,不切业务流量:写入返回 503,数据库报
writer fenced;然后恢复 A 为 0。若旧实例能写,立刻停止,不切入口。
3. 分区与回切#
在预生产阻断 A 到数据库的连接:A 不得产生本地成功响应或缓冲后向旧数据库补写;恢复网络后,a 身份依旧被拒绝。不要通过复制数据库创建第二套独立控制表来绕过权威状态。
B 产生新订单后回切必须先停止新流量、排空、对账,再执行:
python3 dr/switch_writer.py --from-site b --to-site a --expected-epoch 2 --change-id CHG-RETURN > evidence-return.json
# A 更新 writerEpoch="3",先业务验收,再切入口;B 的旧代次必须被拒绝。epoch 永不倒退;不能把控制表恢复为 1。入口 DNS(域名系统)有缓存与长连接,旧站点仍可能收到请求,但必须无法写入。停写与切换时间构成实测 RTO(恢复时间目标);同一数据库内已提交数据应完整,仍需金额/行数对账。
4. 升级与结束#
数据库触发器/角色映射属于安全边界,变更先在双站点重放测试。紧急恢复 owner 只授予受控运维身份;API/worker 无修改控制表、DDL(数据定义语言)或禁用触发器权限。演练结束恢复选定主站点,撤销临时 owner 文件与测试入口,保存脱敏证据。