文件
hyb-oa/.trellis/tasks/08-23-isolated-backup-restore-drill/implement.md
T

22 KiB

隔离备份恢复演练实施计划

执行前门禁

  • 确认任务已在用户审阅最终规划摘要后由 task.py start 进入 in_progress。
  • 只读确认本地 main、当前生产构建事实、来源/当前 migration 集合和现有恢复脚本;现场两构建均为 65 且目录无变化,但后续执行不硬编码数量。
  • 只读确认生产公网 /ready 正常;不读取生产数据库或执行生产写入。
  • 连接 ARM 后只读核对 receiver 配置有效、最新正式日批次身份、dead-man 状态、Docker 可用性和磁盘余量;不列出完整目录或原始 manifest。
  • 磁盘安全预算至少满足“批次总字节数 5 倍 + 2 GiB”;不足则 PAUSE。
  • 若当前账号没有对正式归档只读和本轮临时区/Docker 的最小权限,停止并给用户一条窄权限操作步骤;不得申请通用 root shell 或扩大既有健康监控 sudo 入口。

有序执行清单

  1. 生成不含主机信息的本轮 run id,记录 UTC 开始时间和生产前置 /ready 状态。
  2. 在 ARM 正式日归档中选择 manifest 时间最新的 complete 批次,验证三文件、字节数、SHA-256 与 24 小时 RPO 门禁;在本地仓库确认来源提交是当前构建祖先或相同,并单独记录是否严格相等。
  3. 创建权限 0700 的本轮临时工作区;启动 RTO 计时,复制整个批次并对临时副本重新执行同一校验。
  4. 创建无宿主端口、无业务网络、无持久 volume 的一次性 PostgreSQL 16 容器;数据库名含 drill,凭据随机且仅存在本轮进程环境。
  5. 使用 scripts/restore-db.sh 的 shadow 合同恢复数据库;任何警告、确认不匹配或 restore 非零立即停止后续验证并进入清理。
  6. 从来源提交动态冻结 migration 名称集合;通过错误即停的只读 SQL 验证恢复库成功且未回滚的 migration 集合精确匹配来源、无失败/未知项,并静态记录来源与当前构建的 migration 差异;随后验证固定 12 类关键表并记录行数。
  7. 使用 scripts/restore-storage.sh 恢复附件到本轮空目录;比较归档与解包普通文件数。
  8. 在影子库中聚合本地附件引用,以不输出路径的校验程序验证路径安全、文件存在及适用的大小/SHA-256;记录引用总数和不一致数。
  9. 所有数据检查通过时停止 RTO 计时;按 manifest 时间计算 RPO,记录当前发布到首个同源恢复点的可用滞后,形成区分“原始数据可恢复性”和“当前 schema 升级状态”的 GO/PAUSE/NO_GO 脱敏结论。
  10. 清理本轮容器、临时网络(若有)、临时附件目录、临时批次副本和临时凭据;确认无本轮残留。
  11. 再次只读检查生产公网 /ready 和 ARM dead-man/健康 timer 状态,确认演练没有影响在线生产与现有监控。
  12. 将证据写入当前 task,并在既有事实源补充当前状态;不提交任何真实备份、日志或业务内容。

Attempt 1 执行状态

  • 最新 ARM 候选在 select_latest_batch 阶段因 manifest build_mismatch 失败关闭;复制、容器创建、数据库恢复和附件恢复均未开始。
  • 失败后的只读复核确认:公网 /ready 与生产自动备份 unit 的构建短标识一致,两个生产侧事实均为 7b86ffb3;因此当前不是计划事实源过期或备份 unit 冻结构建漂移。
  • ARM 一次性 root 入口和本轮 runtime 均为零残留,两个监控 timer 仍为 active;本轮 5 个普通用户暂存文件已按 SHA-256 精确匹配并删除,未宽泛删除目录。
  • Attempt 1 保持 NO_GO。在当时取得最新 ARM manifest 的脱敏构建短标识、run id 配对关系和 manifest 摘要一致性前,不进入第 3 步及之后的恢复流程。

Attempt 1 后只读调查清单

  1. 生成固定无参数的 root-owned ARM 只读身份探针和最小 sudoers,只允许读取 receiver 路径配置及最新正式日归档。
  2. 本地执行 bash -n、候选 SHA-256、sudoers candidate visudo(环境可用时)和禁止写入/恢复关键字人工复核。
  3. 上传到 ARM 普通用户私有暂存目录;由用户输入 sudo 密码安装固定入口,安装前再次核对全部候选 SHA-256 和系统 sudoers 语法。
  4. 运行一次固定入口,记录构建短标识、批次年龄、manifest 配对、产物聚合完整性和入口自清理状态。
  5. 对照公网 /ready 与生产自动备份 unit 的当前短标识:相等时记录同源恢复点已形成;不相等时在本地仓库核对是否为当前构建祖先,祖先且完整/新鲜仍可形成原始恢复重试方案,未知或非祖先才 NO_GO。
  6. 按 SHA-256 精确删除普通用户暂存文件;不得删除 ARM 正式归档或宽泛清理目录。

