单个 Kubernetes 集群中的数据库高可用已经有成熟做法,但它通常无法覆盖整片区域不可用、控制平面损坏或集群间网络中断。跨集群数据库要解决的不是“多运行几个 Pod”,而是如何划分故障域、复制数据、隔离旧主库,并在异常情况下恢复服务且不产生双主写入。
两个集群不等于两个故障域
跨集群架构的起点是让依赖真正独立。两个 Kubernetes 集群如果共享同一个区域、网络出口、身份系统或存储控制面,仍可能被一次故障同时击穿。
一套典型部署可以这样划分:
- 主集群位于区域 A,承载数据库主节点和主要读流量。
- 灾备集群位于区域 B,运行持续接收日志或数据流的副本。
- 每个集群使用独立的 Kubernetes 控制平面、节点池和持久卷。
- 客户端通过集群外的 DNS、全局负载均衡器或服务发现系统定位当前写端点。
- 备份写入独立对象存储,并启用版本控制或不可变保留策略。
跨区域同步复制可以降低 RPO,但写请求必须等待远端确认,网络延迟会直接进入事务延迟。异步复制对业务性能更友好,却意味着区域突然失联时可能丢失尚未传输的数据。这个选择需要由业务的 RPO 和延迟预算决定,而不是由 Kubernetes 调度策略决定。
最难的问题是防止两个主库同时写入
网络分区发生后,区域 A 和区域 B 都可能认为对方已经失效。如果两边同时提升为主库,数据会形成无法自动合并的分叉。仅依赖 Pod 探针、节点租约或“连续三次健康检查失败”不足以安全切换。
切换流程至少需要三个控制动作:
- 确认故障范围:区分数据库进程故障、Kubernetes API 故障和区域网络故障。
- 隔离旧主库:撤销访问凭据、关闭网络入口、停止存储挂载,或通过云平台 API 隔离原区域。
- 提升副本并更新入口:检查复制位点,提升灾备副本,再修改写端点。
其中第二步通常称为 fencing。没有可靠 fencing,就不应把自动提升包装成“高可用”。自动化可以缩短 RTO,也会把一次错误判断迅速扩大为数据一致性事故。
可以这样搭建双集群部署骨架
下面是一个可改造的演练脚本。假设已经准备两个 kubeconfig context:db-region-a 和 db-region-b,并且数据库 Operator 提供名为 DatabaseCluster 的 CRD。该 CRD 名称和字段是示意接口,运行前必须替换为所选 Operator 的真实 API;脚本中的 context 检查、命名空间创建和部署顺序可以直接复用。
#!/usr/bin/env bash
set -euo pipefail
PRIMARY_CONTEXT="db-region-a"
DR_CONTEXT="db-region-b"
for context in "$PRIMARY_CONTEXT" "$DR_CONTEXT"; do
kubectl --context "$context" cluster-info >/dev/null
kubectl --context "$context" create namespace database \
--dry-run=client -o yaml | kubectl --context "$context" apply -f -
done
kubectl --context "$PRIMARY_CONTEXT" apply -f primary.yaml
kubectl --context "$DR_CONTEXT" apply -f standby.yaml
kubectl --context "$PRIMARY_CONTEXT" -n database get pods -o wide
kubectl --context "$DR_CONTEXT" -n database get pods -o wide
主区域的示意资源如下。需要把 apiVersion、备份字段和数据库参数替换为实际 Operator 支持的配置。
apiVersion: database.example.io/v1
kind: DatabaseCluster
metadata:
name: orders-primary
namespace: database
spec:
role: primary
instances: 3
storage:
size: 500Gi
storageClassName: regional-ssd
backup:
destination: s3://company-db-backups/orders
retention: 30d
replication:
mode: asynchronous
tlsSecretName: cross-region-replication-tls
灾备区域通过稳定的复制地址连接主区域:
apiVersion: database.example.io/v1
kind: DatabaseCluster
metadata:
name: orders-standby
namespace: database
spec:
role: standby
instances: 3
storage:
size: 500Gi
storageClassName: regional-ssd
replication:
source: orders-primary.db-replication.example.internal:5432
mode: asynchronous
tlsSecretName: cross-region-replication-tls
bootstrap:
fromBackup: s3://company-db-backups/orders
生产环境还要避免把对象存储密钥直接写入 YAML。可以接入云工作负载身份,或由外部 Secret 控制器向两个集群分发独立、最小权限的凭据。
切换手册要比部署清单更具体
灾备能力必须通过演练验证。一次完整演练应记录最后确认的复制位点、切断旧主库的方法、提升耗时、DNS 或负载均衡更新时间,以及应用重新建立连接所需时间。
可以先用以下命令采集两个集群的状态,随后再执行数据库 Operator 提供的正式提升命令:
kubectl --context db-region-a -n database get pods,pvc,events
kubectl --context db-region-b -n database get pods,pvc,events
# 将下面命令替换为实际 Operator 的只读复制状态检查。
kubectl --context db-region-b -n database \
get databasecluster orders-standby -o yaml
不要把示例中的资源删除或修改 role 当作通用切换命令。不同数据库对日志位点、时间线和仲裁的处理不同,提升必须使用数据库或 Operator 明确提供的流程。
上线前检查边界
跨集群方案上线前,至少要回答这些问题:
- 区域失联后,谁有权批准提升,自动化最多可以走到哪一步?
- 如何证明旧主库已经被隔离,而不是暂时无法观测?
- 可接受的 RPO 和 RTO 分别是多少,演练结果是否达到目标?
- 复制链路中断多久后告警,积压量如何监控?
- 备份能否在灾备区域独立恢复,密钥和对象存储是否也跨故障域?
- 应用是否会缓存旧地址,连接池能否在端点变化后主动重连?
- 主区域恢复后,如何重新同步并执行受控回切?
跨集群数据库的价值不在于资源清单看起来对称,而在于故障发生时仍能做出唯一、可审计的主库决策。先明确一致性边界和 fencing 手段,再选择复制模式与自动化程度,通常比追求“一键跨区域高可用”更可靠。