深色模式
多集群容灾与故障转移
面向需要为 K8s 业务设定 RTO/RPO 并落地切换能力的平台/SRE 工程师。覆盖架构选型、数据复制、流量切换、演练与回滚。
适用版本与前提
- Kubernetes:v1.28+
- 前提:≥2 个集群(跨可用区或跨地域)、有统一发布编排(如 Karmada/Argo CD)
- 本文讨论的是应用与流量层容灾,不替代存储的跨地域复制方案设计
背景与问题
"多集群"本身不等于"高可用"。真正的容灾要回答三个问题:
- 故障多久被发现?(检测)
- 多久恢复服务?(RTO, Recovery Time Objective)
- 丢多少数据?(RPO, Recovery Point Objective)
很多团队建了双集群,但没有演练、没有数据复制、没有切换开关,故障时仍只能人工改 DNS 祈祷——这不是容灾,是赌博。
核心概念
| 术语 | 含义 | 典型目标 |
|---|---|---|
| RTO | 从故障发生到服务恢复的时间 | 分钟级~小时级 |
| RPO | 允许丢失的数据时间窗口 | 0(同步)~分钟/小时(异步) |
| 主动-主动(Active-Active) | 两集群同时承载流量 | RTO 最低,成本最高,需处理数据冲突 |
| 主动-备(Active-Standby) | 备集群常驻但不接流量或接少量 | 成本可控,RTO 取决于预热程度 |
| 冷备 | 备集群无负载,故障时拉起 | 成本最低,RTO 最长(镜像拉取+调度) |
注意
"备集群常驻无流量"仍会产生节点成本。真正的成本优化是让备集群以最小副本运行(而非缩到 0),避免冷启动带来的长 RTO。这是一个明确的成本/RTO 权衡。
架构与原理
关键分层:
- 流量层:DNS(含 GSLB/云厂商解析)、全局负载均衡、或客户端侧切换。
- 编排层:Karmada/Argo CD 保证两边配置一致、副本可迁移。
- 数据层:数据库主从/多主、对象存储跨区复制、消息队列镜像——这一层往往决定 RPO。
生产实践
1. 定义并写死目标
不写死目标的容灾方案无法验证。建议写成 SLO 文档的一部分:
text
服务:订单 API(P0)
RTO:≤ 15 分钟
RPO:≤ 60 秒(异步复制可接受)
切换方式:DNS 权重切换 + 备集群常驻 20% 副本
演练频率:每季度 1 次1
2
3
4
5
2
3
4
5
2. 备集群保持"热"而非"冷"
yaml
# 备集群保留最小副本,避免冷启动(示例:常态 2 副本,切换后扩到 10)
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
spec:
replicas: 2
strategy:
type: RollingUpdate
template:
spec:
containers:
- name: api
image: registry.example.com/order-api:1.8.0
resources:
requests: { cpu: "500m", memory: "512Mi" }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
建议
镜像预热是关键。切换时最大的时间杀手往往是镜像拉取。建议备集群常驻最小副本 + 节点上已缓存业务镜像,或用镜像预热 DaemonSet。
3. 切换前必须检查数据层
bash
# 例:确认备库复制延迟在 RPO 窗口内(命令随数据库类型而异)
# 若延迟 > RPO,切换会造成数据丢失,此时决策应是"等待"而非"切换"1
2
2
生产危险
跨地域强制切换(尤其是数据库主从切换)可能造成数据丢失或脑裂。执行前必须:确认复制延迟、确认主库确实不可用(避免误判网络抖动)、留存操作审计记录。
4. DNS 切换要尊重 TTL
bash
# 切换前先把 TTL 降到 60s 以下(提前做,不是故障时才做)
dig +short order.example.com
dig order.example.com | grep -i ttl1
2
3
2
3
版本相关
实际生效时间取决于递归 DNS 缓存,不等于你设的 TTL。切换后要用多地探测确认生效,而不是只看权威 DNS 已改。
验证
切换后必须验证,而不是"DNS 改了就算完":
bash
# 1) 备集群 Pod 就绪
kubectl --context=cluster-b get pods -n prod -l app=order-api
# 2) 入口流量确实打到备集群(看请求日志/指标)
kubectl --context=cluster-b logs -n prod -l app=order-api --tail=50
# 3) 业务健康度(错误率/时延符合 SLO)
# 用 Prometheus 查询,不要只看 Pod Running
# 4) 写链路验证(关键):确认备集群能写库1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
回滚与清理
故障集群恢复后,不要立刻切回:
- 确认原集群根因已消除(不是反复故障)。
- 先把原集群作为"备",同步数据与配置。
- 用低流量比例灰度切回(如 5% → 50% → 100%)。
- 清理演练期间产生的临时资源与 DNS 记录。
bash
# 灰度切回示意(DNS 权重或流量比例)
# 切勿一次性 100% 切回1
2
2
故障排查
| 现象 | 可能原因 | 定位 |
|---|---|---|
| 切换后备集群 Pod Pending | 资源不足 / 节点未扩容 | kubectl describe pod 看 Events |
| 切换后大量 5xx | 备集群配置缺失(Secret/ConfigMap) | 对比两边 ConfigMap/Secret |
| 写请求失败 | 备库只读 / 复制未提升 | 检查数据库角色 |
| DNS 未生效 | TTL 缓存 / 权威未同步 | 多地区 dig 验证 |
| 镜像拉取超时 | 备集群镜像未预热 | 看 ImagePullBackOff |
安全与合规
- 切换凭证(DNS API、数据库提升权限)属于高权限,需独立最小权限账号 + 审计。
- 容灾演练会产生真实变更,必须走变更流程与审批,禁止"顺手演练"。
- 跨地域数据复制要满足数据合规(如数据不出境/不出域要求)。
性能、容量与成本
| 方案 | 成本 | RTO | 复杂度 |
|---|---|---|---|
| 冷备(缩到 0) | 低 | 长(分钟~十几分钟) | 低 |
| 常驻最小副本 | 中 | 短(秒~分钟) | 中 |
| 双活(各 50%) | 高 | 最短 | 高(数据冲突处理) |
未实测
上表的 RTO 为定性区间,实际取决于镜像大小、节点扩容速度、DNS 生效速度。请在你自己的环境演练后替换为本团队实测值。
常见坑
- 只演练过创建,没演练过切换:配置同步没问题,但切换开关从没按过。
- 忽略了数据层:应用能起来,但库是只读的,写请求全挂。
- TTL 没提前调低:故障时改 TTL 要等旧 TTL 过期,白白浪费几十分钟。
- 备集群配置漂移:ConfigMap/Secret 长期不同步,切换后行为异常。
- 没有回滚方案:切过去容易,切回来没人敢做。
参考资料
- Kubernetes 官方文档:集群架构,访问日期:2026-10-09。
- Kubernetes 官方文档:Pod 中断与 PDB,访问日期:2026-10-09。
- Karmada 官方文档,访问日期:2026-10-09。
- Google SRE Book - 可靠性的层次,访问日期:2026-10-09。