容灾切换失败自动补偿机制
Failover 是一个多步骤长流程。某个步骤失败或超时后,operator 会先判断失败发生在哪个阶段,再决定是否自动补偿。自动补偿的目标不是把失败的 DisasterOperation 改成成功,而是尽量把实例恢复到可继续保护的状态,避免实例长期停留在 FailingOver,也避免源、目标两端状态不明确。
自动补偿只针对 operationType=failover。reprotect、undo、cancel、同步和演练失败时会记录失败原因,但不会套用 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
补偿模式
当前实现有三种模式:
| 模式 | 适用步骤 | 行为 | 实例结果 |
|---|---|---|---|
DirectRollback | PreCheck | 还没有进入业务变更阶段,直接将实例回置为 Protected。 | Protected |
CancelPath | PauseSchedules、FinalSync、ScaleDownSource、ScaleUpTarget、CheckReplicas | 进入自动补偿步骤:缩容目标、拉起源端、恢复同步调度。 | 补偿成功后为 Protected |
NoAutoCancel | SwitchRoles 或未知阶段 | 不自动补偿,标记需要人工介入。 | 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,周期同步也可能已经被暂停;如果失败发生在 ScaleDownSource、ScaleUpTarget 或 CheckReplicas,源端或目标端副本状态可能已经发生变化。因此 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"
}
}
判断结果时按下面理解:
| 操作状态 | 自动补偿状态 | 实例状态 | 含义 |
|---|---|---|---|
Failed | Succeeded | Protected | 切换失败,但系统已自动恢复到原保护方向。 |
Running | Running | 通常仍是 FailingOver | 正在执行自动补偿步骤。 |
Failed | Failed | Failed | 切换失败,自动补偿也失败,需要人工介入。 |
Failed | NotTriggered | Failed | 当前失败阶段不支持自动补偿,需要人工介入。 |
排查方式
查看最新 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.currentStepstatus.stepsstatus.messagestatus.autoCancelTriggeredstatus.autoCancelStatusstatus.autoCancelModestatus.autoCancelStepsstatus.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 或人工修复。