🚀 第03课:Deployment与ReplicaSet

📌 课程阶段:K8s基础(3/5)|预计时间:60分钟|难度:⭐⭐☆☆☆

一、控制器模式——K8s的核心设计

K8s的核心是控制器模式:声明期望状态→控制器持续调谐→实际状态趋向期望状态。Deployment是最常用的控制器,管理ReplicaSet,ReplicaSet管理Pod。

层级关系

┌──────────── Deployment ────────────┐
│  metadata.name: my-app             │
│  spec.replicas: 3                  │
│  spec.selector: app=my-app         │
│  spec.strategy: RollingUpdate      │
│  ┌────── ReplicaSet (v2) ──────┐   │
│  │  replicas: 3                │   │
│  │  template: image:v2.0       │   │
│  │  ┌────┐ ┌────┐ ┌────┐     │   │
│  │  │Pod1│ │Pod2│ │Pod3│     │   │
│  │  │ v2 │ │ v2 │ │ v2 │     │   │
│  │  └────┘ └────┘ └────┘     │   │
│  └────────────────────────────┘   │
│  ┌────── ReplicaSet (v1) ──────┐   │
│  │  replicas: 0 (已缩容)       │   │  ← 保留用于回滚
│  └────────────────────────────┘   │
└────────────────────────────────────┘

二、Deployment完整YAML

# deployment-complete.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: default
  labels:
    app: web-app
    version: v1
spec:
  # Pod副本数
  replicas: 3
  
  # 选择器:Deployment通过此匹配管理的Pod
  selector:
    matchLabels:
      app: web-app
    matchExpressions:
    - key: version
      operator: In
      values: ["v1", "v2"]
  
  # 保留的旧ReplicaSet数量(用于回滚)
  revisionHistoryLimit: 10
  
  # Pod就绪前的最小就绪时间
  minReadySeconds: 5
  
  # 进度截止时间
  progressDeadlineSeconds: 600
  
  # 更新策略
  strategy:
    type: RollingUpdate           # RollingUpdate | Recreate
    rollingUpdate:
      maxSurge: 1                 # 滚动更新时最多多出1个Pod(数字或百分比)
      maxUnavailable: 0           # 滚动更新时最多不可用0个Pod
  
  # Pod模板
  template:
    metadata:
      labels:
        app: web-app
        version: v1
    spec:
      containers:
      - name: web
        image: nginx:1.25
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

# ✅ 验证通过
kubectl apply -f deployment-complete.yaml --record
kubectl get deployments
# NAME      READY   UP-TO-DATE   AVAILABLE   AGE
# web-app   3/3     3            3           30s

三、滚动更新深度解析

3.1 RollingUpdate策略

滚动更新过程(maxSurge=1, maxUnavailable=0, replicas=3):

初始状态:
  [v1 Pod] [v1 Pod] [v1 Pod]     ← 3个v1 Pod

步骤1:创建1个新Pod(maxSurge=1)
  [v1 Pod] [v1 Pod] [v1 Pod] [v2 Pod:Creating]
  总数=4,可用=3 ✅

步骤2:v2就绪,删除1个v1(保持可用≥3-0=3)
  [v1 Pod] [v1 Pod] [v2 Pod:Ready] [v1 Pod:Terminating]
  总数=4→3,可用=3 ✅

步骤3:继续滚动
  [v1 Pod] [v2 Pod] [v2 Pod:Creating]
  
最终:
  [v2 Pod] [v2 Pod] [v2 Pod]     ← 3个v2 Pod

关键:maxUnavailable=0 → 零停机!

3.2 实战滚动更新

# 创建初始Deployment
kubectl create deployment roll-demo --image=nginx:1.24 --replicas=3

# ✅ 验证通过
kubectl get pods -l app=roll-demo
# NAME                         READY   STATUS    RESTARTS   AGE
# roll-demo-7d8c6f5b4d-abc12   1/1     Running   0          30s
# roll-demo-7d8c6f5b4d-def34   1/1     Running   0          30s
# roll-demo-7d8c6f5b4d-ghi56   1/1     Running   0          30s

# 触发滚动更新(更新镜像版本)
kubectl set image deployment/roll-demo nginx=nginx:1.25

# 实时观察滚动更新
kubectl rollout status deployment/roll-demo
# Waiting for deployment "roll-demo" rollout to finish: 1 out of 3 new replicas...
# Waiting for deployment "roll-demo" rollout to finish: 2 out of 3 new replicas...
# deployment "roll-demo" successfully rolled out

# ✅ 验证通过
kubectl get pods -l app=roll-demo
# NAME                         IMAGE        READY   STATUS    RESTARTS   AGE
# roll-demo-6c8f9d7b5e-abc12   nginx:1.25   1/1     Running   0          30s

3.3 Recreate策略

# Recreate:先删旧Pod,再建新Pod(有停机时间)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: recreate-demo
spec:
  replicas: 3
  strategy:
    type: Recreate    # 一次性删除所有旧Pod,再创建新Pod
  selector:
    matchLabels:
      app: recreate-demo
  template:
    metadata:
      labels:
        app: recreate-demo
    spec:
      containers:
      - name: app
        image: nginx:1.25

# 适用场景:不支持多版本并行运行(如数据库主从切换)

四、回滚操作

# 查看部署历史
kubectl rollout history deployment/roll-demo
# REVISION  CHANGE-CAUSE
# 1         kubectl create --image=nginx:1.24
# 2         kubectl set image nginx=nginx:1.25

# 查看特定版本详情
kubectl rollout history deployment/roll-demo --revision=1

# 回滚到上一版本
kubectl rollout undo deployment/roll-demo
# deployment.apps/roll-demo rolled back

