4.2 灰度发布与生产化:滚动更新、会话亲和、回滚与监控告警 压测帮你算清了「能扛多少」,但真正上线后最吓人的往往不是容量,而是「改一发而动全身」——一次配置更新、一次权重切换,可能让整个推理服务抖动甚至雪崩。这一节讲的是把 GLM-5.2 推理服务做成一个可灰度、可回滚、可观测的生产级系统,而不是一个「跑起来就别碰」的黑盒。 我踩过最痛的坑:第一次升级 vLLM 版本时直接 replace 了 Deployment,旧 Pod 立刻被删、新 Pod 启动要重新加载权重(冷启动十几秒),这十几秒内所有请求 5xx。业务方以为服务挂了。从那以后我定下铁律:任何变更都必须灰度,任何变更都必须能 30 秒内回滚。
压测帮你算清了「能扛多少」,但真正上线后最吓人的往往不是容量,而是「改一发而动全身」——一次配置更新、一次权重切换,可能让整个推理服务抖动甚至雪崩。这一节讲的是把 GLM-5.2 推理服务做成一个可灰度、可回滚、可观测的生产级系统,而不是一个「跑起来就别碰」的黑盒。
我踩过最痛的坑:第一次升级 vLLM 版本时直接 replace 了 Deployment,旧 Pod 立刻被删、新 Pod 启动要重新加载权重(冷启动十几秒),这十几秒内所有请求 5xx。业务方以为服务挂了。从那以后我定下铁律:任何变更都必须灰度,任何变更都必须能 30 秒内回滚。
K8s Deployment 默认的更新策略就是 RollingUpdate,但默认参数对大模型服务极不友好:
maxSurge 默认 25%:意味着更新时最多多起 25% 的 Pod。对 GPU 服务,这等于更新期间要多占一份显存——如果节点显存本就吃紧,新 Pod 会 Pending 卡住,更新永远完成不了。我的建议:把 maxSurge 设为 0 或 1,maxUnavailable 设为 1,做到「先下后上、逐台替换」,避免显存峰值叠加。minReadySeconds:Pod 变成 Ready 后还要等几秒才算稳定。对 vLLM,Ready 只是容器启动,权重加载完才算真能服务。务必配合就绪探针(readinessProbe)——探针在权重加载完成、能响应推理请求后才返回成功,否则 Pod 不会进 Service endpoints,就不会接流量。terminationGracePeriodSeconds:旧 Pod 收到终止信号后,要留够时间处理完在途请求(尤其是长生成),否则会被强制杀掉、前端收到断流。建议设 60–120 秒,并在 preStop 钩子里做优雅退出。上面提到 preStop,这里必须展开讲,因为这是长文本生成服务区别于普通无状态 Web 服务的关键点。普通 Web 请求几十毫秒就结束,Pod 被杀基本无感;但 GLM-5.2 的一次长生成可能持续几十秒甚至上分钟,如果 Pod 收到 SIGTERM 就立刻退出,正在流式输出的用户会突然断流、拿到半截答案,体验极差。
K8s 删除 Pod 的完整流程是这样的:控制面先把 Pod 从 Service 的 endpoints 摘除(新请求不再进来),同时向容器发 SIGTERM,然后等待 terminationGracePeriodSeconds,超时才发 SIGKILL 强杀。但这里有个经典竞态:摘除 endpoints 和发 SIGTERM 几乎同时发生,而 kube-proxy/Ingress 更新转发规则有延迟,导致 SIGTERM 已经到了、新请求却还在往这个 Pod 打。解决办法是加一个 preStop 睡眠钩子(如 sleep 15),让 Pod 先"装死"十几秒,等转发规则彻底收敛后再真正开始退出。
我的实战配置口径是:preStop 睡 10–15 秒消化转发收敛,terminationGracePeriodSeconds 给到 120 秒覆盖最长的一次生成,vLLM 进程本身要能响应 SIGTERM 停止接新请求、但把已在跑的序列生成完。三者配合,才能做到「滚动更新时用户完全无感」。少了任何一环,都会在高并发下露出断流的马脚。
如果你的业务是「多轮对话」,且 vLLM 没开跨请求前缀缓存,那么把同一用户的请求固定到同一个 Pod 就很重要——否则用户第二轮对话打到另一个 Pod,那个 Pod 没有上一轮的 KV Cache,要么重新 prefill(慢),要么直接丢失历史(错)。
两种做法:
sessionAffinity: ClientIP,按客户端 IP 哈希。简单,但缺点明显:客户端 IP 往往在大网 NAT 后趋同(多个用户共享一个 IP),且 Pod 扩缩容会打破哈希、导致大批量会话漂移。我只在对一致性要求不高的场景用。session_id → Pod 的映射,把同一 session 的请求路由到固定 Pod。这比 ClientIP 更精准,也不受 Pod 扩缩影响。代价是要自己写一点路由逻辑——但对「多轮对话」业务,这笔投入值得。我的主张:多轮对话业务,会话亲和不是可选项,是必选项。否则用户会体验到「上一句还记得,下一句就失忆」的诡异行为,这种 bug 比延迟高更伤信任。
K8s 的 kubectl rollout undo 能把 Deployment 回滚到上一个 ReplicaSet,这是你的第一道保险。但大模型场景要注意:
latest——否则你以为回滚了,结果拉到的还是新镜像。权重同理,PVC 里的权重路径要版本化。revisionHistoryLimit 足够(如 5),保证你能回退到足够早的版本。我给团队的 SOP:变更前先 kubectl rollout history 确认上一份版本在;变更后用 kubectl rollout status 盯进度;异常就 kubectl rollout undo。这套命令要烂熟于心,因为出事时你没时间翻文档。
没有监控的生产服务等于盲飞。vLLM 本身暴露了 metrics 端点(如 /metrics),配合 K8s 生态的 Prometheus + Grafana 就能搭起可观测性。重点监控四类信号:
dcgm-exporter 采集显存占用、SM 利用率、显存带宽、GPU 温度。设告警:显存持续 > 90% 持续 5 分钟 → 预警(容量将触顶);温度 > 85℃ → 严重(可能降频/掉卡)。--max-num-seqs 的 80% → 容量预警;P99 延迟超过 SLA 两倍 → 严重。新手最容易把告警配成「任何波动都通知」,结果三天后所有人默默静音了告警——这是最危险的。我的原则:
一个实用技巧:把你压测得到的容量数字直接写进告警阈值。比如压测得出舒适并发 16、拐点 32,那就可以设「运行中请求数 > 25 持续 5 分钟」为预警——这正是拐点前的缓冲区,提醒你该扩容了,而不是等崩了才知。
GLM-5.2 推理服务要不要上 HPA(Horizontal Pod Autoscaler)?我的判断:谨慎。原因:
我的建议:生产环境先用「固定副本数 + 预留余量」稳跑,HPA 只作为应对可预期峰值的辅助手段,且必须基于 vLLM 队列长度这类真实负载指标,而非 CPU。不要为了「自动化」而自动化,把一个本就紧张的 GPU 服务交给反应迟钝的 HPA,往往适得其反。
前面流程图里那句「切 5% 流量灰度」听起来简单,但很多人到这一步就卡住了:靠 Deployment 自己是做不到按百分比分流的——它只能控制新旧 Pod 的数量比例,不能控制流量比例。想真正做到「只让 5% 的请求打到新版本」,需要在接入层动手。
以 nginx-ingress 为例,它支持 canary 注解:给新版本建一个独立 Service 和一份带 nginx.ingress.kubernetes.io/canary: "true" 和 canary-weight: "5" 的 Ingress,它就会把 5% 的流量导到新版本,其余 95% 还走旧版。确认新版指标正常,就把 weight 逐步改成 25、50、100,最后把旧版 Ingress 下线。这才是真正可控的流量灰度。
我的建议是:对推理服务,灰度不要只看「没报错」,要重点对比新旧版的 P99 延迟、首 token 延迟(TTFT)、显存占用、生成质量。因为很多回归不是 5xx——新版 vLLM 参数调校不当导致延迟悄声变高、或量化配置变化导致输出质量下降,这些都不会报错,只能用指标和抽样看输出才能发现。灰度期多盯 15–30 分钟,别急着放量。
把上面所有点串起来,一个最小可行的闭环是:
kubectl rollout history 确认可回退版本;记下当前压测基线。kubectl rollout undo 回退,Alertmanager 已自动通知。Q1:滚动更新时新 Pod 一直 Pending,更新卡死怎么办?
基本就是显存/GPU 不够。maxSurge 大于 0 时会先拉起新 Pod,但节点没多余 GPU,新 Pod 就排不上队。把 maxSurge 设 0、maxUnavailable 设 1,改成「先下后上」即可。
Q2:sessionAffinity: ClientIP 为什么会失效?
两个常见原因:一是客户端在 NAT 后 IP 趋同,哈希到同一 Pod;二是 Pod 扩缩容打破哈希。多轮对话一致性要求高时,改用应用层 session_id 绑定。
Q3:回滚后发现拉到的还是新镜像?
你用了 latest 或可变 tag。镜像必须打不可变 tag(带 commit hash),权重路径也要版本化,否则回滚只是自欺欺人。
Q4:告警频繁误报导致大家静音了怎么办?
所有阈值加「持续 N 分钟」条件过滤瞬时抖动,分级为 warning/critical,只有 critical 才打电话。阈值直接用压测数字校准,别拍脑袋。
补充一个很多人忽略的细节:灰度不只是发布新版时才用,排查线上问题时它同样是利器。比如怀疑某个参数(如 --max-num-seqs 或 --gpu-memory-utilization)调整能不能提升吞吐,不要直接全量改,而是起一个小比例 canary 用真实流量跑 A/B,对比两边的 P99 和显存曲线再决定。把生产环境当成「可控实验室」而非「不敢碰的黑盒」,是资深运维与新手的分水岭。
还有一点心态上的提醒:生产化不是一次性工程,而是持续迭代的习惯。第一次上线你可能只做到了 RollingUpdate 和基本监控,会话亲和、Ingress 金丝雀、告警分级可以在后续迭代中逐步补齐。关键是每次事故复盘后都把教训固化成一条 SOP 或一个告警规则,让系统随时间越跑越稳。
读完这一节,你应该能搭出一个「不会一改就崩」的生产级 GLM-5.2 服务:RollingUpdate 配 maxSurge=1 避免显存叠加、readinessProbe 守门冷启动、会话亲和保住多轮上下文、kubectl rollout undo 作 30 秒回血、Prometheus+Grafana 把压测结论变成告警阈值。一句话——压测给你『能扛多少』,这一节给你『怎么安全地改动和兜底』。到本章结束,你已经从『能跑』走到了『敢上线』。