一架航班正飞行在一万两千米的高空,乘客们有的在看电影,有的在打瞌睡。驾驶舱里,机长刚刚下达了一个不寻常的命令:更换一台老化的引擎。这不是迫降前的应急处置,而是例行的维护升级——在飞行中完成。机务人员把新引擎逐个挂上挂架,盯着仪表确认转速、油压、温度全部正常后,才拆下旧引擎;四个引擎依次替换,飞机全程保持着充足的推力。客舱里的乘客,只是偶尔感到一丝轻微的颠簸,浑然不觉这架飞机的“心脏”已经全部换新。这正是零停机部署的艺术:服务不落地,升级不中断。而实现这一目标的策略,就是滚动更新——让新旧版本在短暂的窗口期共存,新实例就绪一个,旧实例才退役一个。本文将带你从飞机引擎聊到 Kubernetes 实例,理解滚动更新的核心机制、参数配置、探针设计、会话保持,以及 AI 推理服务中的特殊实践与回滚预案。
掌握滚动更新与零停机部署,是 AI 从业者走向生产环境的必修课。你将学会如何在服务不中断的前提下安全升级模型与推理服务,这直接决定了你能否在团队中独当一面地管理 AI 基础设施。
可视化演示
通过新旧实例平滑替换实现零停机,演示滚动循环、更新策略与就绪探针回滚机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
滚动循环 新建 就绪 终止 NEW→OK→OLD 交替替换零停机
滚动更新原理
想象一架航班正在一万两千米高空巡航,机长决定更换一台老化的引擎。他不能降落检修——几百名乘客正等着准点抵达。他的策略是在飞行中完成更换:机务先把新引擎挂到空余挂架上,启动并确认动力正常,再拆下旧引擎。四个引擎依次替换,飞机始终维持着足够的推力。乘客只是偶尔感到轻微颠簸,浑然不觉引擎已全部换新。
这套“边建边拆”的策略正是滚动更新的精髓。传统部署像“先降落后检修”:先把所有旧实例停掉,再拉起新版本,期间服务完全不可用。而滚动更新让新旧实例在短时间内共存——新实例就绪一个,旧实例才下线一个,服务如同飞机引擎一样始终“有动力”。
在 Kubernetes 中,滚动更新由 Deployment 控制器编排。Deployment 会创建一个新版本的 ReplicaSet(相当于新引擎的“编制名册”),然后逐步缩减旧 ReplicaSet 的副本数,同时扩大新 ReplicaSet。每次只替换一部分实例,直到所有 Pod 都换成新版本。这个过程让可用性公式中的“修复时间”大幅缩短,因为故障实例被自动替换,无需人工停机介入。
拆下的旧引擎先放进货仓而不是扔进海里——每个历史版本的镜像都保留着,随时可以装回去。这就是不可变基础设施配合滚动更新的意义:飞机在飞,服务在线,变化在无声中完成。
- MTTF 平均无故障时间系统从启动到发生故障的平均时长,单位小时。这个值越大,代表服务越“皮实”。
- MTTR 平均修复时间从故障发生到服务恢复的平均时长。滚动更新让修复过程自动化,不必等运维手动重启。
- 可用性 A用正常时间 MTTF 除以总周期(MTTF + MTTR),得到服务可用占比。分子分母同量纲,比值在 0 到 1 之间。
参数配置策略
机长换引擎前会明确两条硬性约束:任何时刻至少保留几台引擎运转?机舱最多能多放几台备用引擎?如果一次拆两台、偏偏另一台又罢工,飞机就失去足够动力;如果备用引擎堆满货仓,飞机超重根本飞不起来。这两条约束,就是滚动更新的节奏开关。
Kubernetes 滚动更新同样有两条关键参数:maxUnavailable(最大不可用数)和 maxSurge(最大额外实例数)。maxUnavailable 决定更新过程中最多允许几个旧实例同时离线,默认 25%;maxSurge 决定允许超出期望副本数的实例数量。它们共同刻画了“机舱的承重限额”。
生产环境为了保证零停机,通常把 maxUnavailable 设为 0,意思是:只有新实例就绪并接管流量后,才允许下线旧实例,任何时刻都不出现“空窗期”。maxSurge 设为 25%,表示可以多启动 25% 的新实例来加速替换。再加上 minReadySeconds——新实例稳定运行多少秒后才算数——三者共同控制着换引擎的节奏:既不让飞机失去动力,也不让货仓超载。
换引擎的节奏也是这样:先装一台新引擎确认稳定,再拆一台旧的。maxUnavailable=0 保证飞机永远有足够引擎,maxSurge 有上限保证不超重。滚动更新,就是在两条硬约束之间寻找最优的替换节奏。
- 期望副本数 NDeployment 里声明要维持的 Pod 数量,是更新后最终要达到的目标值。
- 额外比例 maxSurge/100把百分比换算成小数,得到允许超出的比例。25% 变成 0.25。
- 加总得出容量上限期望副本数乘以“1 + 额外比例”,就是在更新最激烈时集群里同时运行的实例上限。
就绪探针设计
新引擎挂上挂架,不代表可以立刻投入使用。机务人员必须盯着仪表盘:转速达到巡航值、油压稳定、温度正常,最后还要逐步加大推力做试车确认无异常——这一整套检查,就是飞机版的“就绪探针”。没有这套仪表,谁都不敢把新引擎接上动力系统。
在 Kubernetes 中,就绪探针(Readiness Probe)负责回答一个问题:“这个新实例能接流量了吗?”探针定期向容器发起检查,检查通过后负载均衡器才把流量转发给它;没通过就继续等,绝不提前放行。它和存活探针(Liveness Probe)不同——存活探针决定容器是否要重启,就绪探针只决定流量放不放行。
就绪探针有三种类型:HTTP GET(请求某个路径,看返回码是否为 200)、TCP Socket(检查端口能否建立连接)、Exec(在容器内执行命令看退出码)。关键参数有三个:initialDelaySeconds(容器启动后等多久才开始探测)、periodSeconds(每隔几秒探一次)、failureThreshold(连续失败几次判定未就绪)。对 AI 推理服务而言,探针不仅要确认端口可访问,还要确认模型权重已加载、推理引擎已预热,否则流量提前进来只会产生一串超时。
探针就是机长的仪表。没有仪表盲换引擎,新引擎是否可靠全靠运气;有了探针,每次替换都建立在“确认安全”之上。新实例真正 ready,旧实例才正式退役。
- 初始等待时间 T_initial容器启动后不立即探测,先给应用留出初始化时间。AI 服务加载模型权重的前几十秒就靠它兜底。
- 探测间隔 × 失败阈值每 periodSeconds 探一次,连续 failureThreshold 次失败才判定未就绪,这中间的累计等待。
- 最坏情况总时长两部分相加,得到探针从开始工作到给出“未就绪”结论的最长耗时。
会话保持处理
乘客正在高空看电影、用机上 Wi-Fi 发消息。换引擎时,如果娱乐系统的供电闪断一下,乘客会立刻投诉。机务的解决办法是“无缝切换”:新引擎接入供电系统的瞬间,旧引擎继续维持片刻供电,娱乐画面全程不黑。这种切换的精细度,决定了乘客的体验。
Web 服务也有类似的“供电”——会话(Session)。用户在电商网站的登录状态、购物车信息,通常保存在会话中。当旧实例退出时,如果会话没有被妥善迁移,用户会被强制登出、购物车清空。这就是滚动更新中最容易“被投诉”的环节。
处理方案有三层。第一层:负载均衡器开启会话粘滞(Session Affinity),让同一用户的请求固定落到同一个后端实例,避免请求在不同实例间跳来跳去。第二层:旧实例收到关闭信号后先“排空”(drain),停止接收新请求,专注处理完正在进行的请求。第三层:Kubernetes 在停止容器时先发送 SIGTERM 信号,应用捕捉到信号后执行清理逻辑,等待 grace period 结束才被强制结束。如果排空处理得好,会话丢失率趋近于零。
乘客的娱乐系统全程没黑,是因为新旧引擎的“供电切换”设计得足够精细。服务升级时用户的会话不掉线,同样得益于粘滞、排空与优雅退出这三重保险。
- 丢失会话占比更新期间丢失的会话数除以总会话数。只有排空不彻底、连接被强杀时,这个值才会大于 0。
- 用 1 减去因为保持率 + 丢失率 = 1,所以用总体的"1"减去丢失占比,得到成功保留的比例。
- 得到保持率 R_keep这个值越接近 1,说明用户的登录态和请求完成度越高,服务体验越好。
AI推理实践
这架飞机换的不只是普通引擎,还是一台“智慧引擎”——它内置了全球航线数据库和自动驾驶算法。新引擎装好后,必须先加载航线数据、跑一遍完整的自检程序,才能接入自动驾驶系统。如果没等数据加载完就切换,飞机连基本的航线都认不出来,更别提自动巡航了。
AI 推理服务正是这样一台“智慧引擎”。一个 GPU 实例上运行着大模型,模型权重从磁盘加载到显存往往需要数十秒甚至数分钟。滚动更新时,如果新实例还没加载完模型就被并入流量池,用户的推理请求会全部超时或返回垃圾结果。这也是 AI 服务滚动更新与普通 Web 服务的最大分水岭。
AI 推理服务的滚动更新方案需要“四件套”:就绪探针内检查模型加载完成的标志位;新实例就绪后主动发送 warmup 请求预热推理引擎(如 TensorRT、ONNX Runtime 的 CUDA 上下文);持续监控 GPU 利用率和 P99 延迟,发现性能劣化立即暂停更新;用实例饱和度公式评估当前实例数能否扛住流量,避免盲目滚动导致过载。
给智慧引擎换新,核心不是换硬件那一刻,而是让新引擎“带着完整的记忆和手感”上岗。AI 服务的滚动更新,本质上就是让新模型在不中断服务的前提下,无缝接替旧模型的推理职责。
- QPS 与 P99 的乘积QPS 是每秒请求数,P99 是 99% 请求的延迟时间,两者相乘得到“单位时间需要的并发容量”。
- 单实例总并发容量N 个推理实例,每个实例最大并发上限为 C,所有实例同时处理请求的封顶值。
- 两者相除得到饱和度ρ 大于 1 表示需求超过容量,会出现排队和超时;小于 0.7 表示资源有冗余。
回滚与评估
新引擎在空中试车时,仪表显示振动频率异常。机长没有慌,因为旧引擎并没有扔进海里——它好端端地放在货仓里,随时可以重新挂上。机组决定:停止更换计划,把已装上的新引擎拆下,换回旧引擎。飞机继续平稳飞行,仿佛什么都没有发生过。
在不可变基础设施中,回滚同样不是“撤销一个操作”,而是“反向发布”。每个版本镜像都是不可变的——构建完成后永远不会被修改。旧版本镜像依然存在于镜像仓库中,滚动更新的回滚,就是让部署目标重新指向旧版本镜像,让实例重新从旧镜像重建。
自动回滚由健康信号驱动:当新版本实例的就绪探针持续失败、错误率超过设定阈值、或 P99 延迟明显劣化于基线时,控制系统自动触发回滚。回滚的耗时取决于三个变量:流量切换时间、单个实例重建时间、回滚批次数量。性能评估也是一项必修课——更新前要记录基线指标(GPU 利用率、错误率、吞吐量、P99),更新中持续对比,回滚后重新评估,才能量化这次变更对服务的影响。
旧引擎放货仓不是因为恋旧,而是因为备用方案永远是最可靠的保险。不可变基础设施的魅力就在于此:版本不可变,回滚变得异常简单,飞机永远有退路。
- 流量切换时间 T_switch把负载均衡器的指向从新版本切回旧版本,或调整 Deployment 镜像版本所花的控制面操作时间。
- 重建耗时与批次每个旧版本实例从镜像重新拉起需要 T_rebuild 秒,N_batch 表示分几批完成回滚。
- 相加得到回滚总时长控制面切换与数据面重建并行推进,但总耗时近似为两者之和。
AI 应用场景
LLM API 推理服务的零停机版本升级
阶段:模型部署与上线
解决的问题:大语言模型(如 7B/13B 参数量)的权重文件动辄几十 GB,加载到 GPU 显存需要数分钟。传统停机更新会导致对外 API 长时间不可用,调用方集体超时。滚动更新配合 AI 定制的就绪探针,让模型切换在用户无感知的情况下完成。
具体案例:某智能客服团队将 LLM 从 v1 升级到 v2,Kubernetes 配置 maxSurge=1、maxUnavailable=0;就绪探针访问 /ready 接口,确认权重加载完成且 warmup 请求通过后才把 Pod 标记为可用。升级期间 API 的 P99 延迟仅出现约 5% 的瞬时抖动,无一条请求报错。
推荐系统特征平台的滚动更新
阶段:在线推理特征获取
解决的问题:推荐系统依赖实时特征服务,若特征服务重启,所有推荐请求都会因拿不到特征而失败。通过滚动更新 + 会话保持 + 优雅退出,让特征服务的连接在全链路中不中断。
具体案例:电商推荐系统升级特征服务时,负载均衡器开启 Cookie 粘滞,同一用户请求固定路由到同一实例;旧 Pod 配置 PreStop 钩子,收到 SIGTERM 后等待存量特征查询最迟 30 秒内完成再退出。升级期间推荐接口成功率保持在 99.99% 以上。
Python 演示
import randomimport time class Instance: """模拟一个应用实例 (Pod)""" def __init__(self, version, name): self.version = version self.name = name self.ready = True self.connections = random.randint(5, 20) self.health = 100 def drain(self): """优雅排空: 停止接新请求, 等存量请求处理完""" self.ready = False time.sleep(0.1 * self.connections / 5) self.connections = 0 def check_ready(self): """模拟就绪探针""" return self.ready and self.health >= 80 class Deployment: """模拟一个 Deployment 控制器""" def __init__(self, instances): self.instances = instances def add(self, instance): self.instances.append(instance) def remove(self, name): self.instances = [i for i in self.instances if i.name != name] def total_ready(self): return sum(1 for i in self.instances if i.check_ready()) def rolling_update(self, new_version, batch_size=1): print("=== 开始滚动更新到 {} ===".format(new_version)) old_list = [i for i in self.instances if i.version != new_version] for idx, old in enumerate(old_list): old.drain() self.remove(old.name) print(" 移除 {}, 当前 {} 个实例".format(old.name, len(self.instances))) time.sleep(0.05) new_name = "pod-{}-{}".format(idx + 1, new_version) new_inst = Instance(new_version, new_name) self.add(new_inst) print(" 新增 {}, 就绪探针通过, 当前 {} 个实例就绪".format( new_name, self.total_ready())) if (idx + 1) % batch_size == 0: print(" -- 完成一批 ({} 个), 等待确认...".format(batch_size)) print("=== 更新完成, 全部实例版本: {} ===".format(new_version)) def main(): deploy = Deployment([ Instance("v1", "pod-1-v1"), Instance("v1", "pod-2-v1"), Instance("v1", "pod-3-v1"), ]) print("更新前: {} 个实例在线".format(deploy.total_ready())) deploy.rolling_update("v2", batch_size=1) print("更新后: {} 个实例在线".format(deploy.total_ready())) if __name__ == "__main__": main()
self.connections = random.randint(5, 20)模拟每个实例当前正在处理的请求连接数,随机性贴近真实负载。def drain(self)优雅排空的核心:先标记不可用,再等存量请求处理完,最后才真正“关闭”。old.drain(); self.remove(old.name)先排空再移除,保证没有请求被强行掐断,这是零停机的关键动作。new_inst = Instance(new_version, new_name)创建新版本实例,模拟新 Pod 被 Deployment 拉起。self.add(new_inst)新实例加入负载池,对应“接管流量”。self.total_ready()模拟就绪探针统计,只有健康分达标且 ready 标记为真的实例才被计数。if (idx + 1) % batch_size == 0批次节奏控制,每替换完一批就“暂停确认”,对应部署策略中的批次大小参数。
常见误区
误区滚动更新能做到零停机,所以用户在更新期间什么都感觉不到,延迟不会变、连接绝不会断,任何指标都是平的。
正解零停机指的是服务不中断——不会出现 503、连接拒绝、请求丢失。但单次请求的延迟可能有轻微抖动,正在排空的旧实例上的连接会切换。就像飞机换引擎时有轻微颠簸,乘客能感觉到,但飞机不会掉高度。真正要追求的是“可用性不降”,而不是“体验零变化”。
为何常见:工程师容易把“可用性”理解成“毫无感知”,但可用性只保证服务可被访问,不保证体验零波动。
误区就绪探针和存活探针都是健康检查,配一个就行,两者没什么区别,反正都是发 HTTP 请求看返回码。
正解就绪探针决定“流量放不放进来”,不通过时实例被从负载均衡摘除,但不会被杀死;存活探针决定“容器要不要重启”,不通过时 kubelet 会直接重启容器。把 AI 模型加载检查放进存活探针会导致容器被反复重启,应该放进就绪探针里。
为何常见:名字里都带“探针”,功能都是探测健康,不亲自踩坑很难注意到两者处理策略的天壤之别。
误区滚动更新很安全,新版本有问题会被就绪探针拦住,不会出现需要回滚的情况。
正解探针只能拦“注册过的故障”。很多问题要等真实流量放大后才暴露——内存泄漏、慢查询、模型效果退步、数据格式兼容性 bug。生产环境必须有回滚预案并定期演练,否则真出问题时,回滚手忙脚乱只会延长故障时间。
为何常见:滚动更新给人一种“渐进 = 安全”的错觉,忽略了渐进只是缩小爆炸半径,并不能消除爆炸本身。
误区AI 推理服务就是普通 Web 服务加了个模型,就绪探针返回 HTTP 200 就代表可用了,滚动更新配置直接照抄 Web 服务。
正解AI 推理服务的新实例启动后要加载模型权重(数 GB 到显存)、初始化 CUDA 上下文、预热推理引擎,这些可能耗时数分钟。如果 HTTP 服务在模型加载完之前就返回 200,流量提前进入会导致大量超时。GPU 显存也是稀缺资源,maxSurge 设置过大可能直接 OOM。AI 服务的探针和更新参数必须为“模型加载”这一额外阶段定制。
为何常见:在 Kubernetes 里 AI 推理服务也是 Pod,都是容器,很容易让人忽略“模型加载”这个 Web 服务没有的启动阶段。
面试题
Q1. 滚动更新与传统“先停后启”部署最核心的区别是什么?
- A.更新期间新旧实例可以短暂共存← 正确答案
- B.更新前需要备份整个数据库
- C.全部实例停止后才启动新版本
- D.只允许在夜间低峰期进行更新
答案:A。滚动更新让新旧实例在窗口期共存,新实例就绪一个、旧实例下线一个,服务不中断。B 是常规运维习惯,C 是传统的停机发布,D 是发布窗口策略,都不是滚动更新的本质区别。
Q2. 就绪探针(Readiness Probe)的作用是什么?
- A.检查容器是否存活并自动重启已挂容器
- B.判断实例是否准备好接收用户流量← 正确答案
- C.监控实例的 CPU 和内存使用率
- D.定期向监控系统推送应用日志
答案:B。就绪探针决定是否把流量调度给实例,未通过时从负载均衡摘除但不重启。A 是存活探针的描述,C 是资源监控,D 是日志收集,都不涉及流量准入判断。
Q3. 某服务期望 10 个副本,配置 maxSurge=2、maxUnavailable=1,更新期间最多可同时存在多少个实例?
- A.最多 10 个实例同时运行
- B.最多 11 个实例同时运行
- C.最多 12 个实例同时运行← 正确答案
- D.最多 9 个实例同时运行
答案:C。maxSurge=2 允许最多超出期望数 2 个,10+2=12;同时 maxUnavailable=1 保证最少 9 个可用。两个参数共同约束,更新期间实例数在 9 到 12 之间波动,最多 12 个。
Q4. 滚动更新中优雅退出(Graceful Shutdown)的正确顺序是什么?
- A.立即停止进程,然后通知负载均衡器
- B.先摘除流量,再处理完存量请求,最后关闭进程← 正确答案
- C.直接删除 Pod,让控制器重新创建
- D.发送 SIGKILL 信号强制结束进程
答案:B。优雅退出的核心是“先摘流、再排空、后关闭”。摘除流量保证不再接收新请求,排空保证存量请求完成,最后才关闭进程,请求零丢失。A 和 D 属于强制结束,C 则完全绕过了排空逻辑。
Q5. 为什么 AI 推理服务的就绪探针不能只检查 HTTP 200?
- A.HTTP 检查无法检测网络是否连通
- B.模型权重加载完成后 HTTP 才能返回 200
- C.容器启动后 HTTP 可能正常但模型未加载完← 正确答案
- D.HTTP 探针会消耗过多的 GPU 显存
答案:C。AI 服务的 Web 框架通常在容器启动后立即返回 HTTP 200,但模型权重可能还在磁盘到显存的加载过程中,此时推理请求会失败或超时。就绪探针应额外检查模型加载完成的标志位。
Q6. 以下哪些参数属于 Kubernetes 滚动更新配置?
- A.maxUnavailable 最大不可用数量← 正确答案
- B.maxSurge 最大额外实例数量← 正确答案
- C.minReadySeconds 最少就绪稳定秒数← 正确答案
- D.imagePullPolicy 镜像拉取策略
答案:ABC。maxUnavailable 和 maxSurge 是滚动更新的核心速率参数,minReadySeconds 定义新实例就绪后需稳定多久才继续推进更新,三者直接控制更新节奏。imagePullPolicy 决定镜像拉取时机,与更新节奏无关。
Q7. 滚动更新期间会话丢失率与排空时长之间的关系是什么?
- A.排空时间越长,会话丢失率越高
- B.排空时间越长,会话丢失率越低← 正确答案
- C.排空时间与会话丢失率无关
- D.排空时间越长,新实例拉起越慢
答案:B。排空时长决定了存量请求有多少时间完成,时间越长完成度越高、被强杀的连接越少,会话丢失率越低。D 说的是排空对新实例的影响,与会话丢失率无直接因果关系。
Q8. AI 推理服务滚动更新时需要额外考虑哪些因素?
- A.模型权重加载到 GPU 显存的耗时← 正确答案
- B.推理缓存预热(如 TensorRT 引擎)← 正确答案
- C.P99 推理延迟的抖动监控← 正确答案
- D.数据库连接池的大小设置
答案:ABC。AI 推理的核心资产是模型和推理引擎:权重加载耗时决定新实例多久才能真正可用;推理引擎预热避免前几个请求极慢;P99 延迟监控用于发现新版本是否劣化。数据库连接池是通用问题,并非 AI 服务特有。
Q9. 自动回滚机制的触发条件是什么?
- A.新版本实例持续未通过健康检查← 正确答案
- B.用户手动提交了回滚请求
- C.服务器磁盘空间低于阈值
- D.网络流量超过预期的三倍
答案:A。自动回滚由健康信号驱动——新版本实例就绪失败、错误率超阈值、延迟劣化等,系统自动切回旧版本。B 是手动回滚,C 是运维告警,D 是扩容触发条件,都不是自动回滚的触发机制。
Q10. 在不可变基础设施中回滚为什么比传统方式更可靠?
- A.旧版本镜像仍然存在且不可变← 正确答案
- B.回滚本质上是重新部署上一个版本← 正确答案
- C.回滚后实例状态与历史版本完全一致← 正确答案
- D.回滚会自动删除所有新版本数据
答案:ABC。不可变基础设施强调镜像不可变,旧镜像仍可拉取,回滚就是重新发布旧版本,实例从镜像重建后状态与历史完全一致。D 是错误描述,回滚不会删除数据,只是切换版本。