多区域部署最容易制造一种危险的安全感:服务已经在两个 Kubernetes 集群中运行,但其中一个集群消失时,用户流量仍然固执地发往故障区域。副本存在,不等于故障转移已经成立。要让备用集群真正接管请求,必须把工作负载、流量入口、健康判断和数据状态一起纳入设计。
联邦解决的不是“多复制一份”
跨集群联邦的核心目标,是把多个独立集群视为同一个服务的运行位置,同时保留故障隔离边界。每个集群应当能够独立调度 Pod、暴露入口并报告健康状态;集群之间则需要共享或同步服务定义、发布版本和流量策略。
一条完整的请求链路通常包含四层:
- 工作负载层:同一版本的 Deployment、ConfigMap 和 Secret 被部署到多个区域。
- 集群入口层:每个集群拥有独立的负载均衡器或 Gateway 地址。
- 全局流量层:全局负载均衡器、Anycast 网络或支持健康检查的 DNS 服务在区域之间分配请求。
- 状态层:数据库、消息系统和对象存储具有明确的跨区域复制与故障恢复策略。
缺少第三层时,备用集群只是闲置副本;缺少第四层时,流量即使成功切换,也可能遇到旧数据、写入冲突或不可用的下游依赖。
健康检查必须越过 Pod 边界
仅检查某个 Pod 是否返回 200 不足以判断一个区域能否接管生产流量。全局流量系统至少应该检查集群入口,并让探针覆盖服务的关键依赖。与此同时,探针不能把短暂的数据库抖动直接放大成整个区域切换,否则会造成流量在两个区域之间来回摆动。
可以把健康状态拆成两类:
/livez只回答进程是否存活,供 Kubernetes 重启异常容器。/readyz判断实例是否可以接收请求,供 Service 和全局健康检查使用。
区域摘除还应设置连续失败阈值,恢复时设置连续成功阈值。DNS 方案需要特别关注 TTL 和客户端缓存,因为声明的 TTL 并不保证所有递归解析器和应用都按时刷新记录。需要秒级恢复时,具备主动探测和连接级转发能力的全局负载均衡通常更合适。
可以这样实践:同一服务部署到两个集群
下面的示例假设本机已有两个 kubeconfig context:region-a 和 region-b。镜像需要替换为你自己的应用镜像,并确保它在 8080 端口提供 /readyz。
# app.yaml
apiVersion: v1
kind: Namespace
metadata:
name: federated-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: federated-demo
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api
containers:
- name: api
image: ghcr.io/your-org/api:1.0.0
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /readyz
port: http
periodSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: federated-demo
spec:
type: LoadBalancer
selector:
app: api
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api
namespace: federated-demo
spec:
minAvailable: 2
selector:
matchLabels:
app: api
将清单应用到两个集群,并读取各自的入口地址:
set -eu
for context in region-a region-b; do
kubectl --context "$context" apply -f app.yaml
kubectl --context "$context" -n federated-demo rollout status deployment/api
kubectl --context "$context" -n federated-demo get service api
done
接下来把两个 LoadBalancer 地址注册到支持主动健康检查的全局流量系统中,健康检查路径设为 /readyz。具体配置取决于云厂商或 DNS 提供商,因此不能仅靠上述 Kubernetes 清单自动完成。
发布时也不要同时更新所有区域。可以先更新备用区域,验证指标和业务请求,再逐步迁移流量:
kubectl --context region-b -n federated-demo \
set image deployment/api api=ghcr.io/your-org/api:1.1.0
kubectl --context region-b -n federated-demo \
rollout status deployment/api
# 验证 region-b 后,再更新 region-a
kubectl --context region-a -n federated-demo \
set image deployment/api api=ghcr.io/your-org/api:1.1.0
kubectl --context region-a -n federated-demo \
rollout status deployment/api
这种顺序把区域故障转移能力同时用于发布控制:只要旧区域仍能服务,就可以在新版本异常时停止迁移,而不必回滚两个集群。
真正困难的是状态和容量
无状态 API 最容易跨区域接管,但大多数生产服务并不真正无状态。数据库可能采用单主跨区域复制,也可能允许多区域写入;两种模式的故障边界完全不同。
单主模式需要回答:主区域失联后,谁提升副本,如何避免旧主恢复后继续写入?多主模式则必须处理冲突、时钟偏差和一致性要求。消息队列、定时任务和后台消费者也需要防止两个区域重复处理同一任务。
容量同样不能忽略。两个区域各承担 50% 流量时,任一区域都必须留出接管另外 50% 的余量。若正常状态已经接近资源上限,故障转移只会把集群故障变成过载故障。除了 CPU 和内存,还要核对负载均衡配额、数据库连接数、NAT 端口及第三方 API 限流。
上线前用故障演练验证承诺
“零停机”应该由指标证明,而不是由架构图宣布。上线前至少检查以下项目:
- 两个集群能否从同一受控版本来源独立部署。
- 全局入口能否在预期时间内发现区域故障并停止转发。
- 单一区域是否有足够容量承接全部生产流量。
- 会话、缓存、数据库和消息系统在切换后是否保持正确行为。
- 区域恢复时是否逐步引流,避免瞬间压垮刚恢复的服务。
- 故障演练期间是否持续记录错误率、延迟、恢复时间和数据恢复点。
跨集群联邦并不会自动带来零停机。它提供的是多个独立故障域,而全局流量控制、可靠健康检查、状态复制和定期演练,才会把这些故障域连接成真正可用的多区域系统。