Kubernetes Deployment 滚动发布:副本容量、就绪与终止
一个 Deployment 配了 2 个副本、maxSurge: 1,发布过程中却能看到 4 个 Pod。这不一定是控制器越过了发布策略:ReplicaSet 的期望副本数、Available 副本数和仍在终止的进程,是三个不同的量。
另一个容易误判的场景是新版本一直不 Ready,旧版本也迟迟不减少。对于 maxUnavailable: 0 的部署,这可能正是控制器在保护现有容量。本篇从这两个现象解释滚动更新的判断依据。
Deployment 调整 ReplicaSet,ReplicaSet 再调整 Pod
修改 Pod template 后,Deployment Controller 为这个模板找到或创建新的 ReplicaSet,调整新旧 ReplicaSet 的 spec.replicas。ReplicaSet Controller 观察副本差异后创建或删除 Pod,Scheduler 负责尚未绑定节点的 Pod,kubelet 再启动和终止容器。它们通过 API 对象协调,不是 Deployment Controller 直接通知节点替换进程。
sequenceDiagram
participant U as 用户
participant A as API Server
participant D as Deployment Controller
participant R as ReplicaSet Controller
participant K as kubelet
U->>A: 修改 Pod template
A-->>D: Watch 观察到新模板
D->>A: 创建新 ReplicaSet 并调整副本数
A-->>R: Watch 观察到 ReplicaSet 变化
R->>A: 创建新 Pod 或删除旧 Pod
Note over A,K: 新 Pod 经调度绑定到节点
A-->>K: Watch 观察本节点 Pod 变化
K->>K: 启动或终止容器
K->>A: 更新 Pod 状态
A-->>D: Pod 与 ReplicaSet 状态驱动下一轮协调
这条反馈链解释了发布为什么不是一次同步操作。控制器依据缓存观察到的状态重新计算,API 写入成功到容器真正退出之间存在时间差。
两个预算分别约束扩容和缩容
设目标副本数为 R,允许额外副本为 S,允许不可用副本为 U。发布时的两个主要约束是:ReplicaSet 期望副本数总和受 R+S 限制,缩减旧副本时要保留至少 R-U 个 Available 副本。
这里的百分比存在取整差异:maxSurge 向上取整,maxUnavailable 向下取整;两者不能同时为零。3 个副本配 25% 时,surge 是 1,unavailable 是 0,不能都按普通四舍五入计算。具体转换见 Kubernetes v1.35.1 的 ResolveFenceposts。
NewRSNewReplicas汇总新旧 ReplicaSet 的期望副本数,再用 R+S 减去这个总和,计算新 ReplicaSet 本次可以增加多少。该结果还要受新版本自身目标副本数限制。这里统计的是 ReplicaSet 的期望副本数,不能直接理解成节点上存活进程的绝对上限。
缩旧副本的判断在 reconcileOldReplicaSets。它先考虑新版本里仍不可用的副本,避免新版本还没准备好就撤掉旧版本。随后结合当前 Available 数量决定能缩多少,并处理旧版本中的不健康副本。新副本创建成功本身不足以给旧副本的删除开绿灯。
上述计算以控制器本轮观察到的 R 为输入,发布期间的目标副本数并非永远固定。HPA 或操作者修改 replicas 后,scale 在新旧 ReplicaSet 同时活跃的滚动更新中按比例分配增减量,并重新考虑当前 surge 预算。因此不能拿发布开始时的 R 推算整个发布期间的固定上限;本文实验没有同时运行 HPA,比例扩缩容的说明来自源码。
Ready 与 Available 之间还有一段稳定时间
readiness probe 回答容器是否满足当前就绪条件。Pod Ready 还受其他容器和 readiness gates 影响。Deployment 使用的 Available 进一步要求 Pod 在 minReadySeconds 所要求的时间内保持就绪;默认值为零,非零值可以阻止刚 Ready 的副本立刻替换旧容量。
IsPodAvailable 在 Ready 条件为真后继续比较它的最后转换时间与 minReadySeconds,不能用一次 Ready 采样代替这段时间条件。
readiness probe 必须反映业务接流量所需条件。仅检查进程存在,无法证明依赖服务、缓存预热或配置已经可用。minReadySeconds 也不能补救错误的探针;它只是增加稳定观察时间。
progressDeadlineSeconds 用于报告发布缺少进展。超时会反映为 Deployment 的失败条件,不会自动替用户执行回滚;需要发布系统或操作者决定后续动作。相关字段定义见 Deployment 官方说明。
终止中的旧进程仍然占用资源
ReplicaSet 下调副本数后,旧 Pod 收到删除标记。节点执行 preStop 和终止宽限期内的退出流程,这些进程可能继续存活一段时间。与此同时,控制器可以依据已经下调的 ReplicaSet 副本数继续增加新 Pod。
因此,R+S 不能作为 CPU、内存和连接数的硬峰值预算。旧 Pod 的终止时间越长,资源重叠窗口越明显。preStop 消耗的也是 terminationGracePeriodSeconds 总预算,并不是额外赠送的等待时间。有关进程终止和 EndpointSlice 变化,见 Pod 删除流程。
即使 Available 数量一直满足预算,也不代表每个请求都成功。Service 端点更新、代理同步、已有长连接、应用停止接收新请求与完成在途请求,是不同过程。本文的副本实验没有发起 HTTP 流量,不能据此声称验证了零中断发布。
实验:慢启动和慢退出叠加
实验使用 zj 上独立 kind 集群 article-control,Kubernetes 和 kubectl 均为 v1.35.1。Deployment 有 2 个副本,maxSurge=1、maxUnavailable=0、minReadySeconds=5,readiness 通过文件是否存在控制,旧容器的 preStop 保持 25 秒,总终止宽限期为 40 秒。
初始版本启动时创建 readiness 文件,新版本不创建。脚本先确认新 Pod 已启动、尚未满足 readiness,同时旧的两个 Pod 都还存在:
| |
随后逐个打开新 Pod 的 readiness,等待控制器继续发布,记录到:
| |
这次观察同时说明了 surge 预算与真实对象数量的差别,以及 rollout 完成与所有旧进程退出的差别。数量 4 是本实验观察,不是这个配置在所有时序下都固定达到的峰值。
容量不足时,可用性预算也会阻止发布前进
在已完成的 Deployment 上,把新模板 CPU request 提高到实验 worker 无法提供的值。新的 Pod 得到 Unschedulable,旧版本仍有 2 个 Available 副本。调度事件明确指出 worker 的 CPU 不足,三个控制面节点则有未容忍的污点:
| |
在 maxUnavailable=0 的条件下,控制器无法先删除健康旧副本来腾位置,新副本又无法调度,发布就会停在这里。扩大节点容量、降低真实资源需求或允许一定不可用数量是不同取舍;直接调大 maxSurge 只增加创建预算,不能制造节点容量。
实验最后撤销这个不可能调度的模板,并确认 Deployment 重新完成 rollout。配置、自动断言、证据与清理方法见 k8sdev/article-control-rollout。先按该目录 README 的固定提交准备步骤创建独立工作目录,再运行实验。
发布实验先检查新 Pod 未就绪时旧副本仍在,再持续采样终止中的 Pod 与 Available 数量。
分析发布问题时,应同时读取 Deployment 条件、新旧 ReplicaSet 的期望与 Available 副本数、Pod 的调度和就绪条件、删除时间戳,再对照节点资源与应用退出过程。只看 replicas: 2 或一次 rollout status,无法回答容量峰值与请求可用性。