实现应用健康监控(07-27-app-health-monitoring)
这个提交包含在:
@@ -1097,3 +1097,99 @@ bash scripts/run-automatic-backup.sh run
|
||||
# The Docker CLI inherits the value without placing it in argv.
|
||||
PGPASSWORD="$POSTGRES_PASSWORD" docker exec -e PGPASSWORD db pg_dump ...
|
||||
```
|
||||
|
||||
## Scenario: Independent Public Application Health Monitor
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: installing or changing the off-host public `/ready` probe, its ARM
|
||||
timer, application-health alert states or the shared WeCom group-alert helper.
|
||||
- Purpose: detect an unavailable OA host, edge/TLS path, application or database
|
||||
readiness signal without adding a time-series platform or relying on traffic.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Probe: `scripts/check-public-app-health.py --ready-url <https-/ready> --state-dir
|
||||
<root-0700-dir> --webhook-file <root-0600-file> --alert-helper <installed-helper>
|
||||
--failure-threshold 2 --timeout-seconds <1..30>`.
|
||||
- Installer: `deploy/trialrun/install-offhost-app-health-monitor.sh
|
||||
install|enable|uninstall`.
|
||||
- Units: `hyb-oa-public-app-health.service` and
|
||||
`hyb-oa-public-app-health.timer`; the timer uses `OnCalendar=*:0/5`.
|
||||
- Local contract: `bash scripts/qa/run-app-health-monitor-contract.sh`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Run the probe from the existing ARM host against an exact HTTPS `/ready` URL.
|
||||
Success requires trusted TLS, HTTP 2xx, bounded JSON and `data.ok === true`.
|
||||
- Count any stable failure category toward the same consecutive-failure
|
||||
threshold. The first failure is pending; the second evaluates a failure
|
||||
alert. Once alerting, a changed category may send a new alert, identical
|
||||
failures are deduplicated, and one later success sends one recovery notice.
|
||||
- Persist only schema version, count, stable category and the alert state in a
|
||||
root-owned `0700` directory with atomic `0600` files. Never persist or send
|
||||
the URL, resolved address, response payload, credential or raw exception.
|
||||
- Application-health probe state and alert state are separate from backup and
|
||||
dead-man state. The installer must deploy the helper revision that passed the
|
||||
backup contract; an older installed helper may reject the new status tokens.
|
||||
- `install` writes only the exact managed probe/helper/state/unit targets and
|
||||
leaves the timer disabled. `enable`, ARM writes, real robot messages and a
|
||||
controlled failure/recovery drill remain separately approved operations.
|
||||
- `uninstall` removes only the exact application-health units and probe. It
|
||||
retains the shared helper, webhook, state evidence and all backup components.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Non-HTTPS, userinfo, query, fragment or non-`/ready` URL -> reject before a
|
||||
request with `ready_url_invalid`.
|
||||
- DNS/connect, TLS, timeout, HTTP, oversized response or invalid JSON/contract
|
||||
-> stable redacted category and non-zero oneshot exit.
|
||||
- Invalid owner/mode/link/state or alert-helper failure -> fail closed with a
|
||||
fixed local error; do not include the offending value.
|
||||
- A first isolated failure followed by health -> no failure or recovery message.
|
||||
- Any overridden managed target or service name -> installer stops before
|
||||
`systemctl`, file replacement or removal.
|
||||
- ARM itself or its network/robot path unavailable -> the monitor can be silent;
|
||||
do not report this design as having an independent third-party dead-man.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: ARM observes two consecutive unavailable checks, sends one redacted
|
||||
category alert, suppresses repeats and sends one recovery after health returns.
|
||||
- Base: repository contracts pass while units remain disabled; report the code
|
||||
as ready for an approved rollout, not as an active monitor.
|
||||
- Bad: run the timer only on the OA host, send raw `/ready` errors, install and
|
||||
enable in one unapproved step, or deploy the probe without its helper revision.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- The local HTTPS stub must assert healthy baseline, pending first failure,
|
||||
second-failure alert, identical deduplication, changed-category alert, one
|
||||
recovery, transient suppression, TLS/connect/timeout/HTTP/size/JSON failures,
|
||||
corrupt private state, URL rejection and absence of sensitive output.
|
||||
- Run Python compile and shell syntax checks, validate the five-minute calendar,
|
||||
and assert disabled-by-default plus exact managed targets and retained state.
|
||||
- Re-run `bash scripts/qa/run-automatic-backup-contract.sh` whenever the shared
|
||||
helper allowlist or behavior changes.
|
||||
- ARM install, enabled/active timer status and real failure/recovery messages are
|
||||
external evidence and may not be inferred from the local contract.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```bash
|
||||
# Local contract passed, so install and enable remotely and call it active.
|
||||
bash deploy/trialrun/install-offhost-app-health-monitor.sh enable
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```bash
|
||||
bash scripts/qa/run-app-health-monitor-contract.sh
|
||||
bash scripts/qa/run-automatic-backup-contract.sh
|
||||
# After separate ARM-write approval: install disabled, inspect, then seek
|
||||
# enable/real-message/drill approval before claiming the monitor is active.
|
||||
<explicit-controlled-paths> \
|
||||
bash deploy/trialrun/install-offhost-app-health-monitor.sh install
|
||||
```
|
||||
|
||||
@@ -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,91 @@
|
||||
# 应用健康监控设计
|
||||
|
||||
## 1. Architecture and Boundaries
|
||||
|
||||
首版由三个独立部分组成:
|
||||
|
||||
1. 仓库内的 Python 探测器:从 ARM 发起公网 HTTPS `/ready` 请求,验证传输、状态码和最小 JSON 合同,维护连续失败计数,并生成只含状态/类别/计数的 0600 摘要文件。
|
||||
2. 现有 `scripts/send-wecom-group-alert.py`:扩展应用健康允许状态,继续负责 webhook 文件校验、脱敏消息、同类故障降噪和恢复通知。
|
||||
3. 独立 ARM systemd 安装器:复制探测器和本次已通过备份回归的共用告警 helper 修订,引用既有 root-owned 0600 webhook,写入 oneshot service/timer;`install` 默认只安装 disabled units,`enable` 才启用,`uninstall` 只删除应用健康组件并保留共用 helper 与状态证据。
|
||||
|
||||
不修改 Fastify `/ready`,不修改 Compose 拓扑,不引入 Prometheus/Alertmanager,不把应用监控并入备份 receiver 的生命周期。
|
||||
|
||||
## 2. Probe Contract
|
||||
|
||||
### Input
|
||||
|
||||
- 固定要求 `https` URL,拒绝 userinfo、fragment、非预期 query;默认目标为项目公开 OA 的 `/ready`。
|
||||
- 连接与读取均有短超时,单次 timer 不得长期占用。
|
||||
- 状态目录、摘要、探测状态和告警状态均由 root 持有,目录 0700、文件 0600,拒绝符号链接和宽权限输入。
|
||||
|
||||
### Success
|
||||
|
||||
- TLS 证书链和主机名校验通过。
|
||||
- HTTP 2xx。
|
||||
- 响应是受大小限制的 JSON,且 `data.ok === true`。
|
||||
- 不要求固定 `buildSha`,避免正常发布触发误报;不持久化完整响应。
|
||||
|
||||
### Failure categories
|
||||
|
||||
只输出稳定 token,不输出异常原文:
|
||||
|
||||
- `dns_or_connect_failed`
|
||||
- `tls_validation_failed`
|
||||
- `request_timeout`
|
||||
- `http_unavailable`
|
||||
- `response_too_large`
|
||||
- `response_contract_invalid`
|
||||
- `local_state_invalid`
|
||||
- `alert_evaluation_failed`
|
||||
|
||||
HTTP 503 不解析并转发数据库错误详情,只归类为 `http_unavailable`。日志和企微消息不得包含地址解析结果、IP、请求/响应 payload、凭证或绝对路径。
|
||||
|
||||
## 3. Failure Confirmation and Alert State
|
||||
|
||||
探测器维护独立 probe state:`schemaVersion`、`consecutiveFailures`、`lastCategory`。告警 helper 继续维护自己的 alert state。
|
||||
|
||||
状态转换:
|
||||
|
||||
```text
|
||||
healthy ──首次失败──> pending_failure(记录,非零退出,不发消息)
|
||||
pending_failure ──再次失败──> alerting(调用 helper)
|
||||
alerting ──相同失败──> helper 降噪
|
||||
alerting ──类别变化──> helper 发送新类别告警
|
||||
pending_failure ──成功──> healthy(不发送恢复消息)
|
||||
alerting ──成功──> healthy(helper 发送一次恢复通知)
|
||||
```
|
||||
|
||||
连续失败按“任意失败”累计,第二次使用当前类别告警;这样不会因 DNS、连接、HTTP 类别在同一事故中抖动而无限推迟。达到阈值后每次都调用 helper,由 helper 根据状态和类别决定是否发送。状态采用同目录临时文件 + 原子替换。
|
||||
|
||||
若真实消息发送失败,helper 不更新 alert state,下一次 timer 会重试。探测 service 保持非零退出,便于 `systemctl status`/journal 只读检查。
|
||||
|
||||
## 4. Systemd Design
|
||||
|
||||
- service:`Type=oneshot`、`UMask=0077`、依赖 `network-online.target`,以 root 运行以读取现有 root-owned webhook;不加载 OA `.env`。
|
||||
- timer:每 5 分钟执行,`Persistent=true`,开机后补一次错过的执行;不启用随机延迟,以便最长发现时间可预测。
|
||||
- 安装器校验源文件、目标 URL、状态目录、webhook/helper 权限和依赖;`install` 与 `enable` 分离,避免未经真实告警批准就自动发送。
|
||||
- timer 位于 ARM,因此可发现 OA 主机整体不可达;ARM 自身宕机仍会导致探测失声,首版通过 runbook 中的定期 `systemctl list-timers` 检查记录该剩余风险,不新增第三方 dead-man。
|
||||
|
||||
## 5. Compatibility and Reuse
|
||||
|
||||
- `send-wecom-group-alert.py` 仅新增 `FAIL_APP_HEALTH` 与 `PASS_APP_HEALTH_READY` 允许状态;现有 `FAIL_*`/健康恢复逻辑保持不变。
|
||||
- 现有自动备份契约测试必须继续通过,证明 allowlist 扩展未改变备份消息行为。
|
||||
- 应用监控使用独立 alert state 文件,不与自动备份或 WARCH3 的降噪状态互相覆盖。
|
||||
|
||||
## 6. Rollout and Rollback
|
||||
|
||||
实现阶段只产出脚本、测试和现有运维文档更新。远端 rollout 是后续独立批准动作:
|
||||
|
||||
1. 在非真实发送测试模式运行健康/失败/恢复状态机。
|
||||
2. ARM 执行安装但不启用,检查 unit 内容、文件权限和手动健康探测。
|
||||
3. 另获批准后启用 timer,并进行一次受控失败演练,确认只出现一条告警和一条恢复通知。
|
||||
4. 只读核对 timer 最近执行与下一次计划时间。
|
||||
|
||||
回滚时 disable/stop 并卸载应用健康 units/探测器;不触碰备份 receiver、归档、共用 webhook 和 helper。若 helper 已被其他能力共用,不在应用监控卸载中删除。
|
||||
|
||||
## 7. Deferred Items
|
||||
|
||||
- 证书到期前 N 天预警。
|
||||
- 登录或报销等业务合成事务。
|
||||
- 5xx 比率、延迟分位数和趋势图。
|
||||
- ARM timer 自身的第三方 dead-man。
|
||||
@@ -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,47 @@
|
||||
# 应用健康监控实施清单
|
||||
|
||||
## 1. Implementation
|
||||
|
||||
- [x] 新增独立 Python 公网 `/ready` 探测器,完成严格 URL、TLS、HTTP、响应大小和 JSON 合同校验。
|
||||
- [x] 实现 0700/0600 本地状态合同、连续失败计数、原子写入和稳定脱敏类别。
|
||||
- [x] 扩展 `scripts/send-wecom-group-alert.py` 的应用健康状态 allowlist,保持既有降噪与恢复语义。
|
||||
- [x] 新增独立 ARM systemd 安装器,包含 `install`、`enable`、`uninstall`、输入/权限校验和 disabled-by-default 行为。
|
||||
- [x] 新增应用健康契约测试,使用本地 HTTPS/HTTP stub 或受控 mock 覆盖状态机、异常输入和隐私边界,不调用真实 webhook。
|
||||
- [x] 把新增测试接入最小验证入口;复跑现有自动备份契约,防止共用 helper 回归。
|
||||
- [x] 更新现有试运行与自动化验证手册、部署 runbook/checklist 中的安装、只读检查、受控演练和回滚步骤;不新建重复运维文档。
|
||||
|
||||
## 2. Expected Files
|
||||
|
||||
- `scripts/`:应用健康探测器与共用企微告警 helper 小范围扩展。
|
||||
- `deploy/trialrun/`:独立 ARM 安装器及既有 runbook/checklist 更新。
|
||||
- `scripts/qa/`:应用健康契约测试与最小门禁接入。
|
||||
- `docs/试运行与自动化验证手册.md`:操作与验收边界更新。
|
||||
|
||||
若实现时发现需要修改 `/ready`、Compose、数据库、Caddy 配置或企业微信业务接口,必须退回规划并重新批准,不得顺手扩大范围。
|
||||
|
||||
## 3. Validation
|
||||
|
||||
- [x] `python3 -m py_compile <新增探测器> scripts/send-wecom-group-alert.py`
|
||||
- [x] `bash -n <新增安装器> scripts/qa/<新增契约测试>.sh`
|
||||
- [x] `bash scripts/qa/<新增契约测试>.sh`
|
||||
- [x] `bash scripts/qa/run-automatic-backup-contract.sh`
|
||||
- [x] `node deploy/trialrun/validate-env-contract.js`(证明未引入部署 env 漂移)
|
||||
- [x] `node scripts/qa/check-process-contract.mjs --baseline --json`
|
||||
- [x] `node scripts/qa/audit-docs.mjs --check-registry`
|
||||
- [x] `node scripts/qa/check-trellis-governance.mjs`
|
||||
- [x] `python3 ./.trellis/scripts/task.py validate 07-27-app-health-monitoring`
|
||||
- [x] `git diff --check`
|
||||
|
||||
## 4. Review Gates
|
||||
|
||||
- [x] 检查所有告警文本只包含状态、类别和恢复卡提示。
|
||||
- [x] 检查首次瞬时失败不发送、第二次持续失败发送、重复失败降噪、恢复只发送一次。
|
||||
- [x] 检查 helper 共用状态扩展不改变备份告警行为。
|
||||
- [x] 检查安装器默认不 enable、不发送真实消息,卸载不删除共用 helper/webhook 或备份能力。
|
||||
- [ ] 实际 ARM 安装、启用、真实告警演练和公网检查另行取得明确批准。
|
||||
|
||||
## 5. Rollback Points
|
||||
|
||||
- 代码回滚:删除独立探测器/安装器/契约测试,恢复 helper allowlist 与现有文档段落。
|
||||
- 远端回滚:先 disable/stop 应用健康 timer,再运行该安装器的 uninstall;保留脱敏状态证据供复盘。
|
||||
- 不回滚或删除:自动备份 receiver、备份 dead-man、备份归档、共用 webhook 文件。
|
||||
@@ -0,0 +1,69 @@
|
||||
# 应用健康监控
|
||||
|
||||
## Goal
|
||||
|
||||
为内部 OA 建立低维护、可降噪、可恢复通知的持续健康探测,使应用不可用、数据库不可达或公网 HTTPS 链路异常能够在无人访问时被及时发现,而不引入 Prometheus 等不符合 5–10 人团队维护成本的基础设施。
|
||||
|
||||
## Scope
|
||||
|
||||
- 在现有 ARM 异机侧每 5 分钟从公网探测 OA `/ready`。
|
||||
- 新增独立探测脚本、独立 systemd 安装器和无需真实企微的契约测试。
|
||||
- 小范围扩展现有企微群机器人 helper,使其支持应用健康故障、降噪与恢复状态。
|
||||
- 更新现有试运行手册、部署 runbook/checklist;代码实现完成后,远端安装、启用和真实告警演练仍需单独批准。
|
||||
|
||||
## Background and Confirmed Facts
|
||||
|
||||
- `server/src/app.js` 已提供 `/health`、`/ready` 和按配置启用的 `/metrics`;`/ready` 会查询数据库,失败时返回 503,成功响应包含脱敏的构建标识。
|
||||
- `deploy/trialrun/docker-compose.yml` 已有数据库与应用容器 healthcheck,但容器 healthcheck 本身不会向管理员发送通知。
|
||||
- `deploy/prometheus/alerts-slo.yml` 已定义可用性、延迟和 5xx 告警规则,但当前试运行 Compose 未部署 Prometheus/Alertmanager,因此规则没有持续执行者。
|
||||
- 现有部署、live smoke、WARCH3/WAUTO1 等脚本会调用公网 `/ready`,但它们是发布验证或每日业务/归档巡检,不是高频、独立的应用可用性看门狗。
|
||||
- `scripts/send-wecom-group-alert.py` 已实现 webhook 文件权限校验、脱敏状态消息、相同故障降噪和故障恢复通知;当前允许的状态类别仅覆盖备份/异机 dead-man,需要为应用健康状态显式扩展或抽取兼容合同。
|
||||
- `deploy/trialrun/install-warch3-ops-timer.sh` 和 `deploy/trialrun/install-automatic-backup-timer.sh` 提供了 systemd oneshot/timer、安装/卸载、路径校验和运行账户边界的可复用模式。
|
||||
- OA 主机上的 timer 无法在整机宕机、主机断网或 timer 自身停止时发出告警;异机 ARM 探测可覆盖这些故障,但属于外部运行配置,实际安装与启用需要另行明确批准。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:以公网 HTTPS `/ready` 为主要可用性信号,校验 HTTP 状态与响应中的 `data.ok=true`,不得记录完整响应或敏感 payload。
|
||||
- R2:探测失败时生成有限、稳定、脱敏的故障类别,至少区分连接/TLS/HTTP/响应契约异常;不得把域名解析结果、地址、凭证、绝对路径或原始日志发送到群内。
|
||||
- R3:相同故障连续发生时只发送一次告警;故障类别变化时允许重新告警;恢复后发送一次恢复通知。
|
||||
- R4:使用 systemd oneshot + timer 运行,提供安全的安装、启用、状态检查和卸载路径;探测进程不得写 OA 数据库或企业微信业务数据。
|
||||
- R5:告警复用现有企微群机器人安全入口及 0600 webhook 文件合同,不新增新的消息供应商或时序数据库。
|
||||
- R6:提供无需真实 webhook 的契约测试,覆盖健康、连续相同故障降噪、故障变化、恢复通知、异常状态文件和隐私输出。
|
||||
- R7:实际公网安装、systemd 变更、真实企微消息发送和远端配置不属于规划批准;执行前必须再次取得明确批准。
|
||||
- R8:首版探测固定运行在现有 ARM 异机侧,每 5 分钟执行一次;连续两次失败才发送故障告警,一次成功即恢复并在此前确有已发送故障时发送一次恢复通知。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] AC1:定时探测可对健康公网 `/ready` 返回成功,并保留不含敏感信息的本地状态。
|
||||
- [x] AC2:连接失败、TLS 校验失败、非 2xx/503、非法 JSON 或 `data.ok!=true` 均进入稳定的脱敏失败类别并使 oneshot 非零退出。
|
||||
- [x] AC3:同一失败重复执行不会重复发送;失败类别变化会发送新告警;恢复后只发送一次恢复通知。
|
||||
- [ ] AC4:ARM timer 每 5 分钟执行,单次探测有明确连接/总超时;持续故障在约 10 分钟内告警;安装后可用只读命令核对 enabled/active 及最近执行结果。
|
||||
- [x] AC5:测试模式不访问真实企微,自动验证覆盖告警与恢复状态机、输入文件权限和消息脱敏。
|
||||
- [x] AC6:现有备份告警、WARCH3 巡检、部署脚本和 `/ready` 合同不被破坏。
|
||||
- [x] AC7:规划阶段不修改生产配置;若后续获批部署,公网独立探测和真实告警演练通过后才宣告监控可用。
|
||||
|
||||
本地实现已完成;AC4 保持未勾选并标记为 `EXTERNAL_GATED`,因为本任务未获 ARM 写入、timer 启用、真实企微消息或故障演练批准。仓库契约通过不代表监控已在运行。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 部署 Prometheus、Alertmanager、Grafana 或通用可观测性平台。
|
||||
- 业务登录、审批、报销等合成事务监控;首版只判断基础可用性。
|
||||
- 自动重启、自动回滚、数据库修复或流量切换。
|
||||
- TLS 证书提前到期提醒;首版通过标准 HTTPS 校验发现已经无效或不受信任的证书,提前到期预警留给后续独立评估。
|
||||
- 收集或长期存储访问日志、响应 payload、主机地址及其他敏感运行数据。
|
||||
- 本任务不处理组织架构、真实账号导入、发票图片附件或 OCR。
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- 探测运行在现有 ARM 异机侧,而非 OA 主机,以覆盖 OA 整机、主机网络和本机 timer 同时失效。
|
||||
- 每 5 分钟探测,连续两次失败确认持续故障;恢复只需一次成功。
|
||||
- 首版复用现有企微群机器人告警入口,不部署 Prometheus,也不执行自动恢复。
|
||||
- 应用健康安装器独立于备份 receiver 安装器,避免卸载或升级一个能力时意外影响另一个能力。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- 静态检查 Python 与 shell 语法。
|
||||
- 运行新增应用健康契约测试,覆盖健康、首次失败、持续失败、类别变化、重复降噪、恢复、异常状态和隐私边界。
|
||||
- 复跑自动备份契约,证明共用企微 helper 行为未回归。
|
||||
- 运行部署 env contract、流程基线、文档审计、Trellis 治理、task validate 与 `git diff --check`。
|
||||
- 远端阶段另获批准后,先安装 disabled units,再手工健康探测,最后执行一次受控失败/恢复演练并核对 timer 状态。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "app-health-monitoring",
|
||||
"name": "app-health-monitoring",
|
||||
"title": "应用健康监控",
|
||||
"description": "在现有 ARM 异机侧持续探测 OA 公网就绪状态,并以脱敏、降噪、可恢复通知的企微告警及时发现服务中断。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": "ARM off-host public readiness probe, deduplicated WeCom alerting, systemd timer installer, contract tests, and existing runbook updates",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-07-27",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户