Kubernetes 基础体系 · 第 16/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Deployment 与 ReplicaSet:滚动更新、Revision、暂停、回滚和失败

Deployment 是 Kubernetes 中用于声明和管理无状态应用发布状态的工作负载资源。它通常不直接创建 Pod,而是创建并管理一个或多个 ReplicaSet,再由 ReplicaSet 创建 Pod。

这层间接关系是 Deployment 能够实现滚动更新、保留历史版本、暂停发布和回滚的基础:

flowchart TD
    D[Deployment<br/>期望副本数、发布策略、Pod 模板] --> RS1[旧 ReplicaSet<br/>revision 1]
    D --> RS2[新 ReplicaSet<br/>revision 2]
    RS1 --> P1[旧版本 Pod]
    RS2 --> P2[新版本 Pod]
    S[Service] --> P1
    S --> P2

Deployment 决定“应该处于什么发布状态”,ReplicaSet 负责“维持某个 Pod 模板对应的副本数”,Pod 则是最终运行单元。Deployment Controller 会持续比较期望状态和实际状态,并通过创建、扩容、缩容 ReplicaSet 使两者趋于一致。

本文使用 apps/v1 API,这是当前稳定的 Deployment 和 ReplicaSet API。示例中的命令需要一个可用的 Kubernetes 集群和已配置好的 kubectl


一、ReplicaSet:维持一组相同 Pod 的控制器

1.1 ReplicaSet 的核心职责

ReplicaSet 的目标可以形式化为:

实际 Pod 数量spec.replicas\text{实际 Pod 数量} \approx \text{spec.replicas}

其中“实际 Pod”不是集群中所有 Pod,而是满足 ReplicaSet .spec.selector 的 Pod。

一个最小的 ReplicaSet 示例:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: web-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      version: v1
  template:
    metadata:
      labels:
        app: web
        version: v1
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80

应用:

kubectl apply -f rs.yaml
kubectl get rs
kubectl get pods -l app=web,version=v1

预期可以看到:

NAME     DESIRED   CURRENT   READY   AGE
web-rs   3         3         3       ...

这里存在三个重要关系:

  1. spec.replicas: 3 表示 ReplicaSet 希望维持三个匹配的 Pod。
  2. spec.selector.matchLabels 定义 ReplicaSet 管理哪些 Pod。
  3. spec.template.metadata.labels 必须匹配 selector,否则 apps/v1 API 会拒绝该对象。

ReplicaSet 通过标签选择 Pod。若匹配的 Pod 数量少于期望值,它会创建 Pod;若多于期望值,它会删除多余 Pod。Pod 被删除后,ReplicaSet 只会重新创建缺少的数量,不会恢复被删除 Pod 的 UID 或运行时状态。

1.2 ReplicaSet 与 Pod 的归属

ReplicaSet 创建 Pod 时,会在 Pod 上设置 ownerReferences,指向 ReplicaSet。这个引用使得 Kubernetes 能够识别控制关系,并在 ReplicaSet 被删除时按照级联删除策略处理其 Pod。

ReplicaSet 也可能接管符合 selector、但没有其他控制器归属的孤儿 Pod。因此,多个 ReplicaSet 不应使用重叠的 selector,否则可能出现相互争抢 Pod 的异常行为。

apps/v1 中,ReplicaSet 的 selector 是不可变字段。修改 Pod 标签通常不能通过修改已有 ReplicaSet 的 selector 实现版本切换;正确做法是创建新的 ReplicaSet,或者使用 Deployment 管理发布。

1.3 为什么通常不直接使用 ReplicaSet 发布

ReplicaSet 能够保证某一版本的 Pod 数量,但它不负责以下发布语义:

  • 逐步增加新版本并减少旧版本;
  • 保存多个发布历史;
  • 根据历史版本回滚;
  • 监控发布进度;
  • 暂停和继续发布;
  • 在多个 ReplicaSet 之间协调副本数。

这些功能由 Deployment Controller 实现。因此,直接使用 ReplicaSet 适合底层控制或特殊场景,而普通无状态应用通常应直接使用 Deployment。


二、Deployment:管理 ReplicaSet 的发布控制器

2.1 Deployment 的对象结构

