读书笔记服务准备把“收藏”改成“书架”。代码通过测试,镜像构建成功,Deployment也显示两个可用副本。发布看起来已经结束。
十分钟后,用户打开旧页面提交收藏操作,接口开始报错。新版API不再接受旧的 favorite 请求字段,数据库里的旧列也被删除了;浏览器缓存的旧前端仍按原来的API契约发送请求。此时把后端镜像退回去,也无法让已经删除的字段自动出现。
一次发布改变的是一个正在被用户使用的系统。构建成功、容器启动和业务可用,是不同的验收层次。
先给构建结果一个确定身份
在第三章,我们讨论了镜像标签与digest。发布阶段要继续保留这种确定性:同一份已测试的制品应该被逐步推广到不同环境,而不是每到一个环境就重新构建一次。
假设测试环境验证的是提交A构建出的镜像X,生产环境却重新执行依赖安装,得到镜像Y。即使两次源代码相同,依赖或基础镜像变化仍可能造成差异。此时“测试通过”的证据只直接覆盖X。
一条可追溯的记录应把源代码提交、构建结果、测试结果、镜像digest和部署变更联系起来。发生问题时,我们才能回答:哪个版本进入了生产,经过了什么验证,受影响的是哪批实例。
CI主要把变更集成与验证自动化,例如单元测试、构建、静态检查和制品测试。交付流程继续处理环境差异、审批或策略检查、部署执行以及上线后的验证。不同团队对CD缩写的使用可能指持续交付或持续部署,重要的是把是否自动进入生产这条边界说清楚。
滚动更新是一段新旧版本并存的时间
Deployment更改Pod模板后,会协调新旧ReplicaSet,逐步替换实例。1 这意味着一段时间内,用户请求可能到达旧版本,也可能到达新版本。
假设目标为两个副本,设置 maxSurge: 1、maxUnavailable: 0。直观上,平台可以先创建一个新实例,等它可用,再缩掉一个旧实例。这样做需要额外资源;若节点没有容量,新实例可能一直Pending,更新就推进不了。
这两个字段控制的是更新过程中的副本安排,不保证业务请求零失败。就绪检查失真、客户端长连接、入口状态传播、退出处理或数据库不兼容,都仍然可能影响用户。
终止中的Pod还可能在宽限期内占用资源,因此不要把 replicas + maxSurge 当成任意时刻的绝对资源消耗上限。容量计划应给发布和终止过程留下空间。
新旧版本并存还要求接口兼容。API新增一个可选字段通常比突然删除旧字段容易渐进发布;消费者如果把未知字段视为错误,就会让本来可兼容的变更变得脆弱。
滚动更新有新旧共存窗口,需要额外容量、真实就绪信号和数据兼容。
退出过程要给正在处理的请求留时间
替换旧实例之前,系统需要停止向它派发新的常规请求,并让正在处理的工作有机会结束。这涉及多个并行传播的状态,不能假定某个标志一变化,所有代理和客户端就瞬间知道了。
应用收到终止信号后,可以停止接受新任务,继续处理已开始的请求,在截止时间内关闭连接并退出。对长连接、流式响应和后台worker,还要说明如何结束会话或归还尚未完成的任务。
Kubernetes提供终止宽限期及生命周期钩子等机制,但应用必须真正处理信号。2 若启动脚本不正确地转发信号,或程序收到信号立即退出,平台给出的时间窗口也发挥不了作用。
preStop不是额外赠送的一段无限时间,它占用终止宽限期。固定sleep有时能为状态传播留出余量,却不能证明所有连接都已排空。应根据入口实现、应用处理时间和实测行为设计,并验证最慢的合法请求能否安全结束。
对摘要worker尤其要小心:取到任务后还没写回结果就被终止,任务是否会再次被领取?若会重试,结果写入应具备幂等性;若领取即永久删除,任务可能直接丢失。这已经超出了容器生命周期本身,需要队列协议与业务代码共同保证。
失败不一定会自动回滚
Deployment可以报告更新超过进度期限,但 ProgressDeadlineExceeded 本身不会自动完成回滚。1 谁来决定停止、回退或继续,需要由发布系统或操作流程明确承担。
可用副本数也不足以判断业务成功。假设新版本的健康接口返回200,但“保存笔记”接口错误率升高,平台仍可能认为实例就绪。发布验收应加入与这次变更直接相关的业务请求,以及延迟、错误和关键依赖指标。
常见策略各有成本:
- 滚动更新逐步替换实例,资源成本相对可控,但要接受新旧并存
- 蓝绿发布准备两套环境,再切换流量,便于切回,但需要额外容量,也需要处理共享数据和连接
- 金丝雀发布先让少量请求使用新版本,再根据证据扩大范围,但需要可靠分流、分版本观测和明确的停止阈值
金丝雀流量不是随机出现的“几个新Pod”就算完成。你需要知道新版本实际承接了哪些请求,以及观察样本是否包含关键业务路径。
回退 Pod 模板不会自动撤销配置、数据库和外部副作用。
数据库变化如何保留回退空间
“收藏”改成“书架”可以分成几次兼容变化。
先扩展数据结构,增加新字段或新表,保留旧结构。随后发布能够理解两种结构的应用;必要时进行受控回填,并核对结果。确认所有消费者都已迁移、回退窗口已结束之后,才删除旧结构。
这个过程常称为扩展与收缩。它用多一步过渡换取部署期间的兼容性。代价是暂时存在重复结构、回填任务和验证工作;好处是避免让数据库DDL与所有应用实例在同一瞬间完成切换。
双写也不是免费的保险。两次写入可能部分成功,需要事务边界、重试和一致性修复策略。大表回填还可能占满I/O或持有锁,因此应该分批、可暂停,并观察线上负载。
rollout undo回退的是Deployment的Pod模板。它不会恢复被删除的数据,不会自动退回独立ConfigMap,也不会撤销已经发送给外部系统的请求。回退方案应该按每一种状态分别设计。
GitOps增加的是持续对照
把YAML放进Git,已经带来了代码审查和历史记录。GitOps在此基础上强调声明式、版本化的期望状态,由代理自动拉取并持续协调。3
对比一个只在提交时执行部署命令的流程:它运行结束后,某人手工改了集群,未必会被纠正。持续协调则会继续比较实际与期望,发现差异后按规则处理。
这也要求紧急操作有明确路径。线上事故中临时调整配置,如果没有同步到期望状态,自动协调可能很快把修复覆盖掉。团队需要定义紧急变更如何暂停或接管协调、如何补回版本历史,以及恢复自动管理之前检查什么。
GitOps本身也不会判断业务行为正确。Git中的错误配置同样可以被非常稳定地重复应用。审查、测试、权限边界和上线后验证,仍然是交付链条的一部分。
给发布写一份可以执行的完成条件
以读书笔记服务为例,一次发布至少应留下这些证据:确定的镜像制品已经进入目标环境;目标副本达到预期;保存、读取和图片访问等相关请求成功;关键错误和延迟没有越过约定阈值;旧实例退出没有丢失任务;数据库变化的兼容与恢复方案仍成立。
这些条件不需要都做成复杂平台。早期可以是一份小而明确的检查脚本和发布记录,随着频率增加再自动化。真正有价值的是,把“看起来上线了”变成团队共同理解、可以复查的完成标准。
动手想一想
题目 1
目标两个副本,maxSurge为1、maxUnavailable为0,但没有空闲资源容纳第三个Pod。发布可能出现什么情况?
提示
新实例必须先可用,旧实例才有条件被缩掉。
答案
新Pod可能Pending,旧副本继续运行,更新停滞。需要发布容量、调整资源或在明确可用性代价后修改策略。终止中的实例也可能占用资源,不能只按稳定副本数规划。
题目 2
新版本删除了数据库字段,随后执行rollout undo。为什么还会失败?
提示
Pod模板和数据库属于不同的状态。
答案
旧应用被恢复,并不意味着数据库字段和数据被恢复。旧版本仍可能依赖已经删除的结构。需要兼容迁移、独立备份恢复方案,或向前修复;应在发布前保留回退窗口。
题目 3
某个GitOps管理的参数手工改完又被改回,如何解释?
提示
实际状态与声明的期望状态不一致。
答案
持续协调可能按Git中的声明恢复参数。应明确该字段的管理者,把合法变更纳入期望状态;紧急操作也要遵守可审计的接管与恢复流程,避免两个控制来源持续争用。

