K8s 系列 | 第 4 天:Deployment 与 ReplicaSet:声明式应用管理


tags:
– Kubernetes
– K8s系列
– DevOps
– Deployment
– ReplicaSet
– 声明式管理
– 容器编排


K8s 系列 | 第 4 天:Deployment 与 ReplicaSet:声明式应用管理

第 4/30 天

一、引言

当我们进入 Kubernetes 的世界,核心目标是将容器化应用可靠地运行在集群中。第 3 天我们学习了 Pod——K8s 中最小的调度单元。但直接管理 Pod 并不现实:Pod 是临时性的,可能因节点故障、资源不足或人为误操作而消失。如何确保应用始终维持期望的运行副本数?如何实现无损更新和快速回滚?这正是 DeploymentReplicaSet 要解决的核心问题。

Deployment 是 Kubernetes 中最常用的工作负载资源之一,它提供声明式更新能力——你只需描述”最终状态”,K8s 的控制循环便会自动将实际状态收敛到期望状态。而 ReplicaSet 则是 Deployment 的底层基石,负责确保指定数量的 Pod 副本始终运行。

本文将从原理到实战,深入剖析 Deployment 和 ReplicaSet 的工作机制,并通过多个 YAML 示例展示声明式应用管理的全流程。


二、核心概念

2.1 ReplicaSet:Pod 副本的守护者

ReplicaSet 是 Kubernetes 中的一个控制器,它的职责很简单:确保集群中运行着指定数量的 Pod 副本。当 Pod 因故障退出或被删除时,ReplicaSet 会立即创建新的 Pod 来补足;当副本数超出预期时,它会终止多余的 Pod。

ReplicaSet 的工作原理基于标签选择器(Label Selector)控制器循环(Control Loop)

  1. 用户通过 YAML 定义 replicas(期望副本数)和 selector.matchLabels(选择哪些 Pod)
  2. ReplicaSet 控制器持续通过 API Server 监听 Pod 状态
  3. 当实际副本数偏离期望值时,控制器调用 API 创建或删除 Pod
# replicaset-example.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-replicaset
  labels:
    app: myapp
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      tier: frontend
  template:
    metadata:
      labels:
        app: myapp
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 200m
            memory: 256Mi

关键要点:
spec.selector 必须与 spec.template.metadata.labels 匹配,否则 API Server 会拒绝创建
– ReplicaSet 本身不支持滚动更新——更新 Pod 模板需要手动删除并重建所有 Pod
– 这也就是为什么实际生产中几乎不会直接使用 ReplicaSet,而是使用更高级的 Deployment

2.2 Deployment:声明式更新的终极抽象

Deployment 在 ReplicaSet 之上增加了版本管理滚动更新能力。当你定义一个 Deployment,K8s 会自动为其创建对应的 ReplicaSet,并由 Deployment 控制器协调 ReplicaSet 的版本迭代。

Deployment 的核心职责:

  1. 声明式更新:你描述”想要什么状态”,控制器负责”如何到达”
  2. 滚动更新(RollingUpdate):逐步替换旧版本 Pod,保持服务可用
  3. 回滚(Rollback):一键回到任意历史版本
  4. 扩缩容(Scale):调整副本数,增加或减少运行实例
  5. 自愈(Self-healing):Pod 故障时自动重建
# deployment-example.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    app: myapp
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      tier: frontend
  template:
    metadata:
      labels:
        app: myapp
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 200m
            memory: 256Mi
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  revisionHistoryLimit: 10

看到这里的 strategy 字段了吗?它定义了 Deployment 的更新策略,我们稍后会详细讲解。

2.3 Deployment 与 ReplicaSet 的关系

Deployment 和 ReplicaSet 之间的关系可以通过这张逻辑图理解:

Deployment
    ├── ReplicaSet v1 ─── Pod v1 (x3)
    ├── ReplicaSet v2 ─── Pod v2 (x3)    ← 当前活跃版本
    └── ReplicaSet v3 ─── Pod v3 (x3)    ← 最新版本

