跳到主要内容

容灾切换失败自动补偿机制

Failover 是一个多步骤长流程。某个步骤失败或超时后,operator 会先判断失败发生在哪个阶段,再决定是否自动补偿。自动补偿的目标不是把失败的 DisasterOperation 改成成功,而是尽量把实例恢复到可继续保护的状态,避免实例长期停留在 FailingOver,也避免源、目标两端状态不明确。

自动补偿只针对 operationType=failoverreprotectundocancel、同步和演练失败时会记录失败原因,但不会套用 failover 的自动补偿路径。

触发条件

Failover 执行时,每个步骤都会写入 DisasterOperation.status.steps。以下两类情况会进入失败处理:

  • 步骤执行返回错误,例如集群不可达、恢复策略校验失败、缩容或扩容失败。
  • 步骤超过 timeoutMinutes。如果操作没有显式设置超时,会尝试继承实例的 spec.operationTimeoutMinutes
备注

当前平台界面没有提供“容灾切换超时时间”的可编辑设置入口。timeoutMinutes 是后端动作参数/CRD 字段,会由前端请求或直接 API 调用写入 DisasterOperation.spec.timeoutMinutes;如果操作没有设置该字段,operator 会继承实例的 spec.operationTimeoutMinutes,实例默认值为 60 分钟。

因此,控制台中看到“步骤超时”时,表示 operator 按后台参数执行了超时判定,不表示用户曾在界面上手动配置过该时间。

失败后,operator 会根据失败步骤调用自动补偿分流逻辑:

Failover step failed or timed out
-> resolve autoCancel mode
-> write autoCancel status
-> recover instance state when possible

补偿模式

当前实现有三种模式:

模式适用步骤行为实例结果
DirectRollbackPreCheck还没有进入业务变更阶段,直接将实例回置为 ProtectedProtected
CancelPathPauseSchedulesFinalSyncScaleDownSourceScaleUpTargetCheckReplicas进入自动补偿步骤:缩容目标、拉起源端、恢复同步调度。补偿成功后为 Protected
NoAutoCancelSwitchRoles 或未知阶段不自动补偿,标记需要人工介入。Failed

注意:即使自动补偿成功,本次 Failover 操作仍然是 Failed,因为切换本身没有完成。判断是否已经恢复,应同时看 status.autoCancelStatus 和实例状态。

DirectRollback

DirectRollback 用于 PreCheck 失败。此时 operator 还没有暂停同步、没有触发最终同步、没有缩容源端,也没有拉起目标端,因此不需要执行额外的反向动作。

operator 会做这些事:

  • 将失败步骤标记为 Failed
  • DisasterOperation.status.state 标记为 Failed
  • 写入 autoCancelTriggered=true
  • 写入 autoCancelStatus=Succeeded
  • 写入 autoCancelMode=DirectRollback
  • 将实例状态恢复为 Protected
  • 清理实例上的错误状态。

典型场景:

  • 目标集群不可达。
  • RestorePolicy dry-run 失败。
  • modifier 规则提交期校验失败。
  • 源集群不可达且没有开启 force=true

CancelPath

CancelPath 用于已经进入切换链路的失败。即使失败发生在 FinalSync,周期同步也可能已经被暂停;如果失败发生在 ScaleDownSourceScaleUpTargetCheckReplicas,源端或目标端副本状态可能已经发生变化。因此 operator 会执行一组显式补偿步骤。

自动补偿步骤写入:

DisasterOperation.status.autoCancelSteps

步骤顺序固定为:

ScaleDownTarget
-> ScaleUpSource
-> ResumeSchedules

1. ScaleDownTarget

缩容目标集群中受保护命名空间内的 Deployment 和 StatefulSet。

这一步用于确保目标端不会继续被拉起,降低两端同时运行的风险。它不会删除目标端由 ResourceSync 恢复出的资源骨架,也不会清理对象存储中的备份。

2. ScaleUpSource

拉起源集群中受保护命名空间内的 Deployment 和 StatefulSet。

operator 会优先读取 ResourceSync 记录的副本数 ConfigMap:

replicas-<resourceSyncName>

如果没有记录,则尝试读取工作负载上的:

testudo.softcdata.com/original-replicas

CancelPath 的 ScaleUpSource 默认等待工作负载 Ready。若源端集群不可达或工作负载无法 Ready,自动补偿会失败并要求人工介入。

3. ResumeSchedules

恢复实例关联的 DataSync 和 ResourceSync 调度:

DataSync.spec.paused = false
ResourceSync.spec.paused = false

补偿成功后,实例回到 Protected,主备角色保持原方向。

NoAutoCancel