一个 Deployment 至少包含:

  • spec.replicas:期望运行的 Pod 副本数;
  • spec.selector:Deployment 管理的 Pod 选择器;
  • spec.template:新 Pod 的模板;
  • spec.strategy:发布策略;
  • spec.minReadySeconds:Pod 成为可用副本前,必须连续 Ready 的时间;
  • spec.revisionHistoryLimit:保留多少旧 ReplicaSet;
  • spec.progressDeadlineSeconds:多长时间没有观察到发布进展后报告失败。

示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 4
  revisionHistoryLimit: 5
  minReadySeconds: 5
  progressDeadlineSeconds: 120
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 5

应用并检查:

kubectl apply -f deployment.yaml
kubectl get deployment web
kubectl get rs -l app=web
kubectl get pods -l app=web

Deployment 会创建一个 ReplicaSet。该 ReplicaSet 的 Pod 通常会带有由 Deployment 生成的 pod-template-hash 标签,例如:

app=web,pod-template-hash=7d8f6c9b7

pod-template-hash 用于区分不同 Pod 模板生成的 ReplicaSet。它是控制器生成的实现细节,不应手工设置,也不应在应用逻辑中依赖其具体值。

2.2 哪些修改会触发新的 ReplicaSet

Deployment 的核心判断对象是 Pod 模板,也就是 .spec.template

例如修改镜像:

kubectl set image deployment/web web=nginx:1.28

或者修改环境变量、端口、探针、资源限制、卷挂载等 Pod 模板字段,都会使模板发生变化。Deployment Controller 会:

  1. 计算新的 Pod 模板;
  2. 创建一个新的 ReplicaSet;
  3. 为新的 ReplicaSet 设置期望副本数;
  4. 逐步缩减旧 ReplicaSet;
  5. 等待新 Pod 达到可用状态;
  6. 最终让旧 ReplicaSet 的副本数降为零。

相反,单纯修改 Deployment 的副本数通常不会创建新的 Pod 模板,因此不会产生新的发布版本。例如:

kubectl scale deployment/web --replicas=6

这会调整当前 ReplicaSet 或多个现有 ReplicaSet 的副本数,但不是一次新的模板发布。不要把“Deployment 对象发生了修改”和“发生了新的 Deployment revision”混为一谈。

Deployment 的 selector 在创建后不可变。若需要改变应用选择范围,通常应创建新的 Deployment,而不是试图修改已有 Deployment 的 selector。


三、滚动更新:两个 ReplicaSet 之间如何切换

3.1 RollingUpdate 的目标

RollingUpdate 是 Deployment 的默认策略。它不要求先删除所有旧 Pod,而是允许新旧版本在一段时间内同时存在。

滚动更新要同时满足两个约束:

  • 新旧 Pod 的总数不能超过允许的上限;
  • 可用 Pod 数不能低于允许的下限。

假设:

  • N 是 Deployment 的期望副本数;
  • SmaxSurge
  • UmaxUnavailable
  • A 是当前可用 Pod 数量。

滚动更新期间,近似需要满足:

总 Pod 数N+S\text{总 Pod 数} \leq N + S

以及:

ANUA \geq N - U

其中 maxSurge 控制发布时可以额外创建多少 Pod,maxUnavailable 控制相对于期望副本数最多可以少多少个可用 Pod。

这两个参数可以使用整数或百分比。百分比计算时:

  • maxSurge 向上取整;
  • maxUnavailable 向下取整。

3.2 完整算例

假设:

replicas: 10
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 25%
    maxUnavailable: 25%

计算结果为:

S=10×25%=3S = \lceil 10 \times 25\% \rceil = 3

U=10×25%=2U = \lfloor 10 \times 25\% \rfloor = 2

因此:

  • 新旧 Pod 总数最多为 13
  • 可用 Pod 数量通常不应低于 8

一个可能的过程是:

阶段 旧 ReplicaSet 新 ReplicaSet 总 Pod 可用 Pod
初始 10 0 10 10
创建新 Pod 10 3 13 10
新 Pod Ready 7 3 10 10
再创建新 Pod 7 6 13 10
新 Pod Ready 4 6 10 10
最终完成 0 10 10 10

实际过程不一定严格按该表推进,因为控制器是异步工作的,Pod 调度、镜像拉取、探针检查和终止过程都可能改变中间状态。

maxSurge: 1maxUnavailable: 0 常用于要求发布期间保持全部可用副本的场景:

rollingUpdate:
  maxSurge: 1
  maxUnavailable: 0

