Files
hair/docs/监控探活接口.md
2026-10-15 23:04:07 +08:00

13 KiB
Raw Permalink Blame History

监控探活接口

外网监控机器用这个接口判断当前对外服务能不能接业务。不正常就报警。监控侧每 30 秒调用一次,网关不做主动推送。

对外地址:

GET https://hair.xiangsilian.com/monitor

本机等价地址是 GET http://127.0.0.1:8080/monitor。nginx 对 hair.xiangsilian.com 的 location / 整段反代到网关 8080,所以这条路径不用单独改 nginx。


1. 为什么不复用原来的 /health

网关上原来就有两个探活,都不能直接拿来报警:

路径 谁在用 HTTP 状态 实际含义
GET /health 旧路径,和下面这条相同 永远 200 网关进程还活着
GET /gateway-health 人看池子概况 永远 200 同上,body 里带 worker 计数
worker 的 GET /health 网关自己每 8 秒打各台 GPU 机 200 或 503 那一台 worker 自己是否就绪

/health 和 /gateway-health 只要 uvicorn 进程在,就返回 "status": "ok"。五台 worker 全部下线时,HTTP 仍然是 200,只是 workers_healthy 变成 0。只看状态码的监控不会报警。

这次加的 /monitor 专供外部监控:

  • 至少一台 worker 处于在线(online)→ HTTP 200
  • 一台在线的都没有 → HTTP 503
  • 报警演练打开时,即使还有在线 worker,也返回 HTTP 503(见第 9 节)

监控只需要判断状态码。200 正常,非 200(503、连接失败、超时)报警。


2. 调用约定

项目 约定
方法 GET
路径 /monitor
鉴权 不需要。不校验 X-Internal-Token
请求体 无
建议间隔 30 秒
超时 建议 5 秒。接口本身只读内存,正常几十毫秒内返回
请求日志 不记录。网关请求日志只记 /api/,这条不会进 request_log.jsonl,也不会出现在 /admin/stats

30 秒一次不会给 worker 增加探测。/monitor 不访问 GPU 机,只读网关内存里已经维护好的池状态。真正打 worker /health 的是网关后台循环,间隔见 gateway/config.json 的 health_check.interval_seconds(当前 8 秒)。


3. 怎样算「正常」

正常的定义是:网关当前还能把业务请求派到至少一台 worker。

池里每台 worker 有一个 online 标志。workers_healthy 就是 online == true 的台数。这个标志由后台健康检查维护,规则和派发用的是同一份状态:

  1. 启动时立刻对配置里的每台 worker 打一次 GET {worker}/health(带 X-Internal-Token,超时 health_check.timeout_seconds,当前 3 秒)。返回 200 且 healthy_threshold <= 1(当前就是 1)则标记在线。
  2. 之后每 8 秒再探一轮。
  3. 连续失败达到 unhealthy_threshold(当前 2 次)才从在线改成下线。单次抖动不会下线。
  4. 业务转发失败(连不上、超时、HTTP ≥ 500、401)会立刻把那台 worker 标下线,并进入隔离期 cooldown_seconds(当前 60 秒)。隔离期内即使 /health 恢复 200 也不重新上线。
  5. 隔离期结束后,再有一次探测 200 才重新上线。

因此 /monitor 看到的「在线」,就是下一笔生发/测量请求实际可以派过去的机器,不是「进程还在」这么宽的概念。

会返回 200 的情况

  • 主 worker 里还有任意一台在线。当前配置有 3 台主 worker。
  • 主 worker 全挂,但备用 worker 还有在线的。主备分组下,主组全下线之后流量会打到备用,服务仍然能接,所以不报警。body 里 workers_primary_healthy 会是 0,需要看主组是否掉光时看这个字段,不要用状态码。
  • 在线的 worker 全部正在忙。忙只说明队列里有请求,机器没有被摘掉。等某台 worker 空闲最多 queue_wait_seconds(当前 30 秒);全局并发槽排满时最多再等 max_queue_wait_seconds(当前 590 秒)。这不是宕机。

会返回 503 的情况

  • 配置里的 worker 全部不在线。包括:探测连续失败被摘除、转发失败进入隔离期、启动后第一轮探测就没有一台返回 200。
  • 池还没初始化好(进程刚起来、_workers 仍为空)。这种情况窗口很短,发生在 uvicorn 完成启动之前;启动完成前端口还不会接受连接,监控更多会看到连接失败,而不是这条 503。

