先选择发布的形状,再调整速度。新旧版本能否同时处理请求?能否安全共享存储和任务所有权?如果新版本起不来,如何恢复?这些问题决定 Deployment 应该用 RollingUpdate 重叠运行,还是用 Recreate 先停旧版、再起新版。姊妹篇讨论滚动的节奏和现场观察;本篇讨论兼容性、停机与恢复边界。

RollingUpdate 允许新旧版本重叠;Recreate 在升级时等待旧 Pod 终止后才创建新版,因此存在服务空档。接口、数据和任务所有权必须支持混合版本。Recreate 不是分布式锁。ReadWriteOnce 限制可写挂载到一个节点,不是一个 Pod;ReadWriteOncePod 需要受支持的 CSI 卷。rollout undo 恢复记录的 Pod 模板,不撤销数据库迁移或独立编辑的 ConfigMap 内容。副本算术不能证明运行时不中断。
先检查兼容性与所有权,再选择版本重叠。Recreate 只改变升级顺序;回滚仍需经过演练的镜像、配置与数据恢复方案。

逐步替换,还是先停后起

RollingUpdate 是 Deployment 的默认策略。surge 和 unavailable 两个预算控制替换过程,允许新旧 ReplicaSet 同时存在。就绪状态参与可用副本的计算,但不代表业务逻辑正确,更不代表每个请求都成功。不能因为看到 Ready,就推断订单、支付或后台任务已经通过发布验收。

Recreate 在 Deployment 升级时等待旧 Pod 终止,再创建新版本,因此应计划一段没有可用服务副本的时间。选择这个策略时,要移除 rollingUpdate 配置块;其中的字段只属于滚动更新。停机窗口要通过测量得到,不能顺手把三十秒当成保证。旧进程退出慢、新镜像下载慢、存储挂载慢,都可能拉长用户实际感受到的中断。

兼容性才是选择依据

允许版本重叠,意味着接口、持久化数据以及后台工作都能兼容。前端健康检查可能正常,旧 worker 却读不懂新 worker 刚写入的记录。调整副本数之前,应测试混合版本的读写、任务领取和重试行为。副本预算能安排进退顺序,不能修复不兼容的数据库结构变化。

有状态并不自动意味着必须选择 Recreate:协调正确的副本式应用,也可能支持新旧版本同时运行。反过来,Recreate 不是分布式锁。它的先停后起约束适用于 Deployment 升级;如果人工删除一个 Pod,ReplicaSet 可能在旧 Pod 仍处于终止状态时创建替代者。真正的单写者需要应用层协调,必要时还要有隔离旧所有者的机制,确保失去资格的进程不能继续写入。不能把发布策略当成这种机制的替代品。

存储术语也不能混淆。ReadWriteOnce 限制的是一个节点以读写方式挂载,并不等于只能有一个 Pod;同一节点上的多个 Pod 仍可能使用同一卷。对于支持的 CSI 卷,ReadWriteOncePod 才提供单 Pod 访问约束。应检查实际驱动、卷的访问模式与工作负载需求,而不是看到一个存储标签,就认定已经解决了所有权问题。

回滚也是一次变更

保留可以重新启动的旧镜像和修订历史。kubectl rollout undo 恢复记录过的 Pod 模板;只把镜像地址改回去,可能漏掉已经变化的启动参数或环境引用。这两种操作都不会撤销数据库迁移,也不会恢复独立修改过的 ConfigMap 内容。旧模板继续引用同一个 ConfigMap 名称时,重新启动的旧程序仍可能读到新数据。

发布前要记录镜像、配置和数据结构之间的兼容关系。如果需要混合运行或回退,应优先安排向后兼容的迁移顺序。恢复流程必须演练,包括谁执行、按什么顺序做、需要多久,以及如何判断旧版已经恢复业务。只留着旧 ReplicaSet,不能证明恢复是安全的。使用 Recreate 时,回到旧版也可能再经历一次停机和启动;恢复时间不能只按正常升级的一半估算。

坏掉的样子