对于四个副本,这通常意味着先创建一个新 Pod,等它满足可用条件后,再删除一个旧 Pod。代价是发布速度较慢,并且集群需要额外容纳至少一个 Pod。

maxSurge: 0maxUnavailable: 1 则倾向于先删除旧 Pod,再创建新 Pod,从而避免额外容量,但会降低发布期间的可用副本数。maxSurgemaxUnavailable 不能同时为零,否则没有任何合法的替换动作。

3.3 “Ready”不等于“可用”

Deployment 的滚动更新不能只看 Pod 是否已经创建。Pod 需要通过 readiness probe,且满足 minReadySeconds,才会计入 availableReplicas

例如:

minReadySeconds: 30

表示 Pod 必须连续处于 Ready 状态至少 30 秒,才被 Deployment 视为可用。若 Pod 短暂 Ready 后又变为 NotReady,连续计时会失效。

这会影响两个地方:

  1. 控制器何时可以继续缩减旧 ReplicaSet;
  2. Deployment 是否能够在 progressDeadlineSeconds 内完成发布。

因此,一个容器“进程已经启动”并不代表滚动更新可以继续。启动成功但 readiness probe 始终失败的 Pod,会使新 ReplicaSet存在,却不产生足够的可用副本。

3.4 Recreate 与 RollingUpdate 的区别

Deployment 还支持:

strategy:
  type: Recreate

Recreate 会先删除旧版本 Pod,再创建新版本 Pod。它不提供滚动期间的新旧版本并存,也不保证业务无中断。

如果应用不能同时运行两个版本,例如多个版本会争用不兼容的独占资源,Recreate 可能比 RollingUpdate 更合适。但这是一种明确的停机取舍,而不是滚动发布。


四、Revision:Deployment 的发布历史编号

4.1 Revision 是什么

Deployment revision 是 Deployment Controller 为 Pod 模板发布生成的历史编号。相关信息通常可以在 ReplicaSet 注解中看到:

kubectl get rs -l app=web \
  -o custom-columns=NAME:.metadata.name,REVISION:.metadata.annotations.deployment\\.kubernetes\\.io/revision,DESIRED:.spec.replicas

输出可能类似:

NAME                  REVISION   DESIRED
web-7d8f6c9b7         1          0
web-6f9d8c7b4         2          4

通常,每次 Pod 模板发生发布性变化,Deployment 会创建新的 ReplicaSet,并分配新的 revision。旧 ReplicaSet 被缩容到零,但仍可能保留,用于查看历史和回滚。

revision 不是 Pod 版本字符串,也不是镜像标签。它是 Deployment 控制器维护的发布历史序号。revision: 2 本身并不能说明运行的是哪个镜像,必须查看对应 ReplicaSet 的 Pod 模板。

kubectl rollout history deployment/web
kubectl rollout history deployment/web --revision=2

为了让历史更容易理解,可以在修改模板前记录变更原因:

kubectl annotate deployment/web \
  kubernetes.io/change-cause="upgrade nginx from 1.27 to 1.28" \
  --overwrite

kubectl set image deployment/web web=nginx:1.28

kubernetes.io/change-cause 是用于展示变更原因的注解,不是 Kubernetes Controller 用于决定发布内容的字段。没有该注解时,历史中的 CHANGE-CAUSE 可能为空。不要手工修改 deployment.kubernetes.io/revision,该注解由控制器管理。

4.2 Revision 与 ReplicaSet 的关系

每个由 Deployment 管理的 Pod 模板通常对应一个 ReplicaSet。Deployment 会根据模板哈希识别哪个 ReplicaSet是当前模板、哪个是旧模板。

发布过程中的状态可以简化为:

Deployment revision 1
    └── ReplicaSet A: 4 个旧 Pod

修改 spec.template

Deployment revision 2
    ├── ReplicaSet A: 逐步缩容
    └── ReplicaSet B: 逐步扩容

当 revision 2 完成后,ReplicaSet A 通常保留但副本数为零,ReplicaSet B 持有全部副本。

需要注意的是,revision 序号通常不会因为旧历史被删除而重新使用。历史清理后,仍然存在的 revision 号可能不连续。

4.3 revisionHistoryLimit 的边界

默认情况下,Deployment 会保留有限数量的旧 ReplicaSet。可以显式设置:

spec:
  revisionHistoryLimit: 5