只读调查执行结果

  • 探针候选通过本地/ARM SHA-256、Bash 语法和 ARM sudoers candidate 校验。
  • 固定入口只读读取最新正式批次并自删除;未复制、解包、启动容器或恢复数据。
  • 最新批次 ec682565 在 24 小时内,run id/目录配对及 DB/附件大小、SHA-256 均通过,但与当前 7b86ffb3 不匹配。
  • 只读对时确认自动备份合同刷新晚于最新批次生成;finding 收敛为“发布后尚未产生新构建恢复点”。
  • root 入口残留为 0,3 个普通用户候选及暂存目录已按精确身份清理,两个 ARM 监控 timer 仍 active。
  • 08-24 最新自然批次只读探针通过:来源与当前构建相同、年龄不超过 24 小时、run id 配对及 DB/附件 SHA-256 均通过;临时入口和暂存零残留。当前可提交恢复重试审批,尚未获得恢复授权。

Attempt 2 恢复重试结果

  • 用户批准后安装并执行动态 migration 集合版固定恢复入口。
  • ARM 正式归档最新批次首次门禁通过,并开始复制到本轮私有临时区。
  • 临时副本复检因 runner 错把正式源目录 basename 身份规则应用到固定名 batch 而触发 batch_identity;失败阶段为 create_isolated_runtime,数据库容器和恢复均未开始。
  • runtime、root 入口、6 个普通用户候选和暂存目录均精确清理为零残留,两个 ARM 监控 timer 保持 active。
  • 修订 runner:正式源目录继续强制 basename/run id 配对;临时副本 manifest run id 重绑到源侧已验证 run id,并继续校验完整内容合同及源/副本元数据精确一致。补齐 2 正例 + 3 负例后重新执行只读身份探针,实际恢复另行批准。

Attempt 3 准备批准边界

  • 用户已批准修复 runner,并重新执行 08-25 最新正式批次只读身份探针。
  • 回归已覆盖:正式源通过、同内容改名副本通过、内容篡改副本失败、源 basename/run id 错配失败、副本 run id 与源侧已验证 run id 不同失败。
  • Attempt 3 未复用 Attempt 2 新鲜度:08-25 最新批次年龄约 6 小时 11 分,来源与当前构建相同,run id 配对、大小与 SHA-256 均通过。
  • 用户复核探针后明确批准 Attempt 3 实际恢复,范围仅限 ARM 隔离影子恢复。

Attempt 3 恢复结果与停止点

  • 修复后的候选在本地和 ARM 通过 SHA-256、Bash 与 sudoers 语法校验后安装;正式归档门禁和临时副本源侧身份重绑通过。
  • runner 在 restore_database 阶段因 restore-db.sh 返回非零而以 database_restore_failed 失败关闭;migration、12 类关键表、附件恢复与关联验证均未执行。
  • 一次性容器、本轮 runtime、临时 root 入口和普通用户候选均精确清理为零残留;ARM 两个监控 timer 保持 active,生产演练后 /ready 仍为相同构建的 PASS。
  • 在任何 Attempt 4 前修复失败诊断合同:分别记录 dump 列表校验与实际 restore 的脱敏阶段码,并确保失败清理前只保留安全聚合诊断;本地回归已通过,远端探针和实际恢复仍需重新批准。

Attempt 4 前的修复清单

  1. runner 在调用 restore-db.sh 前,以同一 PostgreSQL 16 容器对临时副本执行独立 pg_restore --list,记录 dump_list=PASS|FAIL|NOT_RUN。
  2. 独立确认影子目标数据库名与连接状态,记录 target_confirmation=PASS|FAIL|NOT_RUN;不输出数据库名或连接信息。
  3. 仅在前两项通过后执行实际 restore,记录 restore_apply=PASS|FAIL|NOT_RUN;失败类别区分为 dump_list_failed、restore_target_confirmation_failed 或 restore_apply_failed_after_list_pass。
  4. 原始 stderr 只进入本轮 0600 私有日志并随 runtime 删除;最终输出只含 allowlist 阶段状态和既有脱敏聚合。
  5. 补三条阶段负例、敏感输出负例和每条失败路径零残留回归;通过后重新执行最新批次身份探针,再请求 Attempt 4 实际恢复批准。