没有正确协调的 worker,可能在所有 Pod 都 Ready 时重复执行任务或破坏数据。此时需要检查任务所有权与版本兼容性,而不只是看容器是否健康。另一类故障是 Recreate 的冷启动超出预算:镜像拉取或卷挂载很慢,应用真正开始启动之前,停机时间就已经被耗尽。因此演练要包含真正的冷启动和旧版退出耗时,不能只测缓存齐全时的进程启动。

四个副本设置 maxUnavailable: 50%,预算允许减少到两个可用副本,不会自动变成一个。新替代 Pod 崩溃可能让进度停滞;另外发生的节点故障等独立事件,才可能把现有容量继续压低。决定采用这个预算之前,先验证两个实例能否承担预期流量,而不是把「更新仍在允许范围内」等同于业务容量足够。

零 unavailable 也无法防止无关故障。另一个极端是允许全部副本不可用:这样的配置可以合法存在,只是通常不符合服务目标。若团队要禁止它,应明确这是自己的发布政策,而不是 Kubernetes API 的校验规则。真正不能同时为零的是 surge 和 unavailable,否则替换过程没有可推进的空间。

快速验证

把下面保存为 strategy_window.py,运行 python3 strategy_window.py。这是只使用标准库的本地模型,仅接受正整数副本数与非负整数预算。它计算规划量,不模拟控制器,也不是完整的 API 校验器;百分比换算、自动扩缩容和副本数为零的情况不在这个模型内。Kubernetes 允许把 Deployment 缩容到零;程序拒绝它,只因为本练习讨论的是有副本需要替换的场景。

"""Integer rollout-budget arithmetic; no cluster or traffic is exercised."""


def rolling_budget(replicas, surge, unavailable):
    values = (replicas, surge, unavailable)
    if any(type(value) is not int for value in values):
        raise ValueError("this model accepts integers only")
    if replicas <= 0 or surge < 0 or unavailable < 0:
        raise ValueError("positive replicas and nonnegative budgets required")
    if surge == 0 and unavailable == 0:
        raise ValueError("surge and unavailable cannot both be zero")
    planned_floor = max(0, replicas - unavailable)
    target_ceiling = replicas + surge
    return planned_floor, target_ceiling


def main():
    assert rolling_budget(6, 1, 1) == (5, 7)
    assert rolling_budget(4, 0, 2) == (2, 4)
    assert rolling_budget(4, 1, 4) == (0, 5)
    for invalid in [(4, 0, 0), (4, -1, 1), (0, 1, 0)]:
        try:
            rolling_budget(*invalid)
        except ValueError:
            pass
        else:
            raise AssertionError(f"invalid model input accepted: {invalid}")
    print("rolling: planned availability floor=5, replica target ceiling=7")
    print("full-unavailability budget: planned floor=0 (permitted)")
    print("recreate: expect a zero-serving interval during replacement")
    print("PASS: budget arithmetic only; runtime availability untested")


if __name__ == "__main__":
    main()

预期输出四行,最后一行明确写着只通过预算算术、未测试运行时可用性。六副本、一 surge、一 unavailable 的结果是五和七;允许四个副本全部不可用的例子,故意得到零,而不是抛出「非法配置」。两项预算同时为零、负预算、零副本等不属于本模型有效输入的情况,则通过断言检查拒绝路径。

正在终止的 Pod、独立故障、调度以及真实请求处理都没有进入模型。因此,五不是实际观测到的服务能力下限,七也不是所有运行进程的绝对上限。程序只帮助检查算术和策略假设,不能给线上可用性出具证明。把数字换成自己的副本配置之前,也要先处理百分比和实际需求变化,而不是直接复制结论。

本次修订在 Linux 的 Python 3.12.14 中执行了断言,没有执行 Kubernetes 集成实验。采用某个策略前,应在测试环境演练升级和回滚,同时持续发请求、检查存储使用,并运行混合版本业务测试。把这些现场结果与本地算术结果分开记录,才知道哪些结论有真实环境的证据支持,哪些还只是规划假设。

延伸阅读

来源