可观测性 SSOT (采集 · 告警 · 报告)¶
SSOT Key:
ops.obs核心定义: infra2 可观测性的唯一 owner——遥测采集(logs/metrics/traces)、告警(规则/分级/飞书路由)、 报告/可用率账本(正向证明),以及把这三者统一起来的时间尺度分层模型。收敛自原 SSOT key
ops.alerting+ops.availability_ledger(已并入本文)。watchdog-signals.yaml作为信号数据 registry 保留;遥测标识(env identity)归 core.environments.md。第一性原理(本文的脊梁):告警 = 事件驱动(真出事才发);报告 = 时间驱动(周期发)。 二者绝不混——尤其不能周期性地发告警。详见 §2。
1. 真理来源 (The Source)¶
| 维度 | 物理位置 (SSOT) | 说明 |
|---|---|---|
| 采集 - 存储 | platform/03.clickhouse | ClickHouse + ZooKeeper |
| 采集 - 应用 | platform/11.signoz | Query Service + Frontend + OTLP Collector |
| 告警 - 规则 | SigNoz Alert Manager + finance_report/finance_report/observability/alert_rules.json(config-as-code) |
告警规则 |
| 告警 - 通知 | platform/12.alerting | SigNoz webhook → Feishu custom bot / app bot bridge;in-band probe runner |
| 告警 - 密钥源头 | 1Password platform/{env}/alerting → 运行时镜像 Vault secret/platform/{env}/alerting |
Feishu 凭据 + 可选 bridge basic auth |
| 带外 watchdog | cloudflare/infra-watchdog(主,边缘 30min)+ .github/workflows/ops-checks.yml(GitHub 兜底,日级) |
整机/整栈失联检测 |
| 信号清单 | watchdog-signals.yaml |
按信号(非组件)追踪 watchdog 归属 |
| 报告 - 账本 | Cloudflare KV(热 21 天)+ R2(冷长期)·libs/availability_ledger.py(聚合)·tools/stability_report.py(周报) |
正向证明 |
| 部署指南 | Infra-007 | SigNoz 安装 |
In-band 告警路径恒为:component/app → OTLP Collector → SigNoz → platform/12.alerting → Feishu/Lark。
带外检测独立于 VPS(SigNoz 与 bridge 都在单台机器上,会和宿主一起挂),故走 Cloudflare 边缘 cron 直发 Feishu。
2. 信号模型与时间尺度分层 (Signal model & cadence tiers)¶
统一框架。立论与 MECE 论证见 issue #425;本节是其 SSOT 落地。
不变式:
- ALERT(事件驱动)——只在真故障时发;cadence = f(故障时间尺度)。
- REPORT(时间驱动)——周期性汇总;cadence = 人的复盘节奏。
- 铁律:任何定时器发出的东西都是报告,绝不是告警。 推论:一份报告自身的成功送达,就是投递链路的自证——无需单独的合成告警。
分层(cadence = 1 / 故障时间尺度;每个 check 落且只落一档 = 它"在造成伤害前还能抓住"的最粗 cadence):
| 尺度 | 性质 | 干什么 | 为什么这个频率 |
|---|---|---|---|
| 分钟级 | 告警 | 真 liveness / 写路径 / 公网 5xx / Vault sealed —— 分钟内伤用户的 | 用户面故障第 1 分钟就疼 → 分钟级抓 |
| 小时级 | 告警 + 带外兜底 | 慢失效:证书/token 临期(TTL 6/24h)、备份新鲜度、路由创建能力;整机失联(Cloudflare 边缘 30min) | 小时尺度发展;整机挂也快不过人响应 |
| 天级 | 报告 | 健康日报(探针绿/红、今日 fire/resolve、备份新鲜度、drift/未发布增量)——其送达即投递自证;deploy-v2 canary | 投递配置/drift 在天尺度变;人天级复盘 |
| 月级 | 报告/演练 | DR 全量恢复演练(不可逆数据兜底)、凭据轮换审计、容量趋势、SLA 月度 rollup | 重、且守的东西变得慢,但必须真跑 |
横切不变式:≤小时 = 告警,≥天 = 报告;告警/报告的分界线就是"天"。
3. 告警分级 (Severity)¶
| 等级 | 颜色 | 响应时效 | 定义 |
|---|---|---|---|
| P0 (Critical) | 🔴 Red | 立即 (24x7) | 核心服务不可用 (Vault, SSO, DB Down) |
| P1 (Error) | 🟠 Orange | 30分钟 | 部分功能受损,核心链路仍通 |
| P2 (Warning) | 🟡 Yellow | 工作日 | 资源使用率高,非关键错误 |
4. 采集 (Collection / OTLP)¶
4.1 架构与数据流¶
graph LR
Apps[Applications] -->|OTLP| Collector[OTLP Collector]
Collector -->|Export| ClickHouse[(ClickHouse)]
QueryService[Query Service] -->|Query| ClickHouse
Frontend[Web UI] -->|API| QueryService
QueryService -->|Alert webhook| Alerting[Feishu Alert Bridge]
Alerting -->|Text message| Feishu[Feishu Group]
| 组件 | 位置 | 端口 | 用途 |
|---|---|---|---|
| ClickHouse | platform/03.clickhouse | 9000, 8123 (内部) | 时序数据存储 |
| ZooKeeper | platform/03.clickhouse | 2181 (内部) | 集群协调 |
| OTLP Collector | platform/11.signoz | 4317, 4318(内部) | 数据采集 |
| Query Service | platform/11.signoz | 8080 (内部) | 查询引擎 |
| Frontend | platform/11.signoz | 3301 (Traefik) | Web 界面 |
| Alert Bridge | platform/12.alerting | 8080 (内部) | SigNoz 告警转飞书 |
数据流:应用 OTLP → Collector(4317/4318, Docker 网络内)→ ClickHouse → Query Service → Frontend
(https://signoz${ENV_DOMAIN_SUFFIX}.${INTERNAL_DOMAIN});告警 SigNoz Alertmanager webhook → platform-alerting${ENV_SUFFIX} → Feishu。
采集设计约束:OTLP SDK 埋点 · 结构化(JSON)日志 · 发送前脱敏(密码/Token/PII)· 统一 OTLP 协议。 禁止:日志/trace 输出原始敏感信息 · 私有协议 · 绕过 Collector 直写 ClickHouse。
4.2 应用接入 OTLP¶
前置:SigNoz 已部署健康;应用在 dokploy-network;端点 platform-signoz-otel-collector:4317(gRPC)/:4318(HTTP),仅 Docker 网络内、不对外暴露。
单一全局实例:SigNoz 是
prod_only单实例。preview/staging/production 全部打到这个无后缀 collector,靠deployment.environment.name区分环境(无 per-env collector)。标识规则见 core.environments.md。
| 变量 | 说明 | 示例 |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP HTTP 端点(所有环境无后缀) | http://platform-signoz-otel-collector:4318 |
OTEL_SERVICE_NAME |
服务名 | finance-report-backend |
OTEL_RESOURCE_ATTRIBUTES |
ServiceIdentity 渲染的完整资源身份 |
deployment.environment.name=production,infra.service.id=finance_report/app,service.version=<version>,infra.iac.ref=<sha> |
表层别名与底层 commit 由 infra2 部署时签发,应用只消费、对缺失 fast-fail。
4.3 finance_report 接入(BE + 浏览器 FE,Infra-014)¶
后端(Docker 网络内 OTLP HTTP)由 10.app/secrets.ctmpl / preview/secrets.ctmpl 按环境渲染
OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_SERVICE_NAME=finance-report-backend / 由部署入口签发的 OTEL_RESOURCE_ATTRIBUTES。Vault template 只转交进程环境;不得从 Vault secret 覆盖服务、环境、版本或 IaC 身份。迁移期 payload 同时含 deployment.environment.name=<alias> 与旧 deployment.environment=<alias>。
浏览器前端走唯一公网 ingest otel.${INTERNAL_DOMAIN}(§4.4),运行时(非 build-time)env 注入
NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.${INTERNAL_DOMAIN}/v1/traces、NEXT_PUBLIC_DEPLOYMENT_ENVIRONMENT=${ENV}、NEXT_PUBLIC_GIT_SHA=${GIT_COMMIT_SHA}(promote-not-rebuild:同一镜像跨环境提升保持环境无关)。
4.4 公网浏览器 OTLP ingest:otel.${INTERNAL_DOMAIN}(Infra-014)¶
collector 4317/4318 仅 expose 于 Docker 网络、永不 publish。唯一公网面是 Dokploy 托管域名 otel.${INTERNAL_DOMAIN} → :4318(SigNozDeployer.composing() 通过 ensure_domains(..., service_name="otel-collector") 注册,无手写 Traefik 标签)。
没有 bearer token:浏览器无法保管秘密,下发到页面的静态 token 不是凭据。决策记录:
| 方案 | 想法 | 为何否决 |
|---|---|---|
| A. 静态 bearer(#360 初版) | 给公网 ingest 加"凭据"门槛 | 浏览器无法保密;token 进 JS 即被 DevTools 拿到 → 假凭据、只是障眼法 |
| B. CORS 门控 + collector 限额(现状) | 承认公网 ingest 本质不可鉴权,约束滥用而非鉴权 | 选中 |
⚠️ CORS 不是鉴权:它只约束浏览器跨域,挡不住 curl/脚本直接 POST。这是有意为之的未鉴权公网 ingest,靠 collector
memory_limiter限额 + 边缘按 IP 限流(TODO,须 Dokploy 托管 Traefik ratelimit,禁手写标签)兜底。CORS 允许列表在otel-collector-config.yaml,须与 FE 域名同步。otel.${INTERNAL_DOMAIN}在泛域名内,无需新增 DNS。
4.5 查询 + synthetic round-trip(分钟级,采集自证)¶
- 查询(勿重造):SigNoz
invoke signoz.shared.query-logs/list-services(key 在 Vaultsecret/platform/<env>/signoz);OpenPanel 查询 CLI 在 app 仓库common/observability/openpanel_query.py(本仓库只引用)。 - synthetic round-trip(
infra-probe-runner,写读探针节流): signoz-roundtrip:每 5min 写一条 OTLP log,再从signoz_logs.distributed_logs_v2按 nonce 查回 → 证 collector→ClickHouse ingest/storage 可用。openpanel-roundtrip:每 5min 向 OpenPanel/track写,再从openpanel.events查回 → 证 API→worker/storage 可用。- 窗口由
OBS_ROUNDTRIP_INTERVAL_SECONDS/OBS_ROUNDTRIP_QUERY_WAIT_SECONDS控制;失败作InfraServiceProbeFailed进 bridge。
4.6 finance_report 告警/仪表盘 config-as-code(#373)¶
定义签入 finance_report/finance_report/observability/(alert_rules.json 含 FinanceReportBackendErrorLogs + RED/business 规则;dashboard.json;shared_tasks.py),不在 UI 手点;声明式 apply 见 SOP-004B/C 与 ops.pipeline.md(apply 折进 tag reconcile 的目标态)。
5. 告警覆盖目录 (Alert Coverage Catalog)¶
层级编号沿用 core.md#层级定义(L1 Bootstrap / L2 Platform),
L3为应用层。
| Layer | Component | Signal | Severity | Status |
|---|---|---|---|---|
| L1 Bootstrap | 1Password Connect | /health not active or sync not active |
P0 | Live (op-connect-http) |
| L1 Bootstrap | Vault | sealed / unreachable / token validation fails | P0 | Live probe + vault audit |
| L1 Bootstrap | IaC Runner | /health fails before deploy webhook |
P1 | Live (iac-runner-http) |
| L1 Bootstrap | Dokploy | deployment control-plane API/UI unreachable or deploy webhooks fail; app health alerts remain app-owned | P1 | Live probe |
| Cross-cutting | Docker container health | any container unhealthy/starting/Restarting outside a deploy window |
P0/P1 | Out-of-band watchdog SSH |
| L2 Platform | platform Postgres | TCP readiness fails / restart loop | P0 | Live probe |
| L2 Platform | platform Redis | TCP readiness fails / restart loop | P1 | Live probe |
| L2 Platform | ClickHouse | data dir unwritable / ingestion broken | P0 | Write-path healthcheck + signoz-roundtrip |
| L2 Platform | MinIO | live endpoint unavailable | P1 | Live probe |
| L2 Platform | Authentik | health endpoint fails | P0 | Live probe |
| L2 Platform | SigNoz | frontend/query fails or synthetic OTLP nonce cannot be queried back | P0 | signoz-internal-http, otel-collector-http, signoz-roundtrip |
| L2 Platform | Alert Bridge | /health fails / Feishu unreachable |
P0 | alert-bridge-http, lark-delivery-http + out-of-band bridge health |
| L2 Platform | OpenPanel API | /healthcheck fails or synthetic /track nonce not queryable |
P1 | openpanel-api-http, openpanel-roundtrip |
| L2 Platform | OpenPanel ClickHouse (op-ch) | data dir unwritable / event store broken | P1 | Write-path healthcheck + openpanel-roundtrip |
| L2 Platform | OpenPanel Worker / Dashboard | /healthcheck / /api/healthcheck fails |
P1 / P2 | Live probes |
| L2 Platform | Portal / Prefect | frontend / server-health unavailable | P2 / P1 | Planned |
| L3 Finance Report | fr-postgres / fr-redis | app db / cache health fails | P0 / P1 | Planned |
| L3 Finance Report | fr-app backend | OTEL ERROR/FATAL > 0 over 5m | P1 | code (FinanceReportBackendErrorLogs) |
| L3 Finance Report | fr-app backend | RED SLO: 5xx > 5% 5m / p95 > 1500ms | P0/P1 | code (FinanceReportHigh5xxRate, FinanceReportP95LatencyHigh) |
| L3 Finance Report | fr-app backend | business anomaly: parse spike / reconciliation / rate-limit / async failure | P1/P2 | code (FinanceReport{StatementParseFailureSpike,ReconciliationAnomaly,RateLimitSaturation,AsyncTaskFailures}) |
| L3 Finance Report | fr-app public route | report[-staging].zitian.party/ (web) or /api/health from Cloudflare |
P0 prod / P1 staging | Cloudflare out-of-band watchdog |
| Cross-cutting | Vault app tokens / rendered env | missing / malformed / invalid / low-TTL / <no value> |
P0/P1 | Docker healthcheck + vault-audit.self-refresh |
| Cross-cutting | Backup freshness | latest off-host backup missing/stale/empty/no-checksum | P1 | backup manifest verifier |
| Cross-cutting | Infra2 host reachability / probe heartbeat | public endpoints fail / probe runner stops heartbeat | P0/P1 | Cloudflare out-of-band watchdog |
| Cross-cutting | SSH host diagnostics | external SSH bridge health fails | P0 | GitHub fallback watchdog |
| Cross-cutting | Deploy queue | 部署卡在 running 超过 ceiling(默认 30min;单并发 FIFO 会阻塞所有后续部署) |
P0 | Live (DeployQueueStuck,probe-runner 常驻进程内的 ResidentWatcher 插件 libs/deploy_queue_guard.py,#543 单 sidecar 合并;观测默认开,DEPLOY_GUARD_REMEDIATE=1 才 opt-in 走 Dokploy API kill/clean + 复查升级,绝不直接动 Redis/BullMQ) |
设计约束:告警含 actionable runbook 链接 · 聚合避免风暴 · Feishu 凭据只在 1Password(Vault 仅运行时镜像)· SigNoz webhook 只指向内部 bridge URL。 禁止:为瞬时波动指标设 P0 · 忽略 Critical · SigNoz webhook 直指飞书自定义机器人。
6. 报告与可用率账本 (Reporting & Availability Ledger)¶
故障流告警证明不了"它一直是好的"。账本是闭环的正向一半:成功也记,且绝不能把降级信号报成健康。
- 为何外置(KV/R2 而非 SigNoz):SigNoz 与 bridge 都在单台 VPS,度量不了自己宿主的可用率。账本必须活在比被测对象更可靠的层(Cloudflare)。
- 记账:
worker.jsrecordLedger每次 cron 把各信号 ok/fail 累加进当日一个聚合 rollup(绝不一信号一键,否则击穿 KV 免费写配额→静默假死);跨天结算的昨日写入 R2(S3 标准、静态凭据、与备份同后端、Worker 原生 binding,无需第二套同步)。 - 热 21 天 KV
ledger:YYYY-MM-DD供/ledger//status/周报;冷长期 R2watchdog-ledger/YYYY-MM-DD.json。 - 聚合/算 uptime 只在
libs/availability_ledger.py(纯函数,CLI 与测试共用);R2/KV 缺失时安全降级 no-op,部署不挂。 - 周报:
tools/stability_report.py(弱 CLI)读/ledger→ Lark 正向证明,需INFRA2_WATCHDOG_LEDGER_URL。 - 禁止:per-signal-per-run 建 KV 键 · 把
fail>0计入 100%/perfect · 信任畸形 day/signal 抬高可用率。
天级日报(目标态,#425 T3):统一健康日报(探针绿/红、今日 fire/resolve、备份新鲜度、drift)发 Feishu,其送达即投递自证;"投递真断了"的硬信号留给独立带外 watchdog。6h 合成
alert-delivery-canary已退役(它把投递自证做成了周期性告警);当前 bridge→Feishu 路径由lark-delivery-http(配置有效 + Feishu 可达,不真发)、带外 watchdog 的 bridge/health、日报自身投递、以及真实告警共同覆盖。
7. 标准操作程序 (Playbooks)¶
SOP-001: 响应 P0 告警¶
确认影响范围 → 基础设施故障参考 Recovery SSOT → 状态页更新 Incident。
SOP-002: 接入飞书自定义机器人通道¶
- 飞书群建自定义机器人,复制 webhook URL。
- 写 1Password root vars + setup-approle:
uv run invoke env.set FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/<token> --project=platform --env=production --service=alerting --credential-type=root_vars uv run invoke vault.setup-approle --project=platform --service=alerting - 部署 bridge:
uv run python -m tools.deploy_v2 --service platform/alerting --type prod --iac-ref vX.Y.Z --domain zitian.party --code-reviewed→invoke alerting.status。 - 建 SigNoz channel:
invoke signoz.shared.create-api-key→invoke alerting.create-signoz-channel。 - 测试:
invoke alerting.test-feishu --message="Infra2 alert test"。
SOP-003: 接入飞书 App Bot 通道¶
开放平台启用机器人 + 发布 im:message 权限 + 拿 chat_id,写 1Password root vars(ALERT_DELIVERY_MODE=feishu_app、FEISHU_APP_ID、FEISHU_APP_SECRET、FEISHU_CHAT_ID)→ setup-approle → deploy_v2 → alerting.test-feishu。
SOP-004 / SOP-004B / SOP-004C: 应用 OTEL 错误告警 + finance_report 告警目录 config-as-code(#373 / #1106)¶
- bridge 健康 + SigNoz API key + Feishu channel(SOP-002/004 步骤)。
- 应用定义(幂等):
uv run python -m invoke fr-observability.shared.apply-alerts uv run python -m invoke fr-observability.shared.apply-dashboard uv run python -m invoke fr-observability.shared.print-alerts # 离线看 payload - #1106 SLO/business 目录:
FinanceReportHigh5xxRate(5xx>5% 5m,P0)、FinanceReportP95LatencyHigh(p95>1500ms,P1)、FinanceReportStatementParseFailureSpike、FinanceReportReconciliationAnomaly、FinanceReportRateLimitSaturation(P2)、FinanceReportAsyncTaskFailures。须渲染为 SigNoz v5 PromQL(alertType=METRIC_BASED_ALERT,ruleType=promql_rule,condition.compositeQuery.queries[]);SigNoz 拒任一规则即 fail 整个 apply(部分 apply 不算成功 GitOps)。 - 先跑 schema canary:
gh workflow run apply-observability.yml --ref <ref> -f mode=canary(建一条 disabled PromQL 规则验 v5 信封再删)。apply 应在 app 发完所有引用 metric 名后。
注:
apply_alerts现为声明式 reconcile(upsert + 默认只 log 的 prune),见 ops.pipeline.md。
SOP-005: Cloudflare 带外 watchdog(主,边缘 30min)¶
活在 cloudflare/infra-watchdog,直发 Feishu(不经它要验证的 bridge)。归属按信号记于 watchdog-signals.yaml。默认覆盖:prod 公网路由 cloud/vault/minio/sso/signoz + report web/api;staging 选定路由;prod/staging probe-runner heartbeat 新鲜度。每个 target 携带 registry 校验的 service_id;结构化事件写 identity_schema=v1 / managed_by=infra2,dedupe keys on stable failure identity plus failure domain,具体 fingerprint 使用 (environment, service_id, signal, failure_domain)。config-preflight 失败单独报(不冒充路由故障);投递失败发 watchdog.delivery.failure 结构化事件不静默。
- secrets:webhook 模式 FEISHU_WEBHOOK_URL;app 模式 FEISHU_APP_SECRET;两者 HEARTBEAT_TOKEN、WATCHDOG_STATUS_TOKEN(源 1Password Infra2/bootstrap/cloudflare-worker)。
- KV WATCHDOG_STATE;vars WATCHDOG_ENVIRONMENTS=production,staging、WATCHDOG_RENOTIFY_SECONDS=7200 等。
- 部署:cd cloudflare/infra-watchdog && wrangler kv namespace create WATCHDOG_STATE && wrangler secret put ... && wrangler deploy;再配 probe runner heartbeat:env.set INFRA_PROBE_HEARTBEAT_URL=.../heartbeat + INFRA_PROBE_HEARTBEAT_TOKEN(prod+staging)→ deploy_v2。
SOP-005B: GitHub 兜底带外 watchdog(日级)¶
活在 GitHub Actions(在 infra2 宿主之外),日级直发 Feishu;留作 SSH 宿主诊断、Cloudflare Worker 自检、Dokploy 控制面状态消费、手动诊断。secrets:INFRA2_WATCHDOG_SSH_{HOST,USER,PRIVATE_KEY}、INFRA2_WATCHDOG_WORKER_STATUS_TOKEN、DOKPLOY_API_KEY + Feishu 投递 secrets。默认查:公网 Dokploy 入口、Worker /health+/status、SSH 可达、Docker daemon、platform-alerting 容器内 /health。infra2-docker-health 检查强制(任何 unhealthy/starting/Restarting 容器在部署窗口外即失败),不可移除。投递异常时发 watchdog.delivery.failure + 开 GitHub fallback issue(label watchdog-alert-fallback)+ 非零退出。
已知外部极限:若 watchdog 与 Feishu/Lark 用的所有外部通道同时不可用,本仓库没有第三条独立人工通知通道(#425 月级/兜底范畴)。
SOP-006: In-band 服务探针(分钟级)¶
INFRA_PROBE_SPECS 由各服务 deploy.py Deployer 的 ProbeFacet 声明渲染(#541 单一声明点:聚合器 libs/probe_specs.py::render_probe_spec_text() 经 AlertingDeployer.compose_env_base() 注入 Dokploy env;迁移前的手写 literal 冻结为 libs/tests/fixtures/infra_probe_specs_frozen.txt,由 libs/tests/test_probe_specs_equivalence.py 做永久逐字段等价回归)。循环 INFRA_PROBE_INTERVAL_SECONDS=60(快检);通知分离:FAILURE_THRESHOLD=3、RECOVERY_THRESHOLD=2、RENOTIFY_SECONDS=1800。优先 Docker 网络目标(公网路由归 Cloudflare watchdog;error code: 1010 归类 probe-client-blocked)。spec 格式 name|kind|target|expected|severity|timeout|depends_on|service_id;kind=http/tcp/command。第八字段强制绑定 registry。depends_on 链命中失败 root → 级联抑制(环路 fail-closed,见 tools/infra_probe_runner.py)。dry-run:INFRA_PROBE_DRY_RUN=1 uv run python tools/infra_probe_runner.py --once --json。
公网路由探针(#543,反转 #209):PUBLIC_ROUTE_PROBE_SPECS 由各服务的 PublicRouteFacet 声明渲染(libs/probe_specs.py::render_public_route_spec_text,域名渲染期解析、无 $ 传输);prod_only 服务只渲染生产、非生产一律降为 warning。每个渲染出的 *-public-route 名字必须是已注册 signal(libs/tests/test_infra_probes.py 锁定)。
内部 signal 注册派生(#543):watchdog-signals.yaml 的 primary_owner: internal 条目不再手写,由 libs/watchdog_signal_entries.py 从 ProbeFacet+SignalFacet 派生(声明探针即注册 signal);watchdog_consistency_audit.py 加载时合并派生条目、拒绝手写 internal 条目,并对派生条目强制 #425 T5 tier/type/debounce 校验;跨平面条目(cloudflare/github/self/excluded)仍手写。手写时代的 39 条冻结于 libs/tests/fixtures/watchdog_internal_signals_frozen.yaml,libs/tests/test_watchdog_signal_entries.py 做永久逐字段等价回归。
常驻拓扑(#543 单 sidecar):probe-runner(platform-alerting-probes)是仓库唯一常驻进程;container-breakdown watch 与 deploy-queue guard 以 ResidentWatcher 插件(libs/resident_watchers.py)在其循环内自节奏运行,故障隔离、由 runner 的 healthcheck/heartbeat 兜底(挂起 watcher → state file 过期 → compose healthcheck 重启)。新增常驻能力 = 新增 watcher 插件,不新建 sidecar。
✅
alert-delivery-canary已退役(#425 T3):它把"投递自证"做成了 6h 周期性告警(报告当告警),是 #425 禁止的反模式。bridge→Feishu 路径现由lark-delivery-http+ 带外 watchdog 的 bridge/health+ 日报投递 + 真实告警覆盖,告警频道不再被合成事件刷屏。
SOP-007: Dokploy route canary(已退役,#543)¶
✅ 已退役:每小时部署合成 compose 的 route canary(
tools/dokploy_route_canary.py+libs/dokploy_route_canary.py,约 1000 行)整体删除,不设观察期。其原有覆盖由更便宜的常驻机制承接:公网路由可达性 →PublicRouteFacet声明渲染进 probe runner(SOP-006)+ Cloudflare watchdog;Dokploy 控制面/部署状态 → 带外 watchdog 的run_dokploy_status_check(缺DOKPLOY_API_KEYfail-closed 归类configuration,签名由 canary 移交);部署卡死 → deploy-queue guard(常驻 sidecar 插件)。真实 preview 路由回归由 app PR preview 流程自身承担。
SOP-007B: deploy_v2 Canary(日级/变更触发)¶
tools/deploy_v2_canary.py 只使用保留的 pr-999 预览位,健康检查后必须清理 stack 与临时 DB。
成功保持静默,仅在 GitHub summary 输出 infra2-sdk v1.0.0 StageResult;非 PR 失败才经带外
Feishu page,且告警携带同一结构化记录。不得通过破坏 production 数据或恢复周期性
alert-delivery-canary 来制造失败;Feishu 正向投递仍由日报送达自证。
SOP-008: 账本冷归档 + 周报¶
- R2:确认桶
infra2+wrangler.toml[[r2_buckets]] binding=LEDGER_BUCKET→wrangler deploy;跨天后 R2watchdog-ledger/出现昨日 JSON。 - 周报:
ops-checks.yml(周一 UTC)跑stability_report.py读/ledger→ Lark;本地INFRA2_STABILITY_REPORT_DRY_RUN=1 python tools/stability_report.py --input ledger.json。
8. 部署与容量¶
部署顺序:存储(clickhouse)→ 应用(signoz)→ 告警(alerting),各 deploy_v2 --service ... --type prod --iac-ref vX.Y.Z --code-reviewed,invoke {clickhouse,signoz,alerting}.status 验证。容量:ClickHouse 磁盘 100GB+ / 内存 8GB+ / Collector 1GB(memory_limiter)。alert bridge 启动等 /secrets/.env 最多 300s,但不得要求 vault-agent sidecar 在渲染后保持 Docker-healthy(stale-secret 是另一条服务级信号,不阻塞告警投递)。
9. 验证与测试 (The Proof)¶
| 行为 | 测试锚点 | 状态 |
|---|---|---|
| 采集:ClickHouse/SigNoz/bridge 健康 + OTLP 可用 | invoke {clickhouse,signoz,alerting}.status、signoz.shared.test-trace |
✅ |
| Feishu payload + 日志错误规则 payload | libs/tests/test_alerting.py |
✅ |
| finance_report 告警/看板 config-as-code(#373) | libs/tests/test_observability_dashboards.py |
✅ |
| Cloudflare / out-of-band / GitHub 兜底 watchdog 契约 | test_cloudflare_watchdog.py, test_out_of_band_watchdog.py |
✅ |
| In-band 服务探针 + 级联抑制 | libs/tests/test_infra_probes.py |
✅ |
| Deploy-queue guard(卡死检测纯逻辑 + sidecar 编排:env 加载、扫描失败隔离、renotify 抑制、remediate/升级序列) | libs/tests/test_deploy_queue.py, libs/tests/test_deploy_queue_guard.py |
✅ |
| 备份新鲜度告警 payload | libs/tests/test_backup_verification.py |
✅ |
| 账本聚合(正例+反例:降级绝不报 100%/perfect、畸形输入不抬高、0 检查不除零) | libs/tests/test_availability_ledger.py |
✅ |
Worker 账本 + /ledger + R2 归档 |
libs/tests/test_cloudflare_watchdog.py |
✅ |
| 周 watchdog recall digest / 周正向稳定性报告 | test_watchdog_weekly_digest.py, test_stability_report.py |
✅ |
| Env×Stage failure-domain / disagreement 契约 | libs/tests/test_pipeline_stage_contract.py |
✅ |
| synthetic round-trip | test_observability_roundtrip_probe.py |
✅ |
| IaC/runtime/telemetry/alert 身份契约 | tools/service_identity_audit.py, libs/tests/test_service_identity*.py |
✅ |
| 告警通道手动连通 | uv run invoke alerting.test-feishu |
Manual gate |
10. 故障排查¶
- ClickHouse 启动失败:
docker logs platform-clickhouse${ENV_SUFFIX};常见权限(uid=101)/磁盘 →invoke clickhouse.pre-compose。 - OTLP 未显示:
docker logs platform-signoz-otel-collector;查otel-collector-config.yamlexporter。 - Frontend 502:
docker logs platform-signoz${ENV_SUFFIX};等 query-service 健康。
Used by¶
- docs/ssot/README.md
- docs/ssot/ops.pipeline.md(交付;告警/看板 apply 折进 tag reconcile 的目标态)
- docs/ssot/watchdog-signals.yaml(信号数据 registry)
- platform/03.clickhouse/README.md · platform/11.signoz/README.md · platform/12.alerting/README.md