每次 Deployment 更新 Pod 模板时,K8s 不会修改已有的 ReplicaSet,而是创建一个新的 ReplicaSet,将旧 ReplicaSet 的副本数逐渐缩为 0,同时扩增新 ReplicaSet 的副本数。旧版本的 ReplicaSet 仍然保留(受 revisionHistoryLimit 控制),用于快速回滚。


三、实战步骤

3.1 创建 Deployment

首先,将上面的 YAML 保存为文件并应用到集群:

# 应用 Deployment
kubectl apply -f deployment-example.yaml

# 查看 Deployment 状态
kubectl get deployments -o wide

# 查看对应的 ReplicaSet
kubectl get replicasets -o wide

# 查看 Pod 详情
kubectl get pods -l app=myapp

# 输出示例:
# NAME                               READY   STATUS    RESTARTS   AGE
# myapp-deployment-7d9f8c6b4-abc12   1/1     Running   0          30s
# myapp-deployment-7d9f8c6b4-def34   1/1     Running   0          30s
# myapp-deployment-7d9f8c6b4-ghi56   1/1     Running   0          30s

注意:Pod 名称中的 7d9f8c6b4 正是对应 ReplicaSet 的哈希值,它由 Deployment 的 Pod 模板内容计算得出。

3.2 滚动更新实战

现在我们来模拟一次应用版本升级——将 Nginx 从 1.25 升级到 1.26:

# 方式一:直接修改 YAML 并重新 apply
# 修改 image: nginx:1.25 → nginx:1.26
kubectl apply -f deployment-example.yaml

# 方式二:使用 kubectl set image 在线更新(更便捷)
kubectl set image deployment/myapp-deployment nginx=nginx:1.26 --record

# 查看滚动更新过程
kubectl rollout status deployment/myapp-deployment

# 输出示例:
# Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
# deployment "myapp-deployment" successfully rolled out

在滚动过程中,Deployment 控制器会:

  1. 创建新的 ReplicaSet(例如 myapp-deployment-8f2e1c4a5),副本数为 0
  2. 根据 maxSurgemaxUnavailable 策略逐步调整新旧 ReplicaSet 的副本数
  3. 当新 Pod 通过就绪探针检查后,继续下一批替换
  4. 直到所有旧 Pod 被替换完毕
# 查看更新前后的 ReplicaSet
kubectl get replicasets -l app=myapp

# 输出示例:
# NAME                          DESIRED   CURRENT   READY   AGE
# myapp-deployment-7d9f8c6b4    0         0         0       10m
# myapp-deployment-8f2e1c4a5    3         3         3       1m

可以看到,旧的 ReplicaSet 副本数已缩减为 0,但仍然存在——这就是回滚的”回退点”。

3.3 回滚操作

如果新版本出现问题,可以快速回滚:

# 查看发布历史
kubectl rollout history deployment/myapp-deployment

# 输出示例:
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         kubectl set image deployment/myapp-deployment nginx=nginx:1.26 --record=true

# 回滚到上一个版本
kubectl rollout undo deployment/myapp-deployment

# 回滚到指定版本
kubectl rollout undo deployment/myapp-deployment --to-revision=1

# 确认回滚完成
kubectl rollout status deployment/myapp-deployment

⚠️ 注意: 回滚后 Deployment 控制器会创建一个新的 ReplicaSet,而非直接复用旧的。但旧版本 Pod 模板与新 ReplicaSet 的模板一致,因此效果相同。

3.4 扩缩容操作

# 将副本数扩展到 5
kubectl scale deployment/myapp-deployment --replicas=5

# 缩减到 2
kubectl scale deployment/myapp-deployment --replicas=2

# 查看实时变化
kubectl get pods -l app=myapp -w

四、深入理解:更新策略与参数详解