网关进程本身挂了、nginx 挂了、机器网络不通,监控连不上,同样应当按失败报警。那不是这个接口返回的 503,而是连接错误。


4. 响应

Content-Type: application/json。

200 正常

{
  "status": "ok",
  "service": "gateway",
  "workers_total": 5,
  "workers_healthy": 3,
  "workers_primary_total": 3,
  "workers_primary_healthy": 3
}

503 不正常

{
  "status": "unhealthy",
  "service": "gateway",
  "workers_total": 5,
  "workers_healthy": 0,
  "workers_primary_total": 3,
  "workers_primary_healthy": 0,
  "message": "没有可用的 worker"
}
字段 类型 说明
status string ok 或 unhealthy。与 HTTP 状态码一致:ok 对应 200,unhealthy 对应 503
service string 固定 gateway
workers_total int 配置里的 worker 总数
workers_healthy int 当前在线台数。未开演练时,大于 0 返回 200,等于 0 返回 503
workers_primary_total int 主 worker 台数。未配置 primary_workers 时,全部 worker 都视为主 worker
workers_primary_healthy int 主 worker 里在线的台数。为 0 且 workers_healthy > 0 表示只剩备用在撑
message string 仅 HTTP 503 时出现。真故障文案是「没有可用的 worker」;演练文案以「模拟故障」开头
simulated bool 仅演练打开时出现,值为 true。用来区分演练 503 和真故障

不返回 worker 地址、密码、队列深度。地址和鉴权信息留在网关日志和 gateway/config.json 里。


5. 监控侧建议

按 HTTP 状态码 报警,不要只解析 body。

结果 处理
200 正常
503 报警。body 的 message 可放进告警内容
连接失败、超时,以及其他 5xx 报警。网关或 nginx 不可达

建议连续 2 次失败再发告警(约 60 秒)。网关重启时端口会有几秒不可用(hair-gateway.service 的 RestartSec=3,启动时还要等第一轮 worker 探测完成)。只看单次失败会在重启时误报。

不需要带任何请求头。不要把这条 URL 配成 POST,也不要把它当成业务接口去看 code 字段——这里没有业务信封 {code, message, request_id, data}。

报警延迟,从「最后一台可用 worker 真的挂了」到监控看到 503,大致是两段相加:

  • 网关摘除:后台每 8 秒探一次,要连续失败 2 次才下线,大约 8~16 秒。若是转发失败触发的被动摘除,则立刻下线。
  • 监控拉取:最多再等一个 30 秒周期。

所以典型告警延迟大约半分钟到一分钟,不是秒级。


6. 和业务接口的关系

worker 全下线时,走 GPU 的接口(测量、生发、发际线、遮罩等)都会失败,/monitor 返回 503。这是要报警的情况。

有两个接口在网关本机执行,不经过 worker 池:

  • 接口 4 POST /api/v1/face/features(豆包)
  • 接口 99 POST /api/v1/face/special-points(本机 CPU)

这两条在 worker 全挂时仍可能成功。/monitor 不管它们。它表示的是「依赖 worker 池的对外服务是否还能接」,不是「网关进程里每一个路由都活着」。


7. 已知边界

这些情况 /monitor 不会变成 503,和派发逻辑一致,避免把「还能干活」报成故障:

  • 只挂了一部分 worker,还剩至少一台在线。
  • 主 worker 全挂,备用仍在线。
  • 在线 worker 都在忙,请求在网关排队。排队超时是业务返回「后端服务繁忙」,不是这个探活的失败。
  • worker 的 /health 仍返回 200,但机器实际上不能生图。当前仓库里 worker 的 /health 只看模型是否加载完成(未就绪才 503),不检查磁盘水位,也不检查 ComfyUI、change_hair 是否真的能出图。2026-09-19 那次磁盘写满时,浅探活会一直 200,网关就不会把机器摘掉,/monitor 也会一直 200。worker 侧更深的探活不在本接口范围内。

另外,备用 worker 如果从启动起就探不通,日志里不一定有「下线」警告。下线警告只在「原本在线、随后连续失败」时打印。一直没上线的机器只体现在 workers_total 和 workers_healthy 的差额里。


8. 实现与生效