Attempt 3 后本地修复结果

  • dump_list、target_confirmation、restore_apply 固定输出 PASS/FAIL/NOT_RUN,并增加执行路径、脚本/pg_restore 退出码、对象错误计数与稳定类别。
  • 诊断在清理前输出;注入 apply 退出码 23 后,调用方在 runtime/access 清理完成后仍可读取阶段状态和退出码。
  • partial-success 规则保持失败关闭:任何 pg_restore 非零均为 FAIL/NO_GO,不设置可忽略对象错误白名单。
  • PostgreSQL 16 custom dump 直接驱动真实 scripts/restore-db.sh 的 Docker stdin 与 host-file 两条路径,均恢复成功。
  • dump-list 失败、目标确认失败、apply 失败及敏感输出负例通过;既有成对备份/恢复回归和自动异机备份合同通过。
  • 用户另行批准并完成 Attempt 4 前最新批次只读探针:构建 SAME、年龄约 7 小时 24 分、run id/产物完整性与 migration 集合门禁通过,入口及暂存零残留;尚未执行实际恢复。

Attempt 4 前的持久化门禁

  1. 将已验证的 runner 和附件校验器从个人 workspace 收敛为 scripts/qa/ 下的跟踪文件,不改变恢复边界或生产脚本。
  2. 将八类 runner 回归收敛为可独立执行的 run-offhost-shadow-restore-runner-contract.sh,直接驱动真实 restore-db.sh 双路径。
  3. 合同脚本必须机械验证三阶段诊断、执行路径、两个退出码、非零失败关闭、源/副本身份、dynamic migration、敏感输出和清理顺序;对候选生成稳定 SHA-256。
  4. 运行新合同、既有成对备份/恢复、自动异机备份 74 项、Trellis 治理与敏感扫描;全部通过后才允许把跟踪文件打包为 Attempt 4 候选。
  5. 本阶段仅本地修改与测试;新批次探针结果和持久化门禁均通过后,再单独申请 Attempt 4 实际恢复。

Attempt 4 前持久化门禁结果

  • runner 已收敛为 scripts/qa/offhost-shadow-restore-runner.template.sh;现场路径、账号、容器/数据库命名、timer、构建和 migration 指纹均为经严格校验的私有渲染字段。
  • 附件关联校验器已收敛为 scripts/qa/validate-offhost-shadow-restore-attachments.py。
  • scripts/qa/run-offhost-shadow-restore-runner-contract.sh 可独立复跑并提供 render 模式;相同私有配置生成确定性候选 SHA-256,shell 注入在输出候选前失败关闭。
  • 源/副本身份、内容篡改、dynamic migration、附件安全、三阶段诊断、真实 restore 双路径、NOT_RUN、退出码、清理顺序和敏感输出合同全部通过。
  • 新增三个文件由 check-sensitive-content.mjs 定向扫描,3/3 文件、finding 0;本地测试容器残留 0。
  • .gitignore 增加 /.restore-drill-private/ 专用目录;合同分别证明仓库内未忽略的私有 JSON、未忽略的现场候选均在创建输出前失败,ignored 输入/输出正例通过。
  • 私有原始日志保留唯一初始化截断,五个固定阶段均以标记分隔并追加写;静态合同拒绝后续阶段再次截断。
  • 完整复跑 runner 合同、既有隔离备份/恢复演练和自动异机备份 74 项;task、Trellis、process、默认/定向敏感扫描及空白门禁通过,测试容器和本地私有路径残留均为 0。
  • Attempt 4 最新批次只读探针重新执行并通过;候选已由提交 5ec7db1 的跟踪模板确定性渲染,完整合同、依赖钉住和 SHA-256 复核通过。
  • 用户批准 Attempt 4 后,候选在 ARM 安装前再次通过 SHA 清单与语法校验并执行;dump_list=PASS/0,随后在 target_confirmation 阶段失败,实际 restore apply、migration、关键表和附件均未运行。
  • Attempt 4 runtime、临时 root 入口和普通用户上传暂存均清理为 PASS/0;按预先裁决规则转入本地合同路径与 ARM 实际环境的只读差异调查,不直接修补 runner 或创建 Attempt 5。
  • 只读差异矩阵与本地精确复现收敛到 PostgreSQL 初始化就绪竞态:合同夹具等待连续两次真实查询,runner 仅等待首次 pg_isready;本地稳定数据库上的同一 target query 为 PASS/0,ARM 所有临时对象复核残留 0。
  • Attempt 4 后正式归档只读探针与执行前为同一构建和同一 manifest 脱敏摘要,run id 配对、产物 SHA-256、24 小时新鲜度均通过;探针入口与暂存残留 0,正式归档内容级零修改已补证。
  • 演练后腾讯云生产 /ready 已由执行方沿既有只读路径补证:状态 PASS、12 位构建前缀 7b86ffb3c9ef,与演练前一致;未记录地址或原始响应。
  • 用户已批准仅在本地实施和回归 PostgreSQL 稳定就绪修复;该批准不包含连接 ARM 或执行 Attempt 5。

