Kubernetes 推理 Pod 优雅下线:摘流、在途请求与终止预算
推理 Pod 退出时仅设置 preStop 或延长 grace period 仍可能丢请求。本文串联 readiness、EndpointSlice、SIGTERM、在途计数、PDB、故障测试与回滚。
本文要点
- 主题聚焦:AI、大模型、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
在线推理请求可能持续数十秒,还可能以 SSE 或流式 token 长连接返回。Deployment 更新时若进程收到 TERM 就退出,客户端会得到截断响应;若只延长 terminationGracePeriodSeconds,旧连接虽然可能完成,新连接仍会持续进入。优雅下线必须同时控制“停止被选为新流量端点”和“应用不再接单”,再等待在途请求归零。流量分批比例属于 模型服务金丝雀发布,本篇只处理单个 Pod 的退出合同。
把终止过程写成状态机
服务至少有 STARTING、SERVING、DRAINING、STOPPED 四态。readiness 只有 SERVING 返回成功;进入 DRAINING 的操作必须幂等,并原子地让 readiness 失败、拒绝新推理、停止从内部队列取任务,同时允许已经登记的请求继续。新请求返回可重试的明确状态,不能进入处理一半才取消。
Kubernetes 终止 Pod 时,grace period 的计时先开始,然后执行 preStop,hook 结束后容器运行时通常向主进程发送 TERM。控制面同时更新 EndpointSlice:terminating endpoint 的 ready 为 false,并提供 terminating/serving 条件。但 kube-proxy、Ingress、服务网格和外部负载均衡器各有传播延迟,看到 EndpointSlice 改变不等于最后一个客户端已经停止复用连接。因此应用自己的接单闸门不可省略。
一个可操作的 Pod 片段如下,/app/server drain 是镜像内的受测子命令,它通过本地 Unix socket 通知主进程切换状态,再等待十秒给摘流传播:
spec:
terminationGracePeriodSeconds: 120
containers:
- name: inference
image: registry.example.com/inference@sha256:<digest>
ports:
- name: admin
containerPort: 8081
readinessProbe:
httpGet:
path: /ready
port: admin
periodSeconds: 2
failureThreshold: 1
lifecycle:
preStop:
exec:
command: ["/app/server", "drain", "--propagation-wait=10s"]
120 秒包含 hook 执行和收到 TERM 后的退出时间,不是二者相加。进程对 drain 子命令与 TERM 使用同一状态机:停止 listen/accept 或对新请求快速拒绝,等待 in-flight counter 为零,flush 必需的审计数据,然后在截止时间前主动退出。预留 10 到 20 秒给 sidecar、日志和运行时收尾;不要让请求超时等于完整 grace period,否则轻微抖动就会触发 KILL。
readiness、liveness 与 startup 各司其职
readiness 表示能否接收流量,模型过载或 drain 时可以失败;liveness 只判断进程是否无法自愈,不能因为队列满或模型加载慢就重启;startup probe 覆盖权重加载,在其成功前延后其他探测。探针端点不依赖下游数据库或公共模型 API,否则外部抖动会同时摘除全部副本。负载均衡策略 决定请求怎样落到 ready endpoint,但连接池的 keep-alive、HTTP/2 stream 和网关重试仍需专项验证。
在途计数在鉴权成功、确定接受任务前递增,在响应完全写完或明确取消后递减。流式响应不能在生成完成时就减计数,要等最后一帧写入结果可知。对异步任务,只有消息 lease 已转移或结果落盘才算完成;退出时不再拉取新消息,并让未完成 lease 可被其他 worker 安全重放。
PDB 只覆盖一部分中断
PodDisruptionBudget 能约束使用 Eviction API 的自愿中断,例如节点 drain,但不能阻止节点故障、OOM 或直接删除 Pod/Deployment。Deployment/StatefulSet 的滚动节奏也由各自 strategy 控制,不能用 PDB 替代 maxUnavailable 与 maxSurge。至少保留跨节点、跨可用区的 ready 副本,并把单副本服务视为必然有中断窗口。
对于长请求,客户端还需携带 request ID 和幂等键。网关只能在尚未发送响应体、且操作可安全重放时重试;流已经部分返回后盲目换 Pod 会生成重复 token 或重复扣费。更大范围的状态恢复参考 LLM 灾难恢复。
用持续流量证明无损退出
在预发布环境持续发送短请求、长请求和流式请求,同时滚动更新并主动 drain 节点。记录每个 request ID 的接收 Pod、开始/结束时间、结束原因与响应字节数;成功标准是没有无原因丢失、重复执行或截断,且旧 Pod 在预算内归零退出。
kubectl get endpointslice -l kubernetes.io/service-name=inference -o yaml
kubectl get pods -n ai -w
kubectl logs -n ai <terminating-pod> --timestamps
kubectl get events -n ai --sort-by=.lastTimestamp
故障注入覆盖 preStop 超时、应用忽略 TERM、外部 LB 延迟 30 秒、客户端中途断开、sidecar 卡住和节点直接失联。监控 draining_pods、新请求拒绝数、inflight、强制退出数、截断流及退出耗时分布,并纳入 LLM SLO 监控。只看到 5xx 为零还不够,客户端超时和不完整流也必须计入。
回滚终止策略也要保住旧副本
若新版本 drain 卡死,先暂停 rollout,保持旧 ReplicaSet 容量并停止继续删除 Pod;修复状态机后用单个 canary 再测。可以回滚 Deployment 到上一镜像与生命周期配置,但不要把 grace 临时改成 0 作为常规恢复,那等同于接受在途请求丢失。
若错误 readiness 导致所有副本同时摘流,先恢复上一版探针并扩回已验证副本,确认 EndpointSlice 有足够 ready endpoint 后再处理卡住 Pod。真正的节点失联无法执行 preStop 或 TERM,因此关键请求必须在应用层支持幂等重试,优雅终止从来不是持久化和恢复机制的替代品。
延伸阅读
Related