lili 47644881ff
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
feat(observability): Grafana 看板激活生成指标 + 三条阈值告警(阶段四·面五之上)
- observability-overview.json:业务指标占位 panel 改用面五真实指标名(gen_task_total{status=succeeded}
  / gen_duration_seconds / gen_queue_depth / gen_gate_fail_total,去掉猜错的 huijing_ 前缀与 success 标签)
  + 新增 llm_cost 成本 panel(单次均值/近1h累计,标自估值非 new-api 权威)。
- provisioning/alerting/gen-alerts.yaml:三阈值告警——生成成功率<80%(MVP门 §2)/ 队列深度>15(便宜档
  并发上限)/ game-cloud up=0(后端不可达),+ webhook contact point(飞书/钉钉,URL 待创始人填)+ 路由策略。

对着 MVP 门(成功率≥80% / 可用性≥99.5% / 便宜档并发≤15)。webhook URL 未填前告警在 Grafana UI 可见但不
外发,§7 验收#2「告警真送达」待真 webhook。compose 已挂 provisioning 全目录,重启 grafana 容器即生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:47:52 -07:00
..

可观测性栈(observability)

这套栈把五个组件起在 mini-infra:Collector 收信号,Prometheus 存指标,Tempo 存追踪,Loki 存日志,Grafana 看板。所有服务和两条生成线把 OpenTelemetry 信号推到同一个 Collector,Collector 先脱敏、再采样、再分发到三个存储,Grafana 从三个存储读出来,在一块盘上把「用户点一下到模型返回」这条链路串起来看。

配置控制面阶段四的第一波只做后端五件加自监控,不碰生产;给 game-cloud 挂 agent、重定向 Python 线的 OTLP、打通三跳 traceparent、落业务埋点,是后续波的事。权威设计见 docs/agent-specs/2026-07-05-全链路可观测性-OTel三信号全栈-设计.md

起停

部署目录假设是 /opt/infra-observability(把本目录同步过去)。

cd /opt/infra-observability
cp .env.example .env && chmod 600 .env    # 首次:填真实密钥,见下"密钥与配置"
docker compose up -d                        # 起
docker compose ps                           # 看状态
docker compose logs -f otel-collector       # 看某件日志
docker compose down                         # 停(保留数据卷)

起的时候 compose 会先跑一个一次性的 minio-init 容器,在既有 MinIO 上把 obs-tempoobs-loki 两个桶建好(幂等,重复起不报错),Tempo 和 Loki 等它成功退出后才启动。

探活

HOST=100.64.0.8 bash health-check.sh

逐个探五件的就绪端点,全绿退 0,任一没起来退 1。Tempo 和 Loki 刚起的头几十秒 /ready 会返 503,属正常就绪过程,等一会儿再探。脚本默认绕过本机 fake-ip 代理直连 Tailscale 地址。

端口账

对外端口都逐个核对过没和 mini-infra 现有占用撞(new-api=3000、gitea=3001、ragflow=9380-9381、postgres=5433、minio=9000-9001、mysql=3306、redis=6379、nacos=8848、rocketmq=9876/10911、nginx=80/443、infinity=23817/23820、mihomo=9090)。注:docker ps 只列 bridge 网端口,host 网容器与宿主进程(如 mihomo/clash 占的 9090)要 ss -tlnp 才看得全 —— Prometheus 因此从 9090 让到 9091。

组件 对外端口 用途
Grafana 3002 Web 看板(默认 3000/3001 已被占,改 3002)
Prometheus 9091 Web UI / 查询 API(9090 被 mini-infra 的 mihomo/clash API 占,对外改 9091;容器内仍 9090)
Loki 3100 HTTP API + OTLP 日志摄入(/otlp)
Tempo 3200 查询 API
Collector 4317 / 4318 OTLP gRPC / HTTP 入口(跨机信号源推这里)
Collector 13133 健康探针