Attempt 5 前仅本地修复结果

  • 在跟踪 runner 中以容器内 TCP 回环 127.0.0.1 的真实 SELECT current_database() 作为结构性就绪判据,并以最多 60 次、间隔 1 秒、连续两次成功且名称精确匹配作为额外保险,替换首次 Unix socket pg_isready 即通过逻辑;名称错配立即失败,查询非零重置连续成功计数。
  • 增加 target query 退出码、名称匹配、尝试次数和连续成功次数的 allowlist 诊断,区分查询非零与名称错配;私有原始日志、失败先诊断后清理和零残留边界不变。
  • 可复跑合同直接提取并驱动 rendered runner 中同一稳定就绪函数;fixture 外围与 runner 不同源的等待循环已删除。
  • “socket 可用但 TCP 不可用仍失败”、首次失败后恢复、streak 重置、名称错配立即失败、永久查询失败有界退出五类回归通过;真实 restore-db.sh Docker stdin/host-file、源/副本身份、dynamic migration、附件安全、敏感输出和清理合同继续通过。
  • 已审计合同全部覆盖机制,未发现第二套自写等价恢复逻辑;完整 runner 的 migration/12 类表/附件编排仍只能由真实隔离恢复验证,已作为明确限制保留,不能由本地组件合同替代。
  • scripts/restore-db.sh 相对生产构建字节未改;runner、成对备份/恢复、自动异机备份 74 项、task、Trellis、process、docs、敏感扫描和空白门禁均通过。
  • 本地修复阶段不连接 ARM、不生成或安装 Attempt 5 现场入口。提交后重新选择最新批次并执行只读探针、重新渲染候选和核对 SHA-256;实际恢复须再次单独批准。

Attempt 5 前只读身份探针结果

  • 用户批准后仅安装并执行最新正式批次只读探针;未复制、解包、启动容器、读取业务数据或执行恢复。
  • 最新批次来源与当前生产构建相同,探针时年龄 64625 秒且不超过 24 小时;run id 配对、DB/附件大小与 SHA-256、source digest 均通过。
  • 来源/当前 migration 目录为 65/65、差异 0;仓库 HEAD 相对生产构建的应用代码漂移文件数 0。
  • 探针 root 入口与普通用户暂存残留均为 0;按已验证私有配置取得的 dead-man/健康 timer 实际 unit 状态为 active/active。
  • 本次探针完成时 RTO 八阶段实现尚未批准,因此当时未渲染 Attempt 5 最终候选、未生成最终候选 SHA-256,也未执行实际恢复;后续本地实现授权单独追加,不改写该时点事实。

Attempt 5 前 RTO 分阶段实施(已批准仅本地实现)

  • 设计口径已固定为总 RTO 加八段串行秒数:批次复制、容器启动、dump list、目标确认、restore apply、migration/12 类表、附件恢复与关联校验、最终源/副本复核。
  • runner 增加固定 allowlist 阶段字段,成功时八段均为非负整数且精确加总为 data_restore_rto_seconds;失败时已完成段和当前失败段保留数字,后续段为 NOT_RUN。
  • 合同注入各阶段成功/失败,验证最长 TCP 轮询只进入目标确认阶段、后续 NOT_RUN、总数精确加总、输出不含路径/SQL/凭据;原有清理与恢复合同继续通过。
  • 用户批准的当前边界仅为本地修改 runner、更新可复跑合同并执行回归;不连接 ARM、不重新渲染 Attempt 5 现场候选、不执行实际恢复。
  • 本轮八阶段计时与完整合同通过后,下一份满足 24 小时门禁的新鲜批次直接进入 Attempt 5 的候选渲染、SHA-256 核对、只读探针和实际恢复审批流程。此后发现的改进项,除非会导致 Attempt 5 失败或产生错误结论,否则只登记为后续任务,不再作为 Attempt 5 前置增强插队。
  • 八阶段状态必须在失败路径清理前输出;阶段边界沿用 dump_list、target_confirmation、restore_apply 等既有诊断词汇,不建立第二套同义命名。