该字段控制旧 ReplicaSet 的保留数量,不包括当前正在使用的 ReplicaSet。设置得太小会减少可回滚的历史;设置得太大则会保留更多 ReplicaSet 对象和模板元数据。

回滚依赖旧 ReplicaSet 中保存的 Pod 模板。如果对应 ReplicaSet 已被清理,就不能再通过 Deployment 的历史机制恢复该版本。因此,revision 历史不是无限可靠的制品仓库,镜像本身仍应使用可追溯、不会被重新指向其他内容的版本标识,例如固定版本或 digest。


五、暂停与继续:停止控制器推进发布

5.1 暂停的语义

可以暂停 Deployment:

kubectl rollout pause deployment/web

也可以直接设置:

kubectl patch deployment web \
  --type=merge \
  -p '{"spec":{"paused":true}}'

暂停后,Deployment Controller 不再推进新的滚动更新。它不会继续按原计划扩容新 ReplicaSet或缩容旧 ReplicaSet。

检查状态:

kubectl get deployment web -o jsonpath='{.spec.paused}{"\n"}'

输出:

true

暂停常用于将多个配置修改合并为一次发布。例如:

kubectl rollout pause deployment/web

kubectl set image deployment/web web=nginx:1.28
kubectl set resources deployment/web \
  --limits=cpu=500m,memory=256Mi \
  --requests=cpu=100m,memory=128Mi

kubectl rollout resume deployment/web

暂停期间的修改不会立即推进发布。恢复后,控制器根据恢复时的最终 Pod 模板进行协调。这样可以避免连续修改镜像、环境变量和资源配置时产生多次中间发布。

5.2 暂停不是回滚,也不是冻结 Pod

暂停不会:

  • 删除已经创建的 Pod;
  • 将新版本自动恢复成旧版本;
  • 阻止 Pod 因节点故障、探针失败或其他原因被替换;
  • 阻止 ReplicaSet 维持其自身的副本数;
  • 阻止用户直接修改其他 Kubernetes 对象。

它暂停的是 Deployment 发布协调过程,而不是整个集群或所有下层控制器。

如果 Deployment 正处于发布中间阶段,暂停后可能同时保留新旧两个 ReplicaSet。恢复时,控制器会从当前状态继续收敛。

kubectl rollout resume deployment/web
kubectl rollout status deployment/web --timeout=5m

暂停时不应把 ProgressDeadlineExceeded 简单解释为“暂停导致发布失败”。发布进度计时与暂停状态存在特殊处理,诊断时应同时检查 .spec.paused、Deployment conditions 和 Pod 状态。


六、回滚:恢复 Pod 模板,而不是倒转整个系统

6.1 查看和执行回滚

查看历史:

kubectl rollout history deployment/web

回滚到上一个版本:

kubectl rollout undo deployment/web

回滚到指定 revision:

kubectl rollout undo deployment/web --to-revision=1

观察回滚:

kubectl rollout status deployment/web --timeout=5m
kubectl get rs -l app=web
kubectl get pods -l app=web

回滚的本质不是把 Deployment 对象的 revision 指针移动回旧编号,而是把旧 ReplicaSet 中保存的 Pod 模板重新设置为当前 Deployment 模板。这个动作会触发一次新的发布,因此可能产生新的 revision。

例如:

revision 1: nginx:1.27
revision 2: nginx:1.28
执行 undo 到 revision 1
revision 3: nginx:1.27

此时当前版本是 revision 3,但其 Pod 模板内容与 revision 1 相同。revision 3 不是 revision 1 被“重新激活”,而是一次新的模板变更。

6.2 回滚不会恢复所有 Deployment 字段

Deployment 的回滚主要针对 Pod 模板。不能假设以下内容也会自动恢复:

  • Service 的 selector 和端口;
  • ConfigMap 或 Secret 的当前内容;
  • 数据库 schema;
  • 外部系统配置;
  • PersistentVolume 中已经写入的数据;
  • 镜像仓库中被重新覆盖的同名 tag;
  • 与 Deployment 分开管理的 HPA、PDB、Ingress 或网关规则。

例如,Pod 模板中引用了:

envFrom:
  - configMapRef:
      name: web-config

如果 web-config 的内容已经变更,Deployment 回滚到旧 revision 并不会把 ConfigMap 内容自动恢复。旧模板引用的仍然是当前的 ConfigMap 对象。

同样,回滚一个应用版本不会撤销数据库迁移。不可逆数据库变更是自动回滚最常见的边界之一。

