跳到主要内容

主备角色漂移与副本一致性检查

当管理面记录的期望主备关系,与两个下游集群里的真实工作负载副本分布不一致时,operator 会写入 RoleDrift condition。对于无法安全解释的漂移,实例会进入 Failedstatus.reason=RoleDriftDetected

典型报错:

expectedPrimary=suse-2 role=Standby workloads=1 nonZeroWorkloads=0 zeroWorkloads=1 desiredReplicas=0;
expectedSecondary=local role=Standby workloads=1 nonZeroWorkloads=0 zeroWorkloads=1 desiredReplicas=0;
both clusters are scaled to zero

这表示管理面认为 suse-2 是当前主集群、local 是当前备集群,但 operator 在两个集群中都只采样到 replicas=0 的工作负载。两边都没有活跃业务副本,因此实例会被判定为 BothStandby

管理面期望从哪里来

RoleDrift 不使用 DisasterConfig.spec.sourceCluster/targetCluster 直接推导主备,因为 Failover 和 Reprotect 会改变当前保护方向。

当前期望关系固定来自实例状态:

expectedPrimary = DisasterInstance.status.primaryCluster
expectedSecondary = DisasterInstance.status.secondaryCluster

因此,文案里的 expectedPrimary=suse-2 表示 实例 status 当前认为 suse-2 应该承载业务副本;并不一定等于基础配置中的 source cluster。

下游真实角色怎么判断

operator 会分别连接 expectedPrimaryexpectedSecondary 指向的下游集群,在实例保护范围内采样:

  • Deployment
  • StatefulSet

采样范围受这些字段限制:

  • DisasterInstance.spec.namespaces
  • DisasterInstance.spec.labelSelector

每个集群会计算:

字段含义
workloads被采样到的 Deployment/StatefulSet 数量。
nonZeroWorkloadsspec.replicas > 0 的工作负载数量。
zeroWorkloadsspec.replicas = 0 的工作负载数量。
desiredReplicas所有采样工作负载的 spec.replicas 总和。

真实角色按 spec.replicas 判断:

真实角色判定
Active至少一个采样工作负载的期望副本数大于 0。
Standby有采样工作负载,但所有期望副本数都是 0。
Unknown没有采样到工作负载,或下游集群不可达,或 list 失败。

如果 spec.replicas 字段为空,按 Kubernetes 默认值 1 处理。

判定矩阵

expectedPrimary 真实角色expectedSecondary 真实角色condition实例状态reason说明
ActiveStandbyRoleDrift=False保持当前稳态ExpectedRoleMatched真实副本分布与期望主备一致。
StandbyActiveRoleDrift=TrueFailedRoleReversed真实业务面看起来在期望备集群上,和管理面期望相反。
StandbyStandbyRoleDrift=TrueFailedBothStandby两边都缩 0,没有活跃业务面。
ActiveActiveRoleDrift=False保持当前稳态BothActiveObserved双活只作为观测信号,不直接判错。跳过源端缩容的 Failover 可能产生这种形态。
Unknown任意RoleDrift=Unknown保持当前稳态CheckFailed / NoWorkloadObserved无法可靠判断,不因一次观测失败直接置 Failed。
任意UnknownRoleDrift=Unknown保持当前稳态CheckFailed / NoWorkloadObserved同上。

为什么 BothStandby 要报错

Pilot Light 模式下,正常保护态应满足:

  • 当前主集群:业务工作负载有非 0 副本。
  • 当前备集群:ResourceSync 恢复资源骨架,工作负载保持 replicas=0

如果两个集群都变成 Standby,系统无法确认业务是否仍有可服务的副本。继续执行 Failover、Reprotect、手动同步等操作可能扩大影响,因此 operator 会:

  • 写入 status.conditions[type=RoleDrift]
  • 设置 status.fsmState=Failed
  • 设置 status.reason=RoleDriftDetected
  • 清空 status.availableOperations,阻断会改变运行期语义的操作。
  • 记录 Warning 事件 RoleDriftDetected

server 不重新计算这个结论,只把 operator 写入的 conditionsconditionSummary.roleDrift 回显给前端。

相关代码逻辑

operator 逻辑:

