推理服务为什么一上健康检查就开始误杀正常实例:从 Probe 设计到优雅降级的工程实战
一、健康检查为什么成了"杀手"
在 Kubernetes 集群部署大模型推理服务后,实例正常处理请求却频繁被 kubelet 重启。事件日志原因出奇一致——Liveness probe failed。
大模型推理负载与常规 Web 服务截然不同。一次 Prefill 可能持续数秒,高并发下 GPU 被持续占满。kubelet 的 /healthz 请求进入队列后,若前面排了多个推理任务,探针响应很容易超过 1s 超时。许多团队直接照搬微服务探针配置,连续三次超时即触发重启。对加载 70B 模型需数分钟的 Pod,这是自杀配置。
⚠️ 核心误区是把进程存活等同于服务健康。推理服务健康是多维度的。

二、从误杀根因到 Probe 分级
误杀来自三个层面错配。探针类型错配最常见:Liveness 本意检测死锁,团队却常把就绪逻辑塞进去。队列繁忙时 Liveness 失败直接重启容器,而非摘流。超时参数错配同样致命:P99 延迟 5 到 10 秒,探针超时却设 1 秒。优雅终止缺失雪上加霜:请求断开、KV Cache 丢失、客户端收 502,形成死亡螺旋。
📊 正确做法是建立探针分级。Startup 确认模型加载完成,Liveness 只做存活检查,Readiness 反映能否接收新请求,需检查队列深度与 GPU 显存。
| 探针类型 | 检查目标 | 失败后果 | 建议配置 |
|---|---|---|---|
| Startup Probe | 模型加载完成、服务可访问 | 不标记为 Ready | failureThreshold=30, periodSeconds=10 |
| Liveness Probe | 进程未死锁、主线程存活 | 重启容器 | 极简 HTTP 200,timeoutSeconds=5 |
| Readiness Probe | 是否还能接收新请求 | 从 Endpoints 摘除 | 检查队列深度与 GPU 显存 |
🔍 Readiness 失败只摘流量不杀容器,Liveness 失败才重启。把负载检查放到 Readiness 是第一原则。
[外链图片转存中…(img-7BrQXAbJ-1779153757152)]
三、自定义就绪检查实战
Readiness Probe 不能只做简单 ping。生产就绪端点需暴露真实负载状态:排队请求数或 GPU 显存超阈值时返回 503,让负载均衡器把流量切走。
以下是基于 FastAPI 的实现框架。核心逻辑是维护运行时指标,在就绪端点按阈值返回状态。
from fastapi import FastAPI
app = FastAPI()
queue_depth = 0
max_queue_depth = 16
gpu_memory_percent = 0.0
max_gpu_memory_percent = 0.92
@app.get("/health/ready")
async def readiness_probe():
if queue_depth >= max_queue_depth:
return {"status": "unready", "reason": "queue_full"}, 503
if gpu_memory_percent >= max_gpu_memory_percent:
return {"status": "unready", "reason": "gpu_memory_high"}, 503
return {"status": "ready"}
@app.get("/health/live")
async def liveness_probe():
return {"status": "alive"}
💡 不要把就绪检查做成同步推理。若就绪端点需访问 GPU 状态,确保不进入主推理队列。
[外链图片转存中…(img-UVix0X4i-1779153757153)]
四、优雅终止:别让重启变成事故
即使探针配置正确,实例升级或缩容时仍需终止 Pod。Kubernetes 发送 SIGTERM 后给 Pod 宽限期,默认 30 秒。推理服务须在此窗口内停收新请求,并尽量让进行中的推理跑完。
import signal
import sys
import time
from fastapi import Request
shutting_down = False
def handle_sigterm(signum, frame):
global shutting_down
shutting_down = True
time.sleep(15)
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
@app.middleware("http")
async def shutdown_guard(request: Request, call_next):
if shutting_down and request.url.path != "/health/live":
return {"status": "shutting_down"}, 503
return await call_next(request)
⚙️ 生产环境更稳妥的做法是收到 SIGTERM 后立即将 Readiness 设为 false,等待活跃请求归零再退出。terminationGracePeriodSeconds 建议 120 秒以上,必须大于最长单请求推理时间。
🛡️ 关键参数是 terminationGracePeriodSeconds 必须大于最长单请求推理时间。
五、深度思考与趋势判断
当前主流推理框架的健康端点普遍偏简单,多数只提供 /health 返回 200,在生产规模化部署时显得不足。笔者认为推理服务健康模型正从二元存活向多维状态机演进。未来会出现三个趋势:按模型维度拆分健康状态;服务网格与自定义探针深度集成;推理框架内置过载保护,动态就绪反馈替代静态限流。
🎯 核心判断是推理服务的 SRE 体系不能照搬微服务经验。探针设计的本质是负载感知,而非心跳检测。
总结
推理服务被健康检查误杀,根源在探针逻辑与负载特征不匹配。通过建立三级探针体系、自定义就绪检查与优雅终止机制,可将重启频率从每小时数次降到每周数次。
你在部署大模型推理服务时,是否遇到过探针误杀问题?欢迎分享经验。
📌 别忘了点赞收藏,后续持续更新 AI 推理优化的深度解析与实战干货。关注我带你玩转 AI。
更多推荐



所有评论(0)