6.3 回滚前后的验证

回滚前应先确认历史中的模板:

kubectl rollout history deployment/web --revision=1

如果需要查看精确字段:

kubectl get rs web-旧版本名称 -o yaml

回滚后至少验证:

kubectl get deployment web
kubectl get rs -l app=web
kubectl get pods -l app=web -o wide
kubectl describe deployment web

重点查看:

  • updatedReplicas 是否达到期望值;
  • availableReplicas 是否达到期望值;
  • Pod 是否处于 Ready;
  • ReplicaSet 是否报告 FailedCreate
  • Deployment conditions 是否出现 ProgressDeadlineExceeded
  • 新旧 Pod 的镜像、环境变量和探针是否符合预期。

七、发布失败:Deployment 如何表示失败

Deployment 的失败通常不是一个单一错误,而是多个层次的状态。

7.1 Pod 创建失败

如果 ReplicaSet 无法创建 Pod,常见原因包括:

  • 资源配额不足;
  • 节点没有可调度资源;
  • 镜像不存在或无法拉取;
  • ServiceAccount、Secret 或 ConfigMap 不存在;
  • Pod 安全策略、准入策略或 webhook 拒绝请求;
  • 节点选择器、亲和性或拓扑约束没有可行节点。

诊断命令:

kubectl describe deployment web
kubectl get rs -l app=web
kubectl describe rs <new-replicaset-name>
kubectl get events --sort-by=.lastTimestamp

若是 Pod 已创建但没有运行:

kubectl get pods -l app=web
kubectl describe pod <pod-name>

ReplicaSet 可能出现类似条件:

conditions:
  - type: ReplicaFailure
    status: "True"
    reason: FailedCreate

这表示底层 ReplicaSet 创建 Pod 失败。此时 Deployment 的滚动算法本身可能没有问题,故障发生在 Pod 创建、调度或准入阶段。

7.2 Pod 创建成功但不可用

另一类常见情况是 Pod 对象存在,但无法成为可用副本:

NAME                    READY   STATUS             RESTARTS
web-xxxx-yyyy           0/1     ImagePullBackOff   0
web-xxxx-zzzz           0/1     Running            5

可能原因包括:

  • 镜像拉取失败;
  • 容器不断崩溃;
  • readiness probe 路径、端口或协议错误;
  • 应用启动时间超过预期;
  • 应用启动后无法连接依赖服务;
  • 容器监听地址或端口配置错误。

应分别检查:

kubectl describe pod <pod-name>
kubectl logs <pod-name> -c web
kubectl logs <pod-name> -c web --previous

--previous 用于查看上一次已经退出的容器实例日志,适合诊断 CrashLoopBackOff。

如果 readiness probe 一直失败,Pod 可能显示 Running,但不会进入 availableReplicas。Deployment 会因此无法继续完成发布。

7.3 ProgressDeadlineExceeded:发布进度超时

Deployment 可以配置:

spec:
  progressDeadlineSeconds: 120

如果在指定时间内没有观察到足够的发布进展,Deployment 会在 conditions 中报告:

- type: Progressing
  status: "False"
  reason: ProgressDeadlineExceeded

查询:

kubectl get deployment web -o yaml
kubectl describe deployment web

“没有进展”可能表示:

  • 新 ReplicaSet 没有成功创建 Pod;
  • 新 Pod 没有变为 Ready;
  • 新 Pod 没有满足 minReadySeconds
  • 副本数一直无法达到目标;
  • 调度、镜像拉取或准入过程长期阻塞。

progressDeadlineSeconds 是失败检测阈值,不是自动修复策略。Kubernetes Deployment Controller 默认不会因为该条件自动执行回滚。回滚需要由操作者、发布系统或更高层控制器明确执行。

可以先修复原因,再等待控制器继续推进。例如修正镜像:

kubectl set image deployment/web web=nginx:1.28.1
kubectl rollout status deployment/web --timeout=5m

也可以显式回滚:

kubectl rollout undo deployment/web
kubectl rollout status deployment/web --timeout=5m

如果 Deployment 处于暂停状态,先恢复:

kubectl rollout resume deployment/web

7.4 kubectl rollout status 的含义

执行:

kubectl rollout status deployment/web --timeout=5m

该命令等待 Deployment 达到完成条件。成功时会输出类似:

deployment "web" successfully rolled out