代码位置作用
internal/controller/disasterinstance/role_drift.go采样下游副本、判定 RoleDrift、写 condition。
evaluateRoleDrift读取 status.primaryCluster/status.secondaryCluster,分别采样两个集群。
sampleClusterReplicaRole在实例命名空间和标签范围内 list Deployment/StatefulSet,并按 spec.replicas 归类。
guardByRoleDrift在不可安全解释的漂移时把实例置为 Failed
handleProtected / handleActive在稳态下调用 guardByRoleDrift
handleFailedreason=RoleDriftDetected 时继续复检,真实关系恢复后自动回到 ProtectedActive

server 回显逻辑:

代码位置作用
internal/apis/disaster_instance/v1/types.gostatus.conditions 转成接口字段 conditions,并提取 conditionSummary.roleDrift

排查步骤

1. 查看实例状态

kubectl -n disaster-system get disasterinstance <instance-name> -o yaml

重点看:

status:
fsmState: Failed
reason: RoleDriftDetected
message: ...
primaryCluster: suse-2
secondaryCluster: local
conditions:
- type: RoleDrift
status: "True"
reason: BothStandby
message: ...

2. 确认期望主备

kubectl -n disaster-system get disasterinstance <instance-name> \
-o jsonpath='{.status.primaryCluster}{"\n"}{.status.secondaryCluster}{"\n"}'

第一行是当前期望主集群,第二行是当前期望备集群。

3. 查看两个下游集群的副本

在期望主集群和期望备集群分别执行:

kubectl --context <cluster-context> -n <protected-namespace> \
get deploy,sts \
-o custom-columns=KIND:.kind,NAME:.metadata.name,REPLICAS:.spec.replicas,READY:.status.readyReplicas

如果实例配置了标签筛选器,还要带上同样的 selector:

kubectl --context <cluster-context> -n <protected-namespace> \
get deploy,sts -l '<label-selector>'

4. 对照报错摘要

示例:

expectedPrimary=suse-2 role=Standby workloads=1 nonZeroWorkloads=0 zeroWorkloads=1 desiredReplicas=0

含义是:

  • suse-2 是管理面期望主集群。
  • operator 在 suse-2 采样到 1 个工作负载。
  • 该工作负载期望副本为 0。
  • suse-2 被判断为 Standby,不符合“期望主集群应为 Active”的稳态要求。

修复原则

不要直接手改 DisasterInstance.status.primaryCluster/secondaryCluster 或 condition。正确做法是先确认真实业务面,再让下游副本分布与当前期望主备重新一致。

常见处理:

场景处理方向
BothStandby,期望主集群应继续承载业务在期望主集群恢复 Deployment/StatefulSet 副本到原始值,期望备集群保持 0。
RoleReversed,实际业务在期望备集群先确认是否刚经历失败的 Failover/Reprotect,避免流量误切;按运维决策把副本恢复到实例当前期望方向,或执行受控的人工恢复流程。
BothActiveObserved不会使实例 Failed,但需要确认这是否来自显式跳过源端缩容;如果不是预期双活,应尽快收敛流量和副本。
Unknown先修复集群连接、权限、命名空间或 selector,避免误判。

当真实副本恢复为:

expectedPrimary = Active
expectedSecondary = Standby

operator 会在后续调谐中自动把 RoleDrift=False,并在实例因 RoleDriftDetected 进入 Failed 的情况下自动恢复到稳态:

  • 当前主备方向等于基础配置方向时,恢复为 Protected
  • 当前主备方向是 Failover 后方向时,恢复为 Active

预防建议

  • Failover、Reprotect、Undo 执行期间,不要在下游集群手动缩放同一批 Deployment/StatefulSet。
  • 如果必须人工缩放,先记录当前实例的 primaryCluster/secondaryCluster,并确保最终只有当前主集群保留非 0 副本。
  • 演练使用独立演练命名空间,不要污染生产 standby 命名空间。
  • 对生产切换,保留外部流量控制记录,避免“副本已恢复但流量仍指向旧集群”的二次漂移。
  • 出现 RoleDriftDetected 后先保留事件和 operation 记录,再处理副本,不要清理证据。