第 3 章: K8s Deployment——从 YAML 到零停机发布

5 阅读4分钟

上一章讲了 Pod。本章讲生产环境最常用的控制器 Deployment:副本管理、滚动更新、回滚、扩缩容。

3.1 为什么需要 Deployment

Pod 有几个致命缺陷:

  • 手动创建,没人盯着它,挂了不会自动重建
  • 想扩到 10 个副本,要手动创建 10 次
  • 升级版本只能先删后建,服务中断
  • 出问题了没法快速回滚

Deployment 的出现,就是为了让 Pod 变得"可管理":

Deployment 层级

Deployment(管版本、管策略)
  └── ReplicaSet(管副本数)
        └── Pod(跑容器)

Deployment 不直接管理 Pod,而是管理 ReplicaSet。每次发布新版本,Deployment 都会创建一个新的 ReplicaSet,老 ReplicaSet 上的 Pod 逐步被替换。

3.2 Deployment 的核心字段

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: dev
  labels:
    app: web
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%          # 升级时最多多出多少个 Pod
      maxUnavailable: 0      # 升级时最少允许多少个 Pod 不可用
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web             # 必须和 selector.matchLabels 一致
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 5

重点字段解读:

字段含义建议
replicas期望副本数至少 2,生产建议 3+
strategy.type升级策略,RollingUpdateRecreate生产默认 RollingUpdate
maxSurge升级时最多比期望副本多出多少25% 兼顾速度和资源
maxUnavailable升级时最多允许多少不可用设为 0 保证业务不中断
selector.matchLabels选择哪些 Pod 归这个 Deployment 管创建后不可修改

3.3 创建与日常操作

# 命令方式创建
$ kubectl create deployment web --image=nginx:alpine --replicas=3

# 查看
$ kubectl get deploy,rs,pod -l app=web
NAME                    READY   UP-TO-DATE   AVAILABLE
deployment.apps/web     3/3     3            3

NAME                               DESIRED   CURRENT   READY
replicaset.apps/web-7c4d9f8b5c     3         3         3

NAME                         READY   STATUS    AGE
pod/web-7c4d9f8b5c-2jxmt     1/1     Running   1h

UP-TO-DATE 表示当前模板版本最新的 Pod 数量。发布过程中如果看到 3/3UP-TO-DATE 1/3,说明还在滚动更新。

3.3.1 扩缩容

# 手动扩容
$ kubectl scale deployment web --replicas=5

# 自动扩缩容 HPA(第 9 章详细讲)
$ kubectl autoscale deployment web --min=2 --max=10 --cpu-percent=70

3.3.2 更新镜像

# 方式一:命令行
$ kubectl set image deployment/web nginx=nginx:1.27

# 方式二:编辑 YAML
$ kubectl edit deployment/web

3.4 滚动更新与回滚

滚动更新的本质:

滚动更新

初始状态:
  RS-old: 3/3(v1)

第 1 步:
  RS-old: 2/3  RS-new: 1/12 步:
  RS-old: 1/3  RS-new: 2/2

完成:
  RS-old: 0/3  RS-new: 3/3

在更新过程中:

$ kubectl get rs
NAME               DESIRED   CURRENT   READY
web-7c4d9f8b5c     2         2         2      ← 老版本
web-6f8d5c9b7a     2         2         2      ← 新版本

查看发布进度:

$ kubectl rollout status deployment/web
Waiting for deployment "web" rollout to finish: 2 of 3 updated replicas are available...
deployment "web" successfully rolled out

3.4.1 发布历史与回滚

# 查看发布历史
$ kubectl rollout history deployment/web
deployment.apps/web
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         kubectl set image deployment/web nginx=nginx:1.27

# 回滚到上一个版本
$ kubectl rollout undo deployment/web

# 回滚到指定版本
$ kubectl rollout undo deployment/web --to-revision=2

# 查看新状态
$ kubectl get rs
NAME               DESIRED   CURRENT   READY
web-7c4d9f8b5c     3         3         3    ← 老版本又回来了
web-6f8d5c9b7a     0         0         0

重要:Deployment 默认保留 10 个 ReplicaSet 历史版本,方便回滚。老版本不会立即删除。

3.5 零停机发布的配置技巧

要实现真正的零停机,光靠默认配置不够。核心是:新 Pod Ready 之前,老 Pod 不退出

推荐配置:

spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 最多多 1 个 Pod
      maxUnavailable: 0    # 不允许任何时刻少于 3 个可用 Pod

配合 readinessProbe 使用:新 Pod 没 Ready,就不算"可用",老的继续跑。

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3

完整的优雅发布流程:

1. 创建新 RS,起 1 个新 Pod
2. readinessProbe 通过 → 新 Pod 加入 Service Endpoints
3. 老 Pod 收到 SIGTERM
4. preStop 钩子等一会儿,让 Endpoints 列表更新
5. 老 Pod 处理完已有请求后退出
6. 继续下一轮,直到全部替换完成

3.6 金丝雀发布(进阶)

如果你想让 10% 的流量先访问新版本,验证没问题再全量发布:

# 先只发布 1 个新版本 Pod
$ kubectl set image deployment/web nginx=nginx:1.27
$ kubectl scale deployment web --replicas=10
# 这样新版本 1 个,老版本 9 个,约 10% 流量

# 验证没问题,继续全量
$ kubectl scale deployment web --replicas=10
# 后续设置 maxSurge: 100%, maxUnavailable: 0,一次性全切

更专业的金丝雀可以用 Argo Rollouts、Istio、Nginx Ingress canary 注解等。第 11 章 ArgoCD 会涉及相关的发布策略思路。

3.7 常见 Deployment 故障

现象原因排查
kubectl apply 成功但 Pod 没更新镜像 tag 没变必须改镜像名或 tag 才会触发滚动更新
滚动更新卡住readinessProbe 一直失败kubectl describe pod 看探针
新版本 rollout 后旧 RS 还在正常,保留历史版本用于回滚kubectl get rs
回滚失败老版本 RS 被清理了调大 revisionHistoryLimit
Pod 反复重启livenessProbe 太敏感或应用本身问题调整探针阈值或修 bug

3.8 本章小结

  • Deployment 通过 ReplicaSet 管理 Pod,实现自愈、扩缩、滚动更新
  • 核心字段:replicasstrategyselectortemplate
  • 滚动更新 = 新 RS 扩容 + 老 RS 缩容,历史版本保留用于回滚
  • 零停机发布三要素:maxUnavailable: 0 + readinessProbe + preStop 钩子
  • 常用命令:scaleset imagerollout statusrollout undo

3.9 课后练习

  1. 创建一个 3 副本的 Deployment,更新镜像版本,观察 kubectl get rs 中新旧 RS 数量变化。
  2. 在 Deployment 中配置 maxUnavailable: 0readinessProbe,实现零停机发布。
  3. 故意把镜像 tag 写错,看 Deployment 卡住后如何查看失败原因。
  4. 练习 kubectl rollout historykubectl rollout undo 回滚。