项 位置
路由 gateway/app.py 的 GET /monitor、POST /monitor/fault
判定 同文件 monitor_payload():未开演练时 workers_healthy > 0 为正常;演练打开则一律 503
池状态 gateway/pool.py 的 get_pool_status(),统计 online 台数
单测 tests/test_gateway_monitor.py

改的是网关代码。worker 不用发版。改完后要重启网关进程才会加载新代码:

sudo systemctl restart hair-gateway
systemctl is-active hair-gateway
curl -sS -D - http://127.0.0.1:8080/monitor
curl -sS -D - https://hair.xiangsilian.com/monitor

配置(worker 列表、探测间隔、下线阈值、隔离期)仍在 gateway/config.json,启动时加载一次。只改配置、不改代码,同样要重启 hair-gateway。

/health 与 /gateway-health 保持原样,继续永远返回 200,给「进程是否存活」这类检查用。外部报警请只挂 /monitor。


9. 故障模拟(报警演练)

POST /monitor/fault 用来演练监控会不会报警。打开之后,GET /monitor 固定返回 503;worker 池、派发、业务接口都不变。测完必须关掉,否则监控会一直报。

开关只活在网关进程内存里。重启 hair-gateway 后自动回到关闭。

请求

curl -sS -X POST https://hair.xiangsilian.com/monitor/fault \
  -H "Content-Type: application/json" \
  -H "X-Internal-Token: <gateway/config.json 里的 shared_password>" \
  -d '{"enabled": true}'
项目 约定
方法 POST
路径 /monitor/fault
鉴权 请求头 X-Internal-Token,值与 gateway/config.json 的 shared_password 相同。缺失或错误返回 401
Body JSON 布尔。{"enabled": true} 打开,{"enabled": false} 关闭。字符串 "true"、数字 1 都算参数错误,返回 400

打开成功:

{"status": "ok", "simulated": true, "message": "已开启模拟故障,GET /monitor 将返回 503"}

关闭成功:

{"status": "ok", "simulated": false, "message": "已关闭模拟故障,GET /monitor 恢复真实状态"}

演练期间 /monitor 的 503

计数字段仍是真实池状态,多一个 simulated: true,用来和「真的没有 worker」区分。若演练期间在线数本身已经是 0,文案会同时写明「当前没有可用的 worker」,避免把真故障误读成纯演练。

在线数仍大于 0 时:

{
  "status": "unhealthy",
  "service": "gateway",
  "workers_total": 5,
  "workers_healthy": 3,
  "workers_primary_total": 3,
  "workers_primary_healthy": 3,
  "simulated": true,
  "message": "模拟故障:监控测试中,真实 worker 未摘除"
}

监控若只看 HTTP 状态码,这次 503 会报警,和真故障同一条链路。告警内容里如果带了 body,可以看到 simulated 和「模拟故障」。

建议步骤

  1. 确认当前 GET /monitor 是 200。
  2. POST /monitor/fault,enabled 设为 true。
  3. 再拉一次 /monitor,应为 503,且 workers_healthy 没有变成 0。
  4. 等监控的 30 秒周期(若配了连续 2 次失败才告警,就等两次)。确认告警发出。
  5. enabled 设为 false。再拉 /monitor,应回到 200。确认告警恢复。

演练时业务请求仍会打到在线 worker。这个接口不能用来模拟磁盘满、ComfyUI 挂掉或某台机器被摘除。


10. 上线核对(2026-10-15)

hair-gateway.service 已重启并完成启动。启动日志:Worker 池初始化完成 | 总数=5 | 在线=3 | 主worker=3。三台主 worker 的 /health 均为 200;两台备用在这轮启动探测里没有上线,所以健康数是 3 不是 5。按上面的规则这仍是正常。

当时公网实查:

HTTP/1.1 200 OK
{"status":"ok","service":"gateway","workers_total":5,"workers_healthy":3,"workers_primary_total":3,"workers_primary_healthy":3}

旧的 GET /health 仍是 200。重启前 8080 上没有已建立连接。

故障模拟在同一次上线里用本机请求核对过:不带令牌或令牌错误返回 401,且开关保持关闭;enabled: true 之后 GET /monitor 为 503,simulated 为 true,workers_healthy 仍是 3;enabled: false 之后回到 200。核对结束时开关是关闭的。开关的开、关会在 journalctl -u hair-gateway 里各留一条 warning,不进业务请求日志。