# ✅ 验证通过 - 镜像已回退
kubectl get deploy roll-demo -o jsonpath='{.spec.template.spec.containers[0].image}'
# nginx:1.24

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

# 暂停/恢复滚动更新(金丝雀发布)
kubectl rollout pause deployment/roll-demo
kubectl set image deployment/roll-demo nginx=nginx:1.25
kubectl set resources deployment/roll-demo -c=nginx --limits=cpu=500m,memory=256Mi
# 不会立即生效!
kubectl rollout resume deployment/roll-demo
# 恢复后一次性应用所有变更

五、ReplicaSet详解

ReplicaSet是Deployment的底层控制器,确保指定数量的Pod副本始终运行。通常不直接使用,而是通过Deployment管理。

# replicaset-standalone.yaml(了解,生产用Deployment)
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: rs-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: rs-demo
  template:
    metadata:
      labels:
        app: rs-demo
    spec:
      containers:
      - name: app
        image: busybox:1.36
        command: ['sh', '-c', 'echo "I am $(hostname)" && sleep 3600']

# ✅ 验证通过
kubectl apply -f replicaset-standalone.yaml
kubectl get rs
# NAME       DESIRED   CURRENT   READY   AGE
# rs-demo    3         3         3       30s

# 手动删除一个Pod,RS自动重建
kubectl delete pod rs-demo-xxxxx
kubectl get pods -l app=rs-demo -w
# rs-demo-yyyyy   1/1     Running   0          5s   ← 自动新建
⚠️ ReplicaSet不支持滚动更新!修改Pod模板后需手动删除旧Pod才能生效。这就是为什么应该用Deployment。

六、扩缩容策略

6.1 手动扩缩容

# 方式1:kubectl scale
kubectl scale deployment web-app --replicas=5

# 方式2:kubectl edit
kubectl edit deployment web-app
# 修改 spec.replicas: 5

# 方式3:修改YAML后apply
# 修改YAML文件中replicas字段
kubectl apply -f deployment-complete.yaml

# ✅ 验证通过
kubectl get pods -l app=web-app
# NAME                       READY   STATUS    RESTARTS   AGE
# web-app-6c8f9d7b5e-abc12   1/1     Running   0          30s
# web-app-6c8f9d7b5e-def34   1/1     Running   0          30s
# web-app-6c8f9d7b5e-ghi56   1/1     Running   0          2m
# web-app-6c8f9d7b5e-jkl78   1/1     Running   0          2m
# web-app-6c8f9d7b5e-mno90   1/1     Running   0          2m

6.2 条件扩缩容

# 仅当当前副本数为3时才扩到5(防止并发冲突)
kubectl scale deployment web-app --replicas=5 --current-replicas=3

# 缩容到0(停止所有Pod但保留Deployment配置)
kubectl scale deployment web-app --replicas=0

七、PodDisruptionBudget(PDB)

PDB保护Deployment在自愿中断(节点维护/排空)时的可用性。

# pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-app-pdb
spec:
  minAvailable: 2          # 或 maxUnavailable: 1
  selector:
    matchLabels:
      app: web-app

# ✅ 验证通过
kubectl apply -f pdb.yaml
kubectl get pdb
# NAME          MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
# web-app-pdb   2               N/A               1                     10s

# 节点排空时,PDB确保至少2个Pod可用
kubectl drain k8s-worker1 --ignore-daemonsets
# 驱逐Pod时会遵守PDB,逐个驱逐而非全部

八、故障排查实战

8.1 Deployment不更新Pod

# 现象:kubectl apply后Pod没有变化
# 原因:spec.template没有变化(只有metadata变了)

# 检查Deployment的更新状态
kubectl rollout status deployment/web-app
# deployment "web-app" successfully rolled out  ← 如果没变化,会直接成功

# 确认Pod模板哈希
kubectl get pods -l app=web-app --show-labels | grep pod-template-hash
# pod-template-hash=7d8c6f5b4d  ← 同一个hash说明没更新

8.2 滚动更新卡住

# 现象:rollout status一直不完成
kubectl rollout status deployment/web-app
# Waiting for deployment "web-app" rollout to finish: 1 out of 3 new replicas...

# 检查原因
kubectl get deploy web-app -o jsonpath='{.status.conditions}'
# type=Progressing, reason=ProgressDeadlineExceeded

# 常见原因:
# 1. 新Pod无法就绪 → 检查readinessProbe
# 2. 镜像拉取失败 → describe pod看events
# 3. 资源不足 → 新Pod Pending

# 修复后回滚
kubectl rollout undo deployment/web-app

8.3 ReplicaSet残留过多

# 查看所有ReplicaSet
kubectl get rs -l app=web-app
# NAME                 DESIRED   CURRENT   READY   AGE
# web-app-7d8c6f5b4d   0         0         0       1h   ← 旧版本
# web-app-6c8f9d7b5e   0         0         0       2h   ← 更旧
# web-app-5b7e8a6d4c   3         3         3       3h   ← 当前

# 清理:减少revisionHistoryLimit
# spec.revisionHistoryLimit: 3  ← 只保留3个旧版本

九、练习

  1. 创建一个3副本Deployment,修改镜像触发滚动更新,用kubectl rollout status观察过程
  2. 模拟金丝雀发布:pause→更新镜像→验证→resume
  3. 执行3次滚动更新后,使用kubectl rollout undo回滚到第1个版本
  4. 创建PDB保护一个5副本Deployment,要求至少3个可用,然后kubectl drain一个节点观察效果
  5. 对比Recreate和RollingUpdate策略的停机时间差异

🏆 第03课成就解锁

下一课预告:第04课深入Service与网络——K8s服务发现与流量路由。