Collector 自身遥测 8888、业务指标出口 8889 不对外映射,Prometheus 在同网内用服务名 scrape。Tempo 收 Collector 转发的 OTLP 走容器内 4317,不对外映射,不和 Collector 对外的 4317 冲突(不同容器各自监听)。五件之间用 compose 服务名(otel-collector / prometheus / tempo / loki / grafana)在 infra-shared 网内互通,连既有 MinIO 用它的容器名 infra-minio

内存和 CPU 账

同箱的是跨项目生产件,失控的 Prometheus 或 Tempo 在 CPU/IO 上挤兑 new-api 就是事故,所以每件都设了内存和 CPU 双上限,cpu_shares 压到 512(生产件默认 1024 的一半),CPU 抢起来观测让路。

组件 mem_limit cpus cpu_shares
Collector 512m 0.50 512
Prometheus 1g 0.75 512
Tempo 768m 0.50 512
Loki 768m 0.50 512
Grafana 384m 0.50 512

内存上限合计约 3.4G。mini-infra 扩到 32G 后 available 约 24Gi,装下这套还有大量余量。上限是纪律不是配额,扩了内存也照设。

存储

Tempo 的追踪块和 Loki 的日志块都存在既有 MinIO 上,各用独立的 obs-tempo / obs-loki 桶,和 ragflow 的桶完全分开。Tempo 的 WAL、Loki 的 index、Prometheus 的 TSDB、Grafana 的状态都落 docker 托管的本地卷(tempo-data / loki-data / prometheus-data / grafana-data),用 named volume 而非挂宿主目录,免掉 RocketMQ 那种 uid 权限坑。

保留期:Prometheus 指标 15 天(compose 里 --storage.tsdb.retention.time),Loki 日志 15 天(limits_config.retention_period),Tempo 追踪 7 天(compactor.block_retention)。MinIO 连不上的时候,Tempo/Loki 写不进对象存储会重试并在本地缓冲,Collector 那侧的队列满了会丢弃而不是把上游堵死。

脱敏(安全红线)

追踪的 span 属性里会带生成的输入输出,日志正文里会有报错原文,里面可能有用户手机号或 token。Collector 上有一道 redaction 处理器,在写进 Tempo 和 Loki 之前把手机号、token、密钥、JWT 这类打码,匹配规则在 otel-collector-config.yaml 里。这是进存储前的最后一道、也是不能绕过的一道:三条 pipeline(traces/metrics/logs)导出前都过它。验收时要专门造一条带构造手机号和 token 的生成,跑完在 Tempo 和 Loki 里查不到明文才算过。

密钥与配置

MinIO 的 access/secret key 和 Grafana 管理员口令都经 .env${VAR} 注入,compose 文件和各配置文件里不落明文。.env 只在服务器上 chmod 600 存在、不入仓,权威副本在 docs/内网凭据与端点.md

Tempo 和 Loki 的配置文件里写的是 ${MINIO_ACCESS_KEY} 这种占位,靠各自启动参数 -config.expand-env=true 从容器环境变量展开,所以 compose 给这两件显式注入了环境变量。Collector 不直接连 MinIO,配置里不含密钥。

出事怎么回滚

观测是纯增量的旁路,回滚很干净,主链和业务代码一行不动:

docker compose down                 # 停容器,数据卷和 MinIO 桶留着
docker compose down -v              # 连本地数据卷一起删(Prometheus/Grafana/WAL/index)

MinIO 里的 obs-tempo / obs-loki 桶要清的话,用 mc 单独删,不会碰到 ragflow 的桶:

mc rb --force obs/obs-tempo
mc rb --force obs/obs-loki

停掉整套栈之后,信号源那边推 OTLP 推不上会 best-effort 丢弃,生成主链行为不变。

后续波要接的

第一波之后,按设计 §8 的顺序:给 game-cloud 挂 OTel Java agent 加 actuator,把 Python 便宜档主路的 jsonl 换成真发 OTLP、tier2 的 studio_sink 改发 Collector,打通 Java→worker→本机 Service→回调 的三跳 traceparent,最后落业务埋点、建告警规则、把飞书/钉钉 webhook 接上。prometheus.yml 里已经把跨机抓 game-cloud / nacos / rocketmq 的 scrape 目标注释占位,那几件的指标端点就位后取消注释即可。