实现应用健康监控(07-27-app-health-monitoring)

这个提交包含在:
2026-07-27 22:30:33 +08:00
父节点 99a6c20981
当前提交 bb5113c58b
共修改 15 个文件,包含 1111 行新增和 0 行删除
+8
查看文件
@@ -57,6 +57,14 @@
- [ ] **EXTERNAL_GATED**:已真正停止一次源端 timer 或故意破坏一次传输,ARM dead-man 仅产生一条脱敏企微告警;恢复 finalize 后收到一次恢复通知,同一失败重复运行已降噪
- [ ] 若调整过 `BACKUP_RUN_AT`,已人工确认调整后的次日 UTC 分桶和 26 小时窗口,未把一次预期空窗误判为长期故障
- [ ] 本季度已由第二名内部人员按 runbook 首部恢复卡在 ARM 隔离目标完成影子恢复走查;timer 未自动恢复生产
- [ ] ARM 公网应用健康监控(未完成远端门禁前保持未勾选):
- [ ] 本地 `bash scripts/qa/run-app-health-monitor-contract.sh` 通过,且自动备份告警契约回归通过
- [ ] **EXTERNAL_GATED**:已批准 ARM systemd 写入,并确认复用 root-owned `0600` webhook;安装的是已通过自动备份回归的共用 helper 修订,不复制或输出 webhook 正文
- [ ] `install-offhost-app-health-monitor.sh install` 只安装 disabled units;应用健康 state/alert state 与备份 dead-man 完全独立
- [ ] 手工 oneshot 健康执行通过,输出不含地址、路径、响应 payload、凭证或原始异常
- [ ] **EXTERNAL_GATED**:另获批准后执行 `enable`,timer 为 enabled/active,oneshot service 平时 inactive,下一次运行不超过 5 分钟
- [ ] **EXTERNAL_GATED**:持续失败演练中第一次仅 pending、第二次只发一条、重复失败降噪、恢复只通知一次;未用“机器人能发消息”替代故障链路
- [ ] 已记录剩余风险:ARM 自身失效会使探测失声,首版没有第三方 dead-man
- [ ] 停机复制方案已冻结:旧 PostgreSQL 完全停止后复制到空新目录并保留所有权/权限/链接/扩展属性;旧目录原位保留且观察期内不再写入
## B. 发布(Deploy)
+158
查看文件
@@ -0,0 +1,158 @@
#!/usr/bin/env bash
set -euo pipefail
READY_URL="${READY_URL:-https://oa.hyinda.com/ready}"
PROBE_SOURCE="${PROBE_SOURCE:-}"
PROBE_TARGET="${PROBE_TARGET:-/usr/local/sbin/hyb-oa-public-app-health}"
ALERT_WEBHOOK_FILE="${ALERT_WEBHOOK_FILE:-}"
ALERT_HELPER_SOURCE="${ALERT_HELPER_SOURCE:-}"
ALERT_HELPER_TARGET="${ALERT_HELPER_TARGET:-/usr/local/sbin/hyb-oa-wecom-group-alert}"
STATE_DIR="${STATE_DIR:-/var/lib/hyb-oa-app-health}"
FAILURE_THRESHOLD="${FAILURE_THRESHOLD:-2}"
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-10}"
SERVICE_NAME="${SERVICE_NAME:-hyb-oa-public-app-health}"
SYSTEMD_DIR="${SYSTEMD_DIR:-/etc/systemd/system}"
MODE="${1:-install}"
fail() {
echo "[app-health-installer][ERROR] $1" >&2
exit 1
}
need_root() {
[[ "$(id -u)" == "0" ]] || fail "root privileges are required"
}
validate_absolute_path() {
local name="$1"
local value="$2"
if [[ -z "$value" || "$value" != /* || ! "$value" =~ ^/[A-Za-z0-9._/-]+$ ]]; then
fail "$name must be an explicit safe absolute path"
fi
if [[ "$value" == "/" || "$value" == */ || "$value" == *"//"* \
|| "/$value/" == *"/../"* || "/$value/" == *"/./"* ]]; then
fail "$name contains an unsafe path segment"
fi
}
validate_managed_targets() {
[[ "$PROBE_TARGET" == "/usr/local/sbin/hyb-oa-public-app-health" ]] \
|| fail "PROBE_TARGET must remain /usr/local/sbin/hyb-oa-public-app-health"
[[ "$ALERT_HELPER_TARGET" == "/usr/local/sbin/hyb-oa-wecom-group-alert" ]] \
|| fail "ALERT_HELPER_TARGET must remain /usr/local/sbin/hyb-oa-wecom-group-alert"
[[ "$STATE_DIR" == "/var/lib/hyb-oa-app-health" ]] \
|| fail "STATE_DIR must remain /var/lib/hyb-oa-app-health"
[[ "$SYSTEMD_DIR" == "/etc/systemd/system" ]] \
|| fail "SYSTEMD_DIR must remain /etc/systemd/system"
[[ "$SERVICE_NAME" == "hyb-oa-public-app-health" ]] \
|| fail "SERVICE_NAME must remain hyb-oa-public-app-health"
}
validate_inputs() {
local name value
for name in PROBE_SOURCE PROBE_TARGET ALERT_WEBHOOK_FILE ALERT_HELPER_SOURCE ALERT_HELPER_TARGET STATE_DIR SYSTEMD_DIR; do
value="${!name:-}"
validate_absolute_path "$name" "$value"
done
validate_managed_targets
[[ "$READY_URL" =~ ^https://[A-Za-z0-9.-]+(:([0-9]{1,5}))?/ready$ ]] \
|| fail "READY_URL must be an HTTPS /ready endpoint without query or fragment"
if [[ -n "${BASH_REMATCH[2]:-}" && ("${BASH_REMATCH[2]}" -lt 1 || "${BASH_REMATCH[2]}" -gt 65535) ]]; then
fail "READY_URL port is invalid"
fi
[[ "$FAILURE_THRESHOLD" == "2" ]] || fail "FAILURE_THRESHOLD must remain 2"
[[ "$TIMEOUT_SECONDS" =~ ^([1-9]|[12][0-9]|30)$ ]] || fail "TIMEOUT_SECONDS must be between 1 and 30"
[[ -f "$PROBE_SOURCE" && ! -L "$PROBE_SOURCE" ]] || fail "probe source is missing"
[[ -f "$ALERT_HELPER_SOURCE" && ! -L "$ALERT_HELPER_SOURCE" ]] || fail "alert helper source is missing"
[[ -f "$ALERT_WEBHOOK_FILE" && ! -L "$ALERT_WEBHOOK_FILE" \
&& "$(stat -c %a "$ALERT_WEBHOOK_FILE")" == "600" \
&& "$(stat -c %u "$ALERT_WEBHOOK_FILE")" == "0" ]] \
|| fail "alert webhook file must be root-owned with mode 0600"
[[ -d "$SYSTEMD_DIR" && ! -L "$SYSTEMD_DIR" ]] || fail "systemd directory is invalid"
command -v python3 >/dev/null 2>&1 || fail "python3 is required"
command -v systemctl >/dev/null 2>&1 || fail "systemctl is required"
}
install_monitor() {
need_root
validate_inputs
systemctl disable --now "${SERVICE_NAME}.timer" >/dev/null 2>&1 || true
systemctl stop "${SERVICE_NAME}.service" >/dev/null 2>&1 || true
install -o root -g root -m 0755 "$PROBE_SOURCE" "$PROBE_TARGET"
install -o root -g root -m 0755 "$ALERT_HELPER_SOURCE" "$ALERT_HELPER_TARGET"
mkdir -p "$STATE_DIR"
[[ -d "$STATE_DIR" && ! -L "$STATE_DIR" ]] || fail "state directory is invalid"
chown root:root "$STATE_DIR"
chmod 0700 "$STATE_DIR"
cat >"$SYSTEMD_DIR/${SERVICE_NAME}.service" <<SERVICE
[Unit]
Description=hyb-oa independent public application readiness probe
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
UMask=0077
ExecStart=/usr/bin/python3 ${PROBE_TARGET} --ready-url ${READY_URL} --state-dir ${STATE_DIR} --webhook-file ${ALERT_WEBHOOK_FILE} --alert-helper ${ALERT_HELPER_TARGET} --failure-threshold ${FAILURE_THRESHOLD} --timeout-seconds ${TIMEOUT_SECONDS}
TimeoutStartSec=30
SERVICE
cat >"$SYSTEMD_DIR/${SERVICE_NAME}.timer" <<TIMER
[Unit]
Description=Check hyb-oa public application readiness every five minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
AccuracySec=10s
Unit=${SERVICE_NAME}.service
[Install]
WantedBy=timers.target
TIMER
chmod 0644 "$SYSTEMD_DIR/${SERVICE_NAME}.service" "$SYSTEMD_DIR/${SERVICE_NAME}.timer"
systemctl daemon-reload
echo "[app-health-installer][PASS] disabled application health monitor installed"
}
enable_monitor() {
install_monitor
systemctl enable --now "${SERVICE_NAME}.timer"
echo "[app-health-installer][PASS] application health timer enabled"
}
uninstall_monitor() {
need_root
validate_absolute_path PROBE_TARGET "$PROBE_TARGET"
validate_absolute_path SYSTEMD_DIR "$SYSTEMD_DIR"
[[ "$PROBE_TARGET" == "/usr/local/sbin/hyb-oa-public-app-health" ]] \
|| fail "PROBE_TARGET must remain /usr/local/sbin/hyb-oa-public-app-health"
[[ "$SYSTEMD_DIR" == "/etc/systemd/system" ]] \
|| fail "SYSTEMD_DIR must remain /etc/systemd/system"
[[ "$SERVICE_NAME" == "hyb-oa-public-app-health" ]] \
|| fail "SERVICE_NAME must remain hyb-oa-public-app-health"
systemctl disable --now "${SERVICE_NAME}.timer" >/dev/null 2>&1 || true
systemctl stop "${SERVICE_NAME}.service" >/dev/null 2>&1 || true
rm -f -- "$SYSTEMD_DIR/${SERVICE_NAME}.timer" "$SYSTEMD_DIR/${SERVICE_NAME}.service" "$PROBE_TARGET"
systemctl daemon-reload
echo "[app-health-installer][PASS] monitor removed; state and shared alert files retained"
}
case "$MODE" in
install)
install_monitor
;;
enable)
enable_monitor
;;
uninstall|--uninstall)
uninstall_monitor
;;
--help|-h)
echo "Usage: <explicit probe/webhook/helper paths> bash install-offhost-app-health-monitor.sh [install|enable|uninstall]"
;;
*)
fail "unsupported mode"
;;
esac
+31
查看文件
@@ -340,6 +340,37 @@ RESTORE_SCOPE=shadow \
- 确认完整批次已经复制到异机/异盘,而不是仍只保存在应用主机。
- 准备独立 smoke 账号,避免使用老板、管理员或真实员工账号跑自动化。
## 1.6 ARM 公网应用健康监控(启用仍为 EXTERNAL_GATED)
应用健康监控与自动备份 dead-man 共用受控企微群机器人入口,但使用独立探测器、systemd units、probe state 和 alert state。它从 ARM 每 5 分钟访问公网 HTTPS `/ready`;连续两次失败才告警,一次成功即恢复。不得把它并入备份 receiver 的安装/卸载生命周期,也不得以本地测试通过代替 ARM 已启用证据。
先在仓库运行无真实消息合同:
```bash
bash scripts/qa/run-app-health-monitor-contract.sh
```
获得 ARM systemd 写入批准后,使用受控配置中的绝对路径安装 disabled units。命令中的 webhook 正文不得作为参数、环境变量或聊天内容出现:
```bash
PROBE_SOURCE=<repo-app-health-probe> \
ALERT_WEBHOOK_FILE=<controlled-root-only-webhook-file> \
ALERT_HELPER_SOURCE=<repo-group-alert-helper> \
ALERT_HELPER_TARGET=<installed-shared-alert-helper> \
bash deploy/trialrun/install-offhost-app-health-monitor.sh install
```
安装后先人工执行一次 oneshot 并核对只含稳定状态/类别的输出;另获真实消息与 timer 启用批准后再执行同参数的 `enable`。检查 enabled/active、最近结果和下一次运行:
```bash
systemctl is-enabled hyb-oa-public-app-health.timer
systemctl is-active hyb-oa-public-app-health.timer
systemctl list-timers hyb-oa-public-app-health.timer --no-pager
systemctl status hyb-oa-public-app-health.service --no-pager
```
真实验收需受控制造持续失败,确认首次失败不发、第二次失败只发一条、相同失败降噪、恢复只通知一次。只测试机器人能发送不算健康监控通过。卸载前先记录脱敏状态,随后执行安装器 `uninstall`;该操作只删除应用健康 units 和探测器,保留状态、共用 helper、webhook、备份 receiver、dead-man 和归档。
## 2. 升级流程(试运行)
建议版本化方式:
- 每次发布打 tag,例如 `trialrun-2026-02-08-01`