它主要观察 Deployment 的发布状态,不等于完整的业务验证。命令成功只能说明 Kubernetes 认为期望副本已经更新并可用,不能证明:

  • HTTP 业务逻辑正确;
  • 数据库迁移安全;
  • 所有请求都成功;
  • 新版本性能满足要求;
  • Service、Ingress 或外部流量配置正确。

生产发布通常还需要业务探测、指标检查和错误率验证。


八、滚动更新中的并发和竞态

Deployment Controller 是异步控制器。kubectl set imagekubectl scalekubectl apply 提交的是新的期望状态,而不是直接执行一串同步操作。

如果在发布尚未完成时连续修改模板:

kubectl set image deployment/web web=nginx:1.28
kubectl set env deployment/web FEATURE_X=true
kubectl set image deployment/web web=nginx:1.29

控制器可能观察到多个连续期望状态,并创建或保留多个 ReplicaSet。最终状态通常会收敛到最后一次模板,但中间 ReplicaSet 是否已经创建、是否已经产生 Pod,取决于控制器处理速度和 API Server 中各次更新的时序。

暂停可以把多个修改合并:

kubectl rollout pause deployment/web
kubectl set image deployment/web web=nginx:1.29
kubectl set env deployment/web FEATURE_X=true
kubectl rollout resume deployment/web

发布系统还应避免多个操作者或流水线同时修改同一个 Deployment。即使 Kubernetes 最终能收敛,发布历史、变更原因和回滚目标也会变得难以解释。


九、失败发布的一个完整诊断流程

假设执行:

kubectl set image deployment/web web=example.invalid/web:v2
kubectl rollout status deployment/web --timeout=2m

可能得到等待超时。可以按以下因果路径排查。

第一步:确认 Deployment 期望状态

kubectl get deployment web -o wide
kubectl describe deployment web

检查:

  • desiredupdatedreadyavailable 副本数;
  • 新 ReplicaSet 的名称;
  • ProgressingAvailable conditions;
  • Events 中是否有失败信息。

第二步:确认新 ReplicaSet 是否产生

kubectl get rs -l app=web

如果新 ReplicaSet 存在但 DESIRED 为零,可能是发布已经被新的模板变化取代,或 Deployment 处于其他中间状态。应结合 Deployment 的完整 YAML 和事件判断。

如果新 ReplicaSet 的期望副本数大于零但 Pod 数量很少,继续查看:

kubectl describe rs <new-replicaset-name>

第三步:确认 Pod 卡在哪个阶段

kubectl get pods -l app=web -o wide
kubectl describe pod <new-pod-name>

例如:

  • Pending:优先检查调度、资源、亲和性和配额;
  • ImagePullBackOff:检查镜像地址、凭据和仓库可达性;
  • CrashLoopBackOff:检查容器日志和启动参数;
  • Running0/1 Ready:检查 readiness probe 和应用依赖。

第四步:选择修复或回滚

如果新版本只是镜像地址写错,可以修正模板:

kubectl set image deployment/web web=example.com/web:v2
kubectl rollout status deployment/web --timeout=5m

如果新版本本身不应继续推进,则执行:

kubectl rollout undo deployment/web
kubectl rollout status deployment/web --timeout=5m

回滚后仍要检查旧版本是否真的可用,因为失败可能来自共享依赖、节点故障、Secret 缺失或数据库状态,而不是新镜像本身。


十、Deployment、Service 和流量切换不是同一个机制

Deployment 负责 Pod 模板和 ReplicaSet 的发布;Service 负责通过 selector 选择后端 Pod并提供稳定的访问入口。

如果 Service selector 只选择:

selector:
  app: web

那么滚动更新期间,新旧 Pod 通常都会进入 Service 的后端集合。流量会在符合 selector 且通过就绪条件的 Pod 之间分配。Deployment 的 maxSurgemaxUnavailable 控制的是副本和可用性,不是精确的流量百分比。

因此,下面这些策略不能仅靠普通 Deployment 自动实现:

  • 让 5% 流量进入新版本;
  • 按用户、地域或请求头分流;
  • 两套完整环境之间切换;
  • 新旧版本同时存在但由网关按权重路由。

Canary 通常需要额外的 Service、Ingress、Gateway、服务网格或发布控制器;Blue-Green 通常需要两套独立工作负载和一次明确的流量入口切换。Deployment 的 RollingUpdate 是副本逐步替换,不等同于 Canary 或 Blue-Green。