Attempt 5 最终候选与执行前探针

  • 从提交 0b94b97 的跟踪模板和 ignored 私有配置完成两次确定性渲染;候选字节一致、完整 runner 合同通过,候选 SHA-256 为 7904052aa5a8c31d54734bc2fbf2d9caf2dac8b0116135119519f8467b3dae27。
  • 候选固定的是生产构建前缀和来源 migration 集合指纹,不固定探针时的 source run id;实际执行会在 ARM 上重新选择当时最新正式批次,并对该批次重新执行完整身份、产物 SHA-256、24 小时 RPO 和 migration 集合门禁。
  • 最终只读探针重新选择执行时最新正式批次:来源构建与当前生产相同,年龄 68889 秒且不超过 24 小时,run id/目录配对、DB/附件完整性和 source digest 均通过。
  • 本地复核来源/当前 migration 为 65/65、差异 0,候选 migration 指纹与来源集合一致,应用代码漂移和 scripts/restore-db.sh 字节漂移均为 0。
  • 探针 root 入口、sudoers 和普通用户暂存残留均为 0;本地 runner 合同容器残留为 0。本阶段未复制、解包或恢复正式归档,未连接腾讯云生产。
  • Attempt 5 实际隔离恢复须在新鲜度门禁仍成立时另行批准;runner 会在执行开始重新选择最新正式批次并重算 24 小时门禁,超龄或身份变化均失败关闭。执行证据还必须记录实际选择的批次脱敏身份及其与本次探针批次是否一致,不能默认二者相同。

验证命令类别

  • 本地/仓库:bash -n 覆盖备份、DB 恢复、附件恢复和 receiver 脚本;只读统计 migration 目录。
  • ARM 前置:receiver validate-config、systemd 公共状态、Docker 版本/容量、候选 manifest 脱敏校验。
  • 恢复输入:sha256sum、manifest schema/文件集/字节数校验、pg_restore --list、tar -tzf/tar -tvzf。
  • DB 验证:psql -v ON_ERROR_STOP=1 的 migration 名称/状态集合和固定表计数查询;本地用 Git 比较来源/当前 migration 目录,不执行 migrate。
  • 附件验证:find 普通文件计数及不输出路径的 Python 只读校验;只输出引用数和 mismatch 数。
  • 任务门禁:
    • python3 ./.trellis/scripts/task.py validate .trellis/tasks/08-23-isolated-backup-restore-drill
    • node scripts/qa/check-trellis-governance.mjs
    • node scripts/qa/check-process-contract.mjs --baseline --json
    • node scripts/qa/audit-docs.mjs --check-registry
    • node scripts/qa/check-sensitive-content.mjs
    • git diff --check

具体远端命令在执行阶段依据 ARM 已安装的 root-owned receiver 配置生成,不把真实地址、账号、目录或凭据写入本计划。

停止条件

  • 最新批次不存在、超过 24 小时、manifest 或 SHA-256 不一致,或来源提交未知/不是当前构建祖先或相同。仅“不严格相等”不再单独停止。
  • 权限、Docker、PostgreSQL 16 或磁盘预算不满足。
  • 命令目标可能指向腾讯云生产、ARM 正式归档、receiver 管理目录或既有容器/网络。
  • DB restore、migration、固定表、附件安全、文件引用、哈希/大小任一检查失败。
  • 生产 /ready 在演练期间异常,或现有 ARM dead-man/健康监控状态改变。
  • 清理对象身份不明确。此时保留现场并报告,不使用宽泛删除命令。

变更与证据文件边界

  • 允许写入:当前 Trellis task、既有 OPS7 Runbook/人工试运行报告中的脱敏状态摘要、本轮 ARM 私有临时区和一次性 Docker 对象。
  • 禁止写入:产品代码、migration、生产/ARM 正式配置、生产 DB/附件、ARM 正式归档、企业微信。
  • 计划新增的 task 内证据:research/restore-drill-evidence.md;只在实施实际发生后创建。

审查与收口

  • 本任务涉及真实备份数据和远端运维边界。执行完成后做一次只读复核,重点确认:生产零变更、归档未修改、RTO/RPO 口径正确、附件验证没有漏项、敏感内容未进入证据、临时对象已清理。
  • 若现有脚本需要修改,回到规划阶段补充设计和新的批准;不得在远端临时放宽 shadow/路径/确认门禁。
  • 门禁通过后用中文 commit 引用 08-23-isolated-backup-restore-drill,是否推送仍按用户当时授权执行。