- 合同测试补齐:watchdog/drill 服务仍运行、timer 已 disable 但仍 active、 断链模式文件三类场景,probe 与 refresh 两阶段均断言 - 三个源码变异在旧测试中全部放过、新测试全部拦住 - 切换说明补充超时处置需预先批准、安装前同步并核验源码、 回滚时 Persistent timer 可能立即补跑并告警 - 报错分类细化(复核第 5 条)后置;未推送、未做远端切换 Co-Authored-By: Claude Opus 5.5 <[email protected]>
122 行
13 KiB
Markdown
122 行
13 KiB
Markdown
# 生产到 ARM 备份传输中断设计
|
||
|
||
## 2026-10-05 手动快照切换方案(本地适配已完成,远端切换待批准)
|
||
|
||
用户已选定仅手动快照,本节替代下方历史修复路径。用户 2026-10-05 批准后本地适配已实施并验证(结果见 implement.md);远端切换尚未执行,需另行批准。
|
||
|
||
1. 发布适配:`scripts/refresh-deployment-contract.sh` 的 `verify_timers` 当前强制要求三个备份 timer enabled/active;`deploy-existing.sh`、生产身份引导器和聚合检查仍从 daily service 读取部署身份。已增加显式、受控的手动快照模式(根属 `/etc/hyb-oa-backup-mode` 内容为 `manual_snapshot`),校验备份 timer disabled/inactive 且备份 service 已退出(inactive/failed),`probe` 部署前与 `refresh` 部署后均检查,保留身份、构建、数据目录及非备份运维 timer 的验证。不得删除单位、全局跳过 timer 检查或靠错误吞并放行。
|
||
2. 本地验证:扩展既有发布合同测试,覆盖手动模式三个备份 timer 停用可通过、意外启用失败、非备份 timer 失败仍阻断及旧模式回归。受控入口的模式配置与候选版本一并绑定。用户回传 `f6d1025d` 同模型独立只读复核通过、无阻断项;本轮仅补其测试与说明缺口,原结论不自动延伸到后续新提交。
|
||
3. 远端切换范围:生产 `hyb-oa-automatic-backup-{daily,watchdog,drill-reminder}.timer` 与 ARM `hyb-oa-offhost-deadman.timer`。先核实际单位及既有运行状态;先禁用定时器阻止新任务启动,再有界等待在途 service 退出(上限预先写定),超时停止切换并报告、不强杀。执行批准中须预先选择超时处置:允许回滚则恢复本次停用的 timer 原启用状态并验证自动模式;选择保持停用则不写模式文件,明确记录“切换未完成、部署被阻断”,等待后续处置。没有获批回滚时不擅自重新启用,不将该中间态报告为完成;最终核验含对应 service inactive/failed。ARM dead-man 由用户 sudo 停用并单独核验,生产侧 `refresh` 成功不代替 ARM 核验。源码默认名称须与现场一致,不猜测替代单位。
|
||
4. 保留 unit 文件及其环境、历史归档/partial、状态、凭据和共享告警工具,不调用带删除行为的 installer uninstall。公网健康探针与 `hyb-oa-warch3-ops.timer` 不在备份停用范围,hyinda-20c 未安装本项目备份任务,无迁移或清理动作。
|
||
5. 顺序为先完成兼容适配、再获批部署适配并停用定时器、最后只读核验与记录。执行前保留旧模式/单位启用状态作为回退依据;出现异常停止,恢复旧模式和定时器需在本次获批回滚范围内执行。不得出现脚本仍强制 active 而定时器已关闭的未处理状态。
|
||
|
||
安装前置:`install-deployment-contract-refresh.sh` 从所在代码目录的 `scripts/refresh-deployment-contract.sh` 复制 helper,不会自动取得本地新提交。必须先把最终获批提交中的安装器与 helper 送达生产目标代码目录,校验来源提交和文件摘要,再安装;不能在尚为旧源码的目录直接执行 installer。来源同步也是远端写入,纳入切换批准清单,不把它等同于应用发布或默认重启 OA。
|
||
|
||
回滚副作用:生产三个备份 timer 与 ARM dead-man 的生成配置均为 `Persistent=true`。若停用期间错过日历触发点,`enable --now` 可能立即补跑备份/新鲜度检查/季度提醒或 dead-man,并可能产生告警。现场须核对实际配置;回滚授权应包含该可能的自然补跑和通知,不承诺“重新启用只等下一次计划时间”。不清除 timer 时间戳、改 Persistent 或手工补跑来规避此行为。
|
||
|
||
本方案不购买存储、不创建或删除快照、不删备份、不恢复业务数据。远端具体配置、版本和回滚清单须在实施后形成可审阅结果再申请执行批准;当前选择不等于远端切换已完成。
|
||
|
||
## 设计结论
|
||
|
||
采用“先只读判因、再最小变更、最后两个自然周期验证”的事件处置路径。传输修复和容量处置属于同一个 P1 operations 任务,但每项远端写操作维持独立审批;恢复演练作为下游消费者保持阻塞,不参与本任务的根因修复。
|
||
|
||
## 证据链与判定矩阵
|
||
|
||
```text
|
||
生产 daily timer 触发
|
||
-> 本地成对批次生成
|
||
-> rsync 建立 SSH/receiver 会话
|
||
-> ARM inbox/<batch>.partial 写入
|
||
-> rsync 完成
|
||
-> receiver finalize 原子发布
|
||
-> ARM dead-man + 生产 watchdog 判定新鲜
|
||
```
|
||
|
||
当前证据已经到达 `.partial 写入`,所以初始 SSH 认证、host key 与 receiver 授权不是首要分支。后续只读调查按以下矩阵裁决:
|
||
|
||
| 证据 | 主要指向 | 保留边界 |
|
||
| --- | --- | --- |
|
||
| 两个 partial 停止偏移接近,失败时长也接近 | 确定性大小/读取/会话上限 | 结合 rsync 原始码确认,不直接改超时 |
|
||
| 停止偏移差异明显,时长不收敛 | 网络或间歇超时 | 读取客户端/服务端安全状态,不抓原始 payload |
|
||
| rsync `23` | 部分传输/源或目标文件错误 | 结合私有阶段类别,不输出 stderr |
|
||
| rsync `30` | 超时 | 定位是哪一层超时,禁止无限放宽 |
|
||
| rsync `12` | 协议/数据流错误 | 核对双方版本和会话中断证据 |
|
||
| rsync `11` | 文件 I/O | 联查源读取、目标写入和容量 |
|
||
| SSH/包装层 `255` 且已有 partial | 会话在启动后断开 | 不误判为初始认证失败 |
|
||
| partial 满足完整三文件/manifest/字节合同 | finalize 未完成 | 当前现场为 0,不作为默认假设 |
|
||
|
||
## 只读采集合同
|
||
|
||
- 生产侧先用 manifest/三文件/身份/字节元数据枚举全部本地候选,只对最新一个元数据合格候选完整读取 database/storage 并计算 SHA-256;旧候选明确输出 `older_metadata_batch_hash_status=NOT_RUN`,不得为了计数连续读取数 GiB。最新候选只有通过完整 SHA-256 合同才输出 `complete_local_recovery_point=yes`;其哈希失败时不得现场回退扫描旧候选,而应保守标为“无已证明可用备份”。输出年龄、元数据合格数量和聚合大小,不输出 run id、文件名或路径。该结果先于传输根因分析,决定当前是“生产单点”还是“无已证明可用备份”。ARM 仍对正式归档执行完整哈希合同。
|
||
- 对成功与失败路径分别输出本地完整批次、生成期临时对象和保留策略的聚合结果,明确 `retained_after_failure`、`pruned_after_success` 和连续失败累积趋势;不从单次目录计数猜测脚本语义。
|
||
- partial 只输出每个匿名样本的总字节数、预期总字节数、安全比例和年龄;不输出名称、路径、run id 或 artifact 名称。
|
||
- partial 停止偏移的“接近”必须同时满足相对差异不超过 `50000 ppm`(5%)和绝对差异不超过 `67108864` bytes(64 MiB),等价于以两者较小值作为上限。不得用两者较大值,否则小样本与零字节样本会被绝对阈值错误吞并为 `similar`。
|
||
- 生产私有诊断只输出固定的 `transfer_exit_code`、`failure_category`、开始/结束 epoch 和持续秒数。若现有状态没有持久化真实 rsync 码或开始时刻,记录 `NOT_AVAILABLE`,需要本地设计可观测性修订并另行批准部署,不能现场改脚本。
|
||
- 容量只输出 filesystem 使用率/可用 GiB、完整/不完整/临时批次数量和聚合字节,不列目录。
|
||
- 采集器先探测账号能力;无 sudo 时不得尝试临时扩权。需要 root-owned 状态时先提交固定 allowlist 入口设计并单独审批。
|
||
|
||
若上述静态/历史只读证据仍无法收敛,停止追加远端读取项。下一候选步骤是一次单独审批的受控观测性重传:冻结一个已校验本地批次,只允许一次传输尝试,实时记录固定阶段、rsync/SSH 原始码、开始/结束 epoch 和聚合字节进度,设置有界超时且失败不重试。它是诊断性写操作,不属于第一阶段只读授权。
|
||
|
||
## 容量预算
|
||
|
||
生产修复前冻结以下聚合量:最近成功完整本地批次大小、一次备份生成期间的峰值临时增量、当前保留批次总量和文件系统可用量。最低通过线为:
|
||
|
||
```text
|
||
available_before_schedule >= observed_full_batch_peak * 2
|
||
```
|
||
|
||
其中 `observed_full_batch_peak` 包含 database dump、storage archive、manifest 和生成期临时开销;两倍口径提供一个新批次的实际需求和等量安全余量。若现场无法可靠测得生成期峰值,则使用“最近最大完整批次聚合大小 × 2 + 已知最大临时开销”的更保守口径。任何降低该门槛的决定须重新审阅 PRD;扩容优先于删除唯一恢复证据。
|
||
|
||
## 变更与回滚边界
|
||
|
||
- 根因明确后只设计一个最小变更集,例如有界传输超时、具体 I/O 修复、网络会话修复或容量扩展;不同根因不打包试错上线。
|
||
- 修改 systemd/SSH/rsync 配置前保存脱敏旧值指纹和固定回滚命令;不输出配置正文。
|
||
- 清理自动备份自有对象时必须先以固定模式、文件类型、所有者、时间和引用关系验证身份,逐个命中;不使用宽泛递归清理。
|
||
- 手工补跑仅用于经单独批准的修复后快速验证,不计入两个自然周期;失败时保留新的脱敏 finding,不连续重试。
|
||
|
||
## 告警响应计时
|
||
|
||
从本任务开始使用两个独立 epoch:
|
||
|
||
- `alert_sent_epoch`:helper 返回 `sent=true` 所在调用时刻;
|
||
- `human_ack_epoch`:指定责任人首次明确确认收到并开始处置的时刻。
|
||
|
||
`response_seconds = human_ack_epoch - alert_sent_epoch`。缺任一锚点即 `NOT_MEASURED`,不得用文件 mtime、对话时间或事后回忆替代。该指标只评价响应闭环,不改变 dead-man 的检测正确性。
|
||
|
||
## 验收状态机
|
||
|
||
```text
|
||
PLANNING
|
||
-> READ_ONLY_DIAGNOSIS_APPROVED
|
||
-> ROOT_CAUSE_EVIDENCED
|
||
-> CHANGE_PLAN_APPROVED
|
||
-> FIX_APPLIED
|
||
-> FIRST_ATOMIC_BATCH_PASS
|
||
-> NATURAL_CYCLE_1_PASS
|
||
-> NATURAL_CYCLE_2_PASS
|
||
-> INCIDENT_GO
|
||
```
|
||
|
||
任何一步失败都停在当前状态并保留证据。`FIRST_ATOMIC_BATCH_PASS` 形成一个满足 24 小时门禁的新鲜原子批次后,即可解除对恢复演练的事件阻塞;恢复演练仍需自己的身份探针和实际恢复审批。P1 事件本身只有到 `NATURAL_CYCLE_2_PASS` 才能结案,两条线不互相等待。
|
||
|
||
## 过渡恢复点决策
|
||
|
||
- `complete_local_recovery_point=yes`:若下个自然周期在可接受窗口内且容量预算允许,默认 `wait_natural`;仍可基于风险窗口单独申请手工补跑,但不是自动动作。
|
||
- `complete_local_recovery_point=no`:事件升级为当前无已证明可用备份,默认建议 `request_manual_run`,但必须先保证根因不会使补跑无界失败、容量满足停止条件,并单独审批。
|
||
- 任何手工补跑都只建立过渡恢复点和验证修复,不替代两个自然周期。
|
||
|
||
## 安全与脱敏
|
||
|
||
- 腾讯云生产和 ARM 地址、账号、绝对现场路径、完整 run id/digest、密钥、known_hosts、webhook、原始日志和业务内容不进入仓库或聊天。
|
||
- 远端临时探针若获批,固定目标、SHA-256、语法、sudoers 和自清理均需机械校验,并由外层独立报告入口/暂存残留。
|
||
- timer `active`、partial 存在、单次备份成功或告警 `sent=true` 都是局部证据,不能扩写为链路整体正常。
|
||
|
||
## 2026-08-28 只读调查裁决
|
||
|
||
- 生产本地存在一个通过完整哈希合同的最新可用批次,风险分类为 `production_single_point`,不是“当前无已证明可用备份”;元数据合格批次 7 个,失败传输后保留本地批次、异机成功后才 prune,过渡动作保持 `wait_natural`。
|
||
- ARM 原始调查输出曾将两个 partial 判为 `similar`。该输出保留作为原始证据;现场聚合字节为 `2682168` 与 `0`,按修正后的“双阈值同时满足”合同人工重算为 `different`。一个老 partial 已满足完整合同但未 finalize,另一个较新 partial 为零字节,两者不支持“固定相同偏移的确定性传输上限”,且可能来自不同失败类型。
|
||
- 现有状态未持久化本次失败的 rsync 原始退出码和传输专属时钟,均保持 `NOT_AVAILABLE`;只读证据不足以证明单一根因。若后续自然周期再次失败,按既定边界转为单独审批的一次受控观测性重传,不扩大只读读取范围来补造结论。
|
||
- 调查时 ARM 已出现约 12 小时 43 分的新鲜完整原子归档,dead-man 最近 service 为 `success/0` 且状态为 healthy,说明异机链已自然恢复一次;该事实可解除恢复演练的事件阻塞,但不满足本 P1 事件的两个自然周期结案条件。
|
||
- 私有探针输出的 `arm_deadman_alert_state_finding_count=invalid` 是表示缺陷,不是 ARM 状态损坏:`last-deadman.json` summary 才有 `findingCount`,`deadman-alert-state.json` 按稳定 helper 合同只保存 `schemaVersion/status/category`。后续探针不得对 alert state 索取不存在的 count 字段。
|
||
- ARM health service 同期为 `failed/1`,而生产公网 `/ready` 调查前后均为 PASS、12 位构建前缀一致。该 finding 指向健康监控执行、状态或通知链,不得扩写为 OA 当前不可用;进一步读取或处置需另行授权。
|