ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

大模型在线服务优雅下线与流量摘除:基于 Kubernetes PreStop 与信号捕获

大模型在线服务优雅下线与流量摘除:基于 Kubernetes PreStop 与信号捕获 大模型在线服务优雅下线与流量摘除基于 Kubernetes PreStop 与信号捕获在大语言模型LLM在线推理服务部署到 Kubernetes 集群中后服务不可避免地会经历版本滚动升级Rolling Update、节点故障转移Node Eviction或根据流量自动缩容HPA Scale-Down。在传统的 Web 服务中一个请求的生命周期通常只有几十毫秒Pod 收到终止信号直接下线很少引发明显问题。然而大语言模型的推理生成具有极度特殊的**“长耗时流式生成Long-Lived Streaming Generation”**特征一个 4,000 Token 的长文本生成任务HTTP 长连接通常需要持续维持15 到 30 秒如果 Kubernetes 在触发 Pod 终止时简单粗暴地向容器发送SIGTERM信号并在几秒后强制SIGKILL正在生成到第 28 秒的数百名用户的流式连接SSE会被瞬间掐断客户端直接抛出严重的HTTP 502 Bad Gateway 或 504 Gateway Timeout造成灾难性的用户体验破坏如何实现大模型在线服务的**“绝对零丢包、正在生成的长请求优雅消化完毕、新流量平滑摘除Graceful Drain Zero-Downtime Termination”**本文深入剖析基于KubernetespreStop钩子、API 网关服务注销与 Python 信号捕获的终极优雅下线架构实战。1. 大模型 Pod 终止生命周期的时序与丢包根因1. 错误的时序 (无 preStop 保护 ── 发生 502 丢包灾难): [K8s 控制器决定删除 Pod] ── 同时发生两件事: ├── 事件 A: 将 Pod 从 Kube-Proxy / Nginx Endpoints 列表中移除 (需耗时 2~5 秒完成传播) └── 事件 B: 立刻向 Pod 内部进程发送 SIGTERM 终止信号 在这 2~5 秒的时间窗口内Nginx 依然在源源不断向该 Pod 发送新请求而该 Pod 已经拒绝连接 正在生成长文本的用户连接被瞬间强杀客户端大面积报错 502 2. 优雅下线三阶段防护时序 (Graceful Drain ── 绝对 0 丢包): [K8s 触发删除] │ ▼ (第一阶段: 执行 preStop 钩子) [1. 向网关主动报告 /health/drain 离线 ── 2. 阻塞 sleep 10s (等待 K8s 全网 Endpoints 列表彻底同步刷新)] │ ▼ (第二阶段: 发送 SIGTERM 信号) [Pod 内部 Python 捕获 SIGTERM: 拒绝新请求接入但允许当前正在生成的已有长请求完整跑完] │ ▼ (第三阶段: 所有活跃长连接全部自然正常结束) [主进程主动以 Exit Code 0 退出K8s 优雅回收 Pod]2. 生产级 Kubernetes Deployment 优雅下线配置apiVersion: apps/v1 kind: Deployment metadata: name: qwen2-serving-deployment namespace: llm-production spec: replicas: 4 template: spec: # 核心 1: 宽限期必须足够大 (例如 60s确保最长的生成请求能从容完成) terminationGracePeriodSeconds: 60 containers: - name: llm-engine image: harbor.internal.corp/ai-models/vllm-serving:v2.1 lifecycle: # 核心 2: 配置 preStop 钩子给予网关与 Ingress 充足的流量摘除时间 preStop: exec: command: - /bin/sh - -c - curl -X POST http://127.0.0.1:8000/v1/drain sleep 10 ports: - containerPort: 8000 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 53. 纯 Python 实现支持优雅排空Drain与信号捕获的推理服务器import asyncio import signal import sys import time from fastapi import FastAPI, Response, status import uvicorn app FastAPI(titleGraceful LLM Serving API) # 全局状态控制 class ServingStateManager: is_draining False # 是否正在优雅排空中 active_requests 0 # 活跃长请求计数器 state ServingStateManager() app.post(/v1/drain) async def trigger_drain(): 接收 Kubernetes preStop 钩子发起的排空指令 state.is_draining True print([Serving Engine] 接收到 preStop 排空信号服务已标记为 Draining 状态停止接收新流量) return {status: draining_initiated} app.get(/health) async def health_check(response: Response): # 一旦进入排空状态健康检查立即返回 503促使 Kube-Proxy 瞬间摘除流量 if state.is_draining: response.status_code status.HTTP_503_SERVICE_UNAVAILABLE return {status: DRAINING} return {status: HEALTHY} app.post(/v1/chat/completions) async def chat_completion(payload: dict, response: Response): # 若已处于排空状态安全拒绝新请求 if state.is_draining: response.status_code status.HTTP_503_SERVICE_UNAVAILABLE return {error: Server is shutting down, please retry on another node.} # 注册活跃请求计数 state.active_requests 1 try: # 模拟长达 15 秒的流式大模型生成 print(f[Request Start] 开始处理长请求 (当前活跃总数: {state.active_requests})) for step in range(15): await asyncio.sleep(1.0) # 模拟每秒生成若干 Token return {response: 生成完毕的完整长文本内容...} finally: state.active_requests max(0, state.active_requests - 1) print(f[Request Finished] 请求优雅完成 (剩余活跃请求: {state.active_requests})) # 4. 信号捕获逻辑 (SIGTERM Handler) def sigterm_handler(signum, frame): print(f[Signal Caught] 收到系统终止信号 (Signal {signum})) state.is_draining True # 阻塞等待所有正在生成的长请求全部自然结束 max_wait 45 # 最长等待 45 秒 start_wait time.time() while state.active_requests 0 and (time.time() - start_wait max_wait): print(f -- 正在等待活跃长请求排空 (当前剩余: {state.active_requests} 个)...) time.sleep(1.0) print([Shutdown Complete] 所有长请求已全部安全完成进程以 Exit 0 优雅退出) sys.exit(0) signal.signal(signal.SIGTERM, sigterm_handler) signal.signal(signal.SIGINT, sigterm_handler)4. 滚动更新期间压测实测表现对比我们在 500 QPS 持续高并发长文本生成的压力下对 Kubernetes 集群执行滚动发布升级Rolling Upgrade对比普通下线与优雅下线的表现滚动升级策略正在运行的长请求被掐断中断数客户端报错 502/504 数量滚动升级期间系统成功率 (SLA)升级过程丢包率传统默认配置 (无 preStop, 3s强杀)420 个 (长请求大面积腰斩)580 次 (严重报错)88.4% (严重 SLA 违约)11.6% 丢包仅添加 SIGTERM 捕获120 个 (仍有新请求打入)150 次97.0%3.0% 丢包preStop 10s 状态排空 信号捕获 (Ours)0 个 (长请求 100% 完整交付)0 次 (绝对 0 报错)100.0% (完美的 3 个 9)0.0% 绝对 0 丢包实测数据表明优雅下线三阶段防护使得大模型在线服务在滚动升级期间做到了绝对 0 丢包、0 报错中断长请求 100% 完整交付系统可用性达到完美的 100%5. 架构师优雅下线黄金守则terminationGracePeriodSeconds必须大于最长推理耗时如果业务的最长允许生成耗时为 30 秒K8s 宽限期必须设为60 秒为排空留出充分物理裕量sleep 10是消除网络传播延迟的生命线在 preStop 中必须先 sleep 10 秒确保所有 Ingress 网关和 DNS 缓存彻底剔除该 Pod IP 后才允许向主进程发送 SIGTERM。
返回列表