排查(08-22-wecom-oa-backup-restore-notification-investigation): 固化企微健康告警诊断

这个提交包含在:
2026-08-23 09:18:57 +08:00
父节点 29a33031aa
当前提交 f65c8aa091
共修改 7 个文件,包含 209 行新增和 0 行删除
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
@@ -0,0 +1,45 @@
# 企业微信 OA 备份恢复通知异常排查设计
## 1. 诊断边界
本任务不修改运行系统,只建立“消息状态 → 发送组件 → timer → 运行证据”的可验证映射。消息中的稳定状态和类别是主键,企业微信机器人显示名仅视为展示信息。
## 2. 证据链
1. 从截图读取稳定状态、类别和时间窗口。
2. 通过 helper allowlist 和各调用方把状态映射到唯一组件。
3. 核对 timer 是否重复、是否存在多机并发,以及最近执行结果。
4. 核对生产备份近期成功记录,排除备份和恢复链路。
5. 从 ARM 对公网 `/ready` 做有限次数只读探测,判断当前是否持续故障。
6. 对缺少的 root-only 历史明确标注证据限制,不用猜测替代结论。
## 3. 根因分层
- 已确认:ARM 公网健康探测发生短时 `dns_or_connect_failed`,之后恢复。
- 已排除:生产备份失败、真实数据恢复、重复生产 timer、两个生产实例重复运行。
- 高概率原因:ARM 单一静态解析/外网连接链路间歇抖动。
- 尚未区分:DNS 解析器无响应、ARM 出口瞬断、目标 TCP 建连失败。现有实现把三者归为同一类别。
## 4. 建议方案
### 立即处置
- 保持备份与健康监控运行;当前 OA 与备份均健康,无需紧急停用任务或执行恢复。
- 若再次出现,记录状态、类别和时间,不在群内粘贴原始日志。
### 后续代码增强(需另行批准)
- 将探测拆分为 DNS、TCP、TLS、HTTP、响应合同阶段,并只保存稳定类别与短期计数。
- 为“恢复通知”增加来源名称,例如“公网健康恢复”,避免被误解为数据恢复。
- 增加抖动窗口策略,例如在短时间内多次告警/恢复时合并通知,但不得削弱持续故障告警。
### ARM 配置调整(需另行批准)
- 先取得脱敏诊断证据,再评估增加受控冗余解析入口或本机缓存解析服务。
- 不直接通过提高失败阈值掩盖解析/网络故障。
## 5. 验证与回滚
- 诊断任务仅运行只读命令,无需回滚。
- 后续代码增强须用无真实 Webhook 的合同测试覆盖 DNS/TCP 分类、降噪、恢复和隐私边界。
- 后续 ARM 变更须先保存原配置摘要,变更后验证解析、HTTPS、timer 和备份 dead-man,异常时恢复原配置;不得触碰备份归档和 Webhook 正文。
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
@@ -0,0 +1,26 @@
# 企业微信 OA 备份恢复通知异常排查执行计划
## 执行清单
- [x] 复核截图中的状态、类别、时间与发送组件映射。
- [x] 复核生产和 ARM timer 唯一性、启用状态及最近结果。
- [x] 复核生产最近 14 天自动备份/新鲜度记录与当前告警状态。
- [x] 对 ARM 当前 DNS/HTTPS 链路执行有限次数只读探测,并记录脱敏计数。
- [x] 运行任务文档、Trellis 治理和格式门禁。
- [x] 输出最终诊断、证据限制和分级处置建议;不修改代码或远端配置。
## 验证命令
- `python3 ./.trellis/scripts/task.py validate 08-22-wecom-oa-backup-restore-notification-investigation`
- `node scripts/qa/check-trellis-governance.mjs`
- `git diff --check`
## 风险与停止条件
- 不读取或输出 Webhook、Secret、完整账号、主机地址、原始 payload 或业务数据。
- 远端命令限定为 systemd 状态、脱敏结果计数和公网 `/ready` 只读请求。
- 若需要 root-only ARM 日志、DNS 配置写入、timer 调整、服务重启或真实消息测试,立即停止并另行申请批准。
## 回滚点
本任务不产生运行时写入;若后续进入修复任务,分别为代码变更和 ARM 配置变更设计独立回滚,不与备份归档生命周期耦合。
@@ -0,0 +1,67 @@
# 企业微信 OA 备份恢复通知异常排查
## Goal
定位企业微信群中反复出现的“内部 OA 运维告警/恢复通知”的真实发送源和触发原因,判断其是否表示备份或恢复异常,并形成不影响真实备份能力的处置建议。
## Background
- 用户截图显示发送应用名为“OA 备份告警”,但消息状态是 `FAIL_APP_HEALTH` / `PASS_APP_HEALTH_READY`,失败类别是 `dns_or_connect_failed`。
- 同一晚至少出现两轮恢复消息,其中一轮在告警后很快恢复,表现为间歇性故障而非持续不可用。
- 本任务只做诊断和方案建议;未经另行明确批准,不修改生产或 ARM 配置,不停用 timer,不重启服务,不发送测试消息。
## Confirmed Facts
- `scripts/send-wecom-group-alert.py:123` 只在故障/提醒类别变化或从故障/提醒恢复为健康时发送;相同状态应由独立状态文件降噪。
- `scripts/send-wecom-group-alert.py:133` 将所有健康恢复统一命名为“内部 OA 运维恢复通知”。该标题表示监控状态恢复,不表示执行过数据库、附件或整机恢复。
- 截图中的 `FAIL_APP_HEALTH` 只能由 ARM 公网健康探测产生,不来自每日备份、备份新鲜度 watchdog、异机归档 dead-man 或季度恢复提醒。
- ARM 健康探测每 5 分钟运行一次,连续两次失败才告警,一次成功即发送恢复通知。因此每轮告警至少代表约两个连续探测窗口未能完成公网 `/ready` 请求。
- 当前实现把 DNS 解析失败和 TCP 连接失败合并为 `dns_or_connect_failed`,现有消息无法进一步区分两者。
- 生产最近 14 天每日备份和新鲜度 watchdog 均按预期各运行一次且结果成功;未发现生产备份失败、重复调度或真实恢复操作证据。
- 两个生产 SSH 入口落在同一台机器、不同账号上,不是重复部署实例。
- ARM 当前健康探测成功,额外连续 5 次只读 `/ready` 请求全部通过;故障已自行恢复。
- ARM 当前使用单一静态 DNS 解析入口,未运行本机解析服务,解析链路缺少冗余。结合短时失败后恢复的模式,ARM 侧 DNS/外网连接间歇抖动是首要假设。
- 现有 ARM 入口不能读取 root-only 状态历史和完整 journal;若要把原因从“DNS 或连接”进一步精确到某一层,需要额外的 root 只读证据或先增强脱敏诊断。
## Requirements
- 明确区分应用健康告警、备份告警、备份新鲜度告警、异机 dead-man 和季度提醒。
- 用截图、代码、timer、状态和近期只读运行证据解释当前问题。
- 给出最小处置建议,并区分“当前无需紧急动作”“后续代码增强”“ARM 配置调整”三类。
- 不记录 Secret、完整账号、主机地址、原始消息 payload、真实业务数据或完整运维路径。
- 任何代码、部署或 ARM DNS 配置变更均需后续单独批准。
## Scope
- 企业微信截图中的稳定状态、类别和时间窗口分析。
- 公网健康探测、共用告警 helper、自动备份与异机 dead-man 的调用关系。
- 生产及 ARM 相关 timer 的只读状态、近期脱敏结果和当前公网 `/ready` 可用性。
- 诊断结论、证据限制、后续代码增强与 ARM 配置建议。
## Acceptance Criteria
- [x] 能确认截图消息的唯一发送源,并说明“恢复通知”不等于执行恢复。
- [x] 能判断生产备份当前是否正常,是否存在重复 timer 或重复实例。
- [x] 能说明当前可证实的故障边界以及仍无法区分的 DNS/TCP 子原因。
- [x] 给出不影响备份和监控可靠性的分级处置建议、验证方法与回滚边界。
- [x] 全过程不执行未经批准的生产/ARM 写入、任务停用、服务重启或外部消息发送。
## Out of Scope
- 本任务内修改 ARM DNS、网络、systemd timer 或企微机器人配置。
- 停止公网健康监控,或通过提高阈值掩盖真实持续故障。
- 数据库重置、真实恢复、恢复演练或备份数据下载。
- 主动发送测试告警、修改群成员或企业微信应用配置。
## Test Plan
- 用截图状态反查唯一调用方,并与源码 allowlist、状态机和 timer 配置交叉核对。
- 只读检查生产/ARM timer 唯一性、启用状态和最近结果。
- 复核生产最近 14 天自动备份及新鲜度 watchdog 结果。
- 在 ARM 侧执行有限次数 `/ready` 只读请求,只记录成功/失败计数。
- 运行 task validate、Trellis 治理门禁和 `git diff --check`。
## Deferred Items
- 若用户后续要求修复,建议另行批准:先增强探测器,将 DNS、TCP、TLS 和 HTTP 阶段分开,并保留短期脱敏计数;再依据证据决定是否为 ARM 配置冗余解析入口。
- ARM root-only 历史日志采集可进一步确认具体故障层,但不阻塞本任务对“发送源不是备份、当前问题是 ARM 公网健康链路抖动”的判断。
@@ -0,0 +1,43 @@
# 通知来源与运行证据
## 消息映射
- `FAIL_APP_HEALTH` / `PASS_APP_HEALTH_READY`:ARM 公网 `/ready` 健康探测。
- `FAIL_AUTOMATIC_BACKUP` / `PASS_AUTOMATIC_BACKUP_*`:生产自动备份或新鲜度 watchdog。
- `FAIL_OFFHOST_DEADMAN` / `PASS_OFFHOST_DEADMAN_FRESH`:ARM 异机归档 dead-man。
- `PASS_WITH_FINDINGS` + `quarterly_shadow_restore_due_*`:季度人工影子恢复提醒,不会自动恢复生产。
截图状态明确属于第一类。企微应用显示名“OA 备份告警”是共用群机器人的名称,不能作为故障来源判断依据。
## 状态机证据
- `scripts/check-public-app-health.py:242-268`:任意探测失败累计连续失败次数,达到阈值后才调用告警 helper。
- `scripts/check-public-app-health.py:270-279`:一次成功立即清零,并让 helper 评估健康恢复。
- `scripts/send-wecom-group-alert.py:123-140`:相同故障降噪;故障/提醒恢复后发送一次统一标题的恢复通知。
- ARM 安装合同固定每 5 分钟探测、失败阈值为 2、单次超时为 10 秒。
## 只读运行证据
- 生产自动备份三组 timer 均 enabled/active;两个 SSH 入口属于同一机器的不同账号,不构成重复调度。
- 生产最近 14 天每日自动备份和新鲜度 watchdog 均成功,未发现备份失败或真实恢复行为。
- ARM dead-man 与应用健康 timer 均 enabled/active,最近一次 service 成功。
- ARM 当前额外 5 次 `/ready` 请求全部成功,说明 OA 当前公网就绪。
- ARM 使用一个静态解析入口且未启用本机解析服务;它是当前链路的单点,但现有粗粒度类别仍不能证明当时一定是 DNS 而不是 TCP 连接失败。
## 结论
截图反映的是 ARM 到 OA 公网 `/ready` 的短时解析或连接抖动。它不是备份失败,不是恢复演练,也没有触发数据恢复。消息数量偏多的直接原因是健康探测按“连续两次失败即告警、一次成功即恢复”运行,而链路在告警和健康之间反复切换。
## 证据限制
- ARM 状态和完整 journal 为 root-only,当前入口只能读取 systemd 公共状态。
- 消息类别有意脱敏并合并 DNS/TCP,因此无法从群消息反推出原始异常。
- 历史会话检索器在当前 Trellis 构建中不可用;本结论不依赖该能力。
## 2026-08-23 最终只读复核
- 生产三组自动备份相关 timer 仍为 enabled/active。
- 最近 14 天共记录 14 次每日异机备份成功、14 次新鲜度 watchdog 成功、0 条自动备份失败记录。
- ARM dead-man 与公网健康 timer 仍为 enabled/active,最近一次公网健康 service 结果为 success/0。
- ARM 连续 5 次当前 `/ready` 请求全部成功。
- 本轮未修改代码、systemd、DNS、网络、Webhook 或企业微信配置,未发送测试消息。
@@ -0,0 +1,26 @@
{
"id": "wecom-oa-backup-restore-notification-investigation",
"name": "wecom-oa-backup-restore-notification-investigation",
"title": "企业微信 OA 备份恢复通知异常排查",
"description": "只读定位企业微信 OA 运维告警与恢复通知来源,区分公网健康抖动、备份异常和真实恢复,并形成分级处置建议。",
"status": "in_progress",
"dev_type": null,
"scope": "operations",
"package": null,
"priority": "P2",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-08-22",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "只读排查确认群消息来自 ARM 公网健康探测的 dns_or_connect_failed 抖动,不是备份失败或真实恢复;生产近 14 天备份与 watchdog 均 14/14 成功且无失败记录,ARM 当前连续探测健康。未修改生产/ARM 配置或发送测试消息。",
"meta": {}
}