4.1 RollingUpdate 策略参数

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1          # 更新时允许超出期望副本数的最大 Pod 数(可以是数字或百分比)
    maxUnavailable: 0    # 更新时允许不可用 Pod 的最大数量(0 表示始终保持全部可用)
  • maxSurge: 1:当期望副本数为 3 时,更新期间最多同时运行 4 个 Pod(3+1)
  • maxUnavailable: 0:保证始终至少有 3 个 Pod 提供服务,零中断发布

这种配置适合对可用性要求高的生产环境。如果希望节省资源,可以设置为:

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

这表示更新过程中最多超出期望值 25%,同时最多允许 25% 的 Pod 不可用。对于 4 副本 Deployment,意味着可以最多有 1 个额外的 Pod(4 × 25% = 1),同时最多 1 个 Pod 可以被关闭。

4.2 Recreate 策略

如果应用不能同时运行多个版本(例如使用了独占资源或有状态应用),可以使用 Recreate 策略:

strategy:
  type: Recreate

注意: Recreate 策略会先终止所有旧 Pod,再创建新 Pod,这会导致短暂的停机时间。

4.3 就绪探针(Readiness Probe)的重要性

滚动更新的核心依赖于就绪探针。如果没有配置就绪探针,K8s 认为 Pod 一启动就是”就绪”状态,这会导致新 Pod 还没准备好服务就切入了流量,引发 502 错误:

# 在 Deployment 的 Pod template 中添加就绪探针
spec:
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:1.26
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 20

五、常见问题与最佳实践

Q1:为什么我的 Deployment 一直处于 “progressing” 状态?

  • 原因:新 Pod 未能通过就绪探针检查
  • 排查kubectl describe deployment/myapp-deployment 查看事件,或 kubectl logs <pod-name> 检查容器日志

Q2:滚动更新太慢怎么办?

  • 增大 maxSurgemaxUnavailable 的值
  • 减少 readinessProbe.periodSecondsfailureThreshold
  • 使用更快的镜像仓库(配置 imagePullPolicy: IfNotPresent 减少拉取)

Q3:如何暂停滚动更新?

kubectl rollout pause deployment/myapp-deployment
# 做一些金丝雀验证...
kubectl rollout resume deployment/myapp-deployment

Q4:Deployment 和 StatefulSet 有什么区别?

  • Deployment 无状态:所有 Pod 可互换,使用共享存储或云盘
  • StatefulSet 有状态:每个 Pod 有唯一标识和稳定的网络标识,适合数据库
  • 第 10 天我们会详细讲解 StatefulSet

最佳实践清单

实践 说明
✅ 总是配置就绪探针 确保滚动更新中流量无损切换
✅ 使用 --record 在修订历史中记录变更原因
✅ 设置 revisionHistoryLimit 控制历史版本数量,避免 etcd 存储膨胀
✅ 配置资源请求与限制 帮助调度器做出更好的放置决策
✅ 避免直接修改 ReplicaSet 始终通过 Deployment 管理副本
❌ 不要在生产使用 latest 标签 导致不可控的版本漂移

六、总结

今天我们系统学习了:

  • ReplicaSet 确保 Pod 副本数恒定,是控制器模式的基础实现
  • Deployment 在 ReplicaSet 之上提供了声明式更新、滚动发布和回滚能力
  • 滚动更新策略通过 maxSurgemaxUnavailable 精确控制更新期间的容量
  • 就绪探针是零中断发布的关键依赖

Deployment 是 Kubernetes 中最常用的工作负载资源,理解了它,你就掌握了声明式运维的核心思想——告诉系统你想去哪,而不是手把手教它每一步怎么走


下期预告

第 5 天我们将深入 Service 与网络基础,解析 ClusterIP、NodePort、LoadBalancer 三种 Service 类型的区别与适用场景,并手把手搭建一个从集群内到集群外的完整网络通路。

👉 系列目录K8s 30 天系列文章

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容