SwitchRoles 失败或未知阶段失败时,operator 不会自动补偿。

原因是 SwitchRoles 已经处在角色切换边界,系统无法仅凭单个步骤失败安全判断外部流量、源端副本、目标端副本和实例角色是否完全一致。此时自动做反向动作可能扩大影响。

此类失败会写入:

autoCancelTriggered=false
autoCancelStatus=NotTriggered
autoCancelMode=NoAutoCancel
manualInterventionRequired=true

实例会进入 Failed,需要人工确认后再决定修复、重置、重新切换或回退。

状态字段

原始 CR 中可以查看:

status:
state: Failed
currentStep: FinalSync
message: 故障切换在步骤 FinalSync 失败后已自动补偿,实例已恢复为 Protected
autoCancelTriggered: true
autoCancelStatus: Succeeded
autoCancelMode: CancelPath
autoCancelReason: 步骤超时...
autoCancelTriggerStep: FinalSync
autoCancelCurrentStep: ""
autoCancelSteps:
- name: ScaleDownTarget
state: Completed
- name: ScaleUpSource
state: Completed
- name: ResumeSchedules
state: Completed
autoCancelTriggeredAt: "2026-05-15T..."
autoCancelCompletionTime: "2026-05-15T..."
manualInterventionRequired: false

server 会在实例列表、实例详情、操作详情和历史记录里汇总为:

{
"autoCancel": {
"triggered": true,
"status": "Succeeded",
"reason": "步骤超时...",
"triggerStep": "FinalSync",
"manualInterventionRequired": false,
"triggeredAt": "2026-05-15T15:01:00+08:00",
"completionTime": "2026-05-15T15:02:00+08:00"
}
}

判断结果时按下面理解:

操作状态自动补偿状态实例状态含义
FailedSucceededProtected切换失败,但系统已自动恢复到原保护方向。
RunningRunning通常仍是 FailingOver正在执行自动补偿步骤。
FailedFailedFailed切换失败,自动补偿也失败,需要人工介入。
FailedNotTriggeredFailed当前失败阶段不支持自动补偿,需要人工介入。

排查方式

查看最新 Failover 操作:

kubectl -n disaster-system get disasteroperation \
-l testudo.softcdata.com/instance=<instance-name>

查看自动补偿字段:

kubectl -n disaster-system get disasteroperation <operation-name> -o yaml

重点检查:

  • status.currentStep
  • status.steps
  • status.message
  • status.autoCancelTriggered
  • status.autoCancelStatus
  • status.autoCancelMode
  • status.autoCancelSteps
  • status.manualInterventionRequired

同时检查实例状态:

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

如果 autoCancelStatus=Succeeded,实例应恢复为 Protected,可继续下一次切换或恢复周期同步。如果 manualInterventionRequired=true,不要直接重复触发 Failover,先确认以下内容:

  • 源集群 Deployment/StatefulSet 是否已经拉起。
  • 目标集群 Deployment/StatefulSet 是否仍被缩到 0
  • DataSync 和 ResourceSync 是否仍处于 paused=true
  • 外部 DNS、Ingress、网关或负载均衡是否已经被人工切走。
  • 对象存储、Velero Backup/Restore、PodVolumeRestore 是否有失败事件。

边界和注意事项

  • 自动补偿不会替代外部流量切换或回切。DNS、GSLB、网关和业务流量仍需按 runbook 处理。
  • 自动补偿不会删除目标端所有已恢复资源,只会按步骤缩容目标工作负载、拉起源端工作负载并恢复同步调度。
  • 如果外部系统已经把生产流量切到目标端,自动补偿成功后仍必须人工确认流量方向。
  • 如果补偿步骤本身失败,实例会进入 Failed,并写入 manualInterventionRequired=true
  • SwitchRoles 失败不会自动补偿,因为此时角色边界已经开始变化,继续自动处理风险更高。
  • 如果通过 API 或 CRD 发起生产切换,应根据数据量和工作负载规模设置合理的 timeoutMinutes,避免最终同步或就绪检查被过短超时打断;从当前控制台发起切换时,界面没有该字段的手动配置入口。

和手动 Cancel 的关系

自动补偿内部复用了 cancel 路径的核心步骤,但它不是用户手动创建的 operationType=cancel

  • 自动补偿:由失败的 failover 操作内部触发,状态写在同一个 DisasterOperation.status.autoCancel* 字段里。
  • 手动 Cancel:用户主动创建 operationType=cancel,用于中止仍在进行中或已失败但可复原的切换流程。

自动补偿成功后,通常不需要再执行手动 Cancel。自动补偿失败或未触发时,才需要根据现场状态决定是否手动 Cancel、Reset 或人工修复。