十一、PodDisruptionBudget 不会替代 Deployment 的滚动策略

PodDisruptionBudget(PDB)用于限制自愿中断造成的可用性损失,例如节点排空时的 Eviction。它描述的是在某类中断期间需要保持多少 Pod 可用。

PDB 不应被当作 Deployment 滚动更新参数的替代品:

  • maxUnavailable 是 Deployment 发布协调的一部分;
  • PDB 主要约束自愿中断;
  • PDB 不会自动把错误版本回滚;
  • PDB 不能保证应用级请求一定成功;
  • PDB 不等价于“永远保持 N 个 Pod”。

工作负载控制器为了完成滚动更新而删除旧 Pod 时,不应简单假设 PDB 会像流量闸门一样精确限制删除行为。发布可用性主要由 Deployment strategy、readiness、minReadySeconds 和副本数共同决定;节点维护等自愿中断则需要单独评估 PDB。


十二、常见误解和边界

12.1 删除 Pod 不会回滚版本

kubectl delete pod <pod-name>

这只会删除一个运行实例。ReplicaSet 会重新创建符合当前模板的 Pod,Deployment 不会因为 Pod 被删除而恢复历史版本。

12.2 回滚镜像不等于回滚应用

Deployment 回滚的是 Pod 模板。它不会自动撤销数据库迁移、外部配置变更、消息消费副作用或已经写入的数据。

12.3 revision 不等于镜像版本

revision 4 可能使用 nginx:1.27,revision 5 也可能在回滚后再次使用 nginx:1.27。应查看 ReplicaSet 模板或 Pod 实际镜像,而不是仅凭 revision 数字判断版本。

12.4 发布成功不等于业务成功

Deployment 只知道 Pod 是否按 Kubernetes 的条件变为可用。错误的业务响应、性能下降、依赖服务异常和数据兼容问题仍需要应用指标、日志和端到端探测发现。

12.5 镜像 tag 可能使回滚结果不确定

如果多个发布都使用 app:latest,或者镜像仓库允许覆盖同名 tag,那么历史 ReplicaSet 保存的是相同字符串,而不是镜像构建内容。回滚后重新拉取的内容可能已经变化。

更可追溯的方式是使用不可变 tag 或 digest:

image: registry.example.com/web@sha256:<digest>

digest 必须替换为实际镜像摘要,不能使用占位字符串。


十三、一个可验证的发布实验

可以用以下命令观察完整生命周期:

kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3

kubectl get deployment web
kubectl get rs -l app=web
kubectl get pods -l app=app  # 此处通常不会匹配

kubectl create deployment web 默认会使用 app=web 标签,因此最后一条命令的 selector 写错了,不会匹配这些 Pod。这是一个有意保留的反例:查询 selector 必须与实际标签一致。

正确查询:

kubectl get pods -l app=web

记录变更原因并更新镜像:

kubectl annotate deployment/web \
  kubernetes.io/change-cause="upgrade to nginx 1.28" \
  --overwrite

kubectl set image deployment/web web=nginx:1.28
kubectl rollout status deployment/web --timeout=5m

观察 ReplicaSet:

kubectl get rs -l app=web

通常会看到一个旧 ReplicaSet 副本数逐步降为零,以及一个新 ReplicaSet逐步达到三个副本。

暂停一次发布:

kubectl rollout pause deployment/web
kubectl set image deployment/web web=nginx:1.29
kubectl get rs -l app=web

此时不要期待新版本立即完成。恢复:

kubectl rollout resume deployment/web
kubectl rollout status deployment/web --timeout=5m

查看历史并回滚:

kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl rollout status deployment/web --timeout=5m

这个实验可以验证四个关键事实:

  1. Deployment 通过 ReplicaSet 间接管理 Pod;
  2. Pod 模板变化会产生新的 ReplicaSet;
  3. 暂停会停止 Deployment 的发布推进;
  4. 回滚会恢复旧模板内容,但会形成一次新的发布过程。

Deployment 的可靠性不在于它保证每次发布都成功,而在于它把期望副本数、版本模板、替换策略、历史模板和进度状态明确建模出来。理解 Deployment,必须同时理解 ReplicaSet 如何维持副本、滚动更新如何在可用性约束下推进、revision 如何保存模板历史,以及失败时哪些状态会停止、哪些状态仍会继续自愈。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。