2.4. 工作负载

Pod 是最小单元,但你不应该直接管理 Pod。工作负载控制器负责副本数、发布策略、稳定身份和任务生命周期——选错控制器,后面的存储、网络、升级都会别扭。

层级:

L2 · 核心

难度:

基础到进阶

预计:

55 分钟

前置:

Pod 与容器

2.4.1. 你将学到

  • 五种工作负载的适用边界,以及如何选

  • Deployment 的滚动更新参数与回滚机制

  • ReplicaSet 的所有权与蓝绿发布切换

  • StatefulSet 的稳定身份、有序启停与卷模板

  • DaemonSet 的节点覆盖与更新策略

  • Job / CronJob 的完成语义与并发控制

  • PDB、HPA 与工作负载的配合

2.4.2. 怎么选

工作负载

适合

身份

存储

典型例子

Deployment

无状态、可水平扩展

随机

共享或无

Web、API

StatefulSet

有状态、需要稳定身份

固定序号

每副本独立 PVC

数据库、消息队列

DaemonSet

每个节点一个

跟随节点

节点本地

日志、CNI、监控代理

Job

运行到完成

随机

通常无

迁移、批处理

CronJob

定时任务

随机

通常无

备份、报表

架构图

图:工作负载选型的最简决策树。

2.4.3. Deployment

Deployment 管理 ReplicaSet,ReplicaSet 管理 Pod。这种三层结构让「滚动发布」和「回滚」成为可能。

deployment-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-demo
spec:
  replicas: 4
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 300
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  selector:
    matchLabels:
      app: demoapp
      release: stable
  template:
    metadata:
      labels:
        app: demoapp
        release: stable
    spec:
      containers:
        - name: demoapp
          image: ikubernetes/demoapp:v1.0
          ports:
            - containerPort: 80
              name: http
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
            initialDelaySeconds: 5

2.4.3.1. 发布策略

策略

行为

适用

RollingUpdate

逐步替换旧 Pod,服务不中断

大多数无状态服务

Recreate

先删光旧 Pod,再创建新 Pod

不能并存两个版本的应用

maxSurge 和 maxUnavailable 可以是绝对值或百分比:

  • maxSurge: 1:最多多出 1 个 Pod;

  • maxUnavailable: 1:最多有 1 个 Pod 不可用;

  • 两者不能同时为 0。

发布过程:

kubectl apply -f deployment-demo.yaml
kubectl rollout status deploy/deployment-demo
kubectl rollout history deploy/deployment-demo

kubectl set image deploy/deployment-demo demoapp=ikubernetes/demoapp:v1.1
kubectl rollout status deploy/deployment-demo

kubectl rollout undo deploy/deployment-demo

小技巧

发布失败自动停住

progressDeadlineSeconds 控制「多久没有进展就标记失败」。配合 kubectl rollout status --timeout=120s,可以让 CI 在发布卡住时及时失败,而不是一直等。

2.4.3.2. 常见的「发布卡住」

  1. 新 Pod readiness 一直失败;

  2. 资源不足,新 Pod Pending;

  3. 镜像拉取失败;

  4. maxUnavailable 太小,旧 Pod 不退,新 Pod 起不来。

排查:

kubectl rollout status deploy/deployment-demo
kubectl get rs -l app=demoapp
kubectl describe pod <新 Pod>
kubectl logs <新 Pod>

2.4.4. ReplicaSet 与蓝绿发布

Deployment 管理 ReplicaSet,ReplicaSet 管理 Pod。日常不需要直接操作 ReplicaSet,但理解它可以解释两件事:为什么改镜像会出现新的 ReplicaSet,以及如何用两个 ReplicaSet 做蓝绿发布。

replicaset-blue-green.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: demoapp-stable
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
      track: stable
  template:
    metadata:
      labels:
        app: demoapp
        track: stable
        version: v1.0
    spec:
      containers:
        - name: demoapp
          image: ikubernetes/demoapp:v1.0
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: demoapp-canary
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
      track: canary
  template:
    metadata:
      labels:
        app: demoapp
        track: canary
        version: v2.0
    spec:
      containers:
        - name: demoapp
          image: ikubernetes/demoapp:v2.0
          ports:
            - containerPort: 80

蓝绿的关键在 Service 的 selector:先让 Service 只选 track: stable,验证新版本 ReplicaSet 就绪后,再 patch selector 切到 track: canary。

kubectl apply -f replicaset-blue-green.yaml
kubectl expose rs demoapp-stable --name=demoapp-svc --port=80
kubectl get pods -l app=demoapp --show-labels
kubectl patch svc demoapp-svc -p '{"spec":{"selector":{"app":"demoapp","track":"canary"}}}'

切流前确认:

  • 新 ReplicaSet 的 READY 等于期望副本数;

  • 新旧版本使用兼容的数据库 schema 与 API;

  • 观察错误率与延迟,再决定保留或回滚;

  • 回滚只需把 selector 切回 track: stable。

备注

ReplicaSet 的 selector 不可变 spec.selector 创建后不可修改;标签不匹配时,ReplicaSet 可能“收养”已有 Pod。直接使用 ReplicaSet 时务必确保 selector 足够精确,生产环境优先用 Deployment。

对应原书第 8 章的 replicaset-blue-green.yaml、service-blue-green.yaml、replicaset-demo.yaml。

2.4.5. StatefulSet

StatefulSet 为每个副本提供:

  • 稳定网络标识:<pod-name>.<service-name>.<namespace>.svc.cluster.local;

  • 稳定存储:volumeClaimTemplates 为每个序号创建独立 PVC;

  • 有序生命周期:默认按 0 → N-1 创建,按 N-1 → 0 删除。

statefulset-demo.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-sts
spec:
  clusterIP: None
  selector:
    app: demoapp
    controller: sts-demo
  ports:
    - name: http
      port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sts-demo
spec:
  serviceName: demoapp-sts
  replicas: 2
  podManagementPolicy: OrderedReady
  selector:
    matchLabels:
      app: demoapp
      controller: sts-demo
  template:
    metadata:
      labels:
        app: demoapp
        controller: sts-demo
    spec:
      containers:
        - name: demoapp
          image: ikubernetes/demoapp:v1.0
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: appdata
              mountPath: /app/data
  volumeClaimTemplates:
    - metadata:
        name: appdata
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "nfs-csi"
        resources:
          requests:
            storage: 2Gi

创建后:

kubectl apply -f statefulset-demo.yaml
kubectl get pod -l controller=sts-demo -w
kubectl get pvc -l app=demoapp

预期 Pod 名为 sts-demo-0、sts-demo-1,PVC 为 appdata-sts-demo-0、appdata-sts-demo-1。

2.4.5.1. 稳定身份验证

kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- \
  nslookup sts-demo-0.demoapp-sts

预期解析到 sts-demo-0 的 Pod IP。删除 sts-demo-0 后重建,名字和 PVC 都不变。

警告

Headless Service 不是可选的

serviceName 必须指向一个 clusterIP: None 的 Service。没有它,StatefulSet 的稳定 DNS 不成立,Pod 之间无法通过序号互相发现。

2.4.5.2. 扩缩容与更新

kubectl scale sts sts-demo --replicas=3
kubectl rollout status sts/sts-demo
kubectl rollout history sts/sts-demo

StatefulSet 默认从最后一个序号开始缩容,并保留 PVC(persistentVolumeClaimRetentionPolicy 可配置删除策略)。这意味着「缩容再扩容」通常能拿回原数据。

2.4.6. DaemonSet

DaemonSet 保证每个(符合条件的)节点运行一个 Pod。典型用途:日志采集、监控代理、CNI、kube-proxy。

daemonset-demo.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
spec:
  selector:
    matchLabels:
      app: node-agent
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: agent
          image: busybox:1.36
          command: ["sh", "-c", "while true; do date; sleep 30; done"]
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
kubectl apply -f daemonset-demo.yaml
kubectl get ds node-agent
kubectl get pod -l app=node-agent -o wide

DESIRED 应该等于可调度节点数。如果某个节点没有 Pod,检查污点与 tolerations。

2.4.7. Job 与 CronJob

2.4.7.1. Job

Job 保证指定数量的 Pod 成功完成。

job-demo.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
spec:
  completions: 3
  parallelism: 2
  backoffLimit: 3
  activeDeadlineSeconds: 120
  ttlSecondsAfterFinished: 300
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: task
          image: busybox:1.36
          command: ["sh", "-c", "echo processing $HOSTNAME; sleep 5"]

字段

含义

completions

需要成功完成多少个 Pod

parallelism

最多并行多少个

backoffLimit

失败重试次数

activeDeadlineSeconds

整个 Job 的超时

ttlSecondsAfterFinished

完成后保留多久自动删除

2.4.7.2. CronJob

cronjob-demo.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cronjob-demo
spec:
  schedule: "*/2 * * * *"
  timeZone: "Asia/Shanghai"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 60
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      ttlSecondsAfterFinished: 600
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: task
              image: busybox:1.36
              command: ["sh", "-c", "date; echo hello from cronjob"]

警告

仓库中的旧 API

原仓库的 examples/jobs_and_cronjobs/cronjob-demo.yaml 使用 batch/v1beta1,该版本在 Kubernetes 1.25 已移除。请改用 batch/v1,字段结构基本一致。

concurrencyPolicy:

  • Allow:允许并发(默认);

  • Forbid:上一次没跑完就跳过本次;

  • Replace:取消上一次,启动本次。

2.4.8. PDB 与 HPA

2.4.8.1. PodDisruptionBudget

PDB 限制「自愿中断」时同时不可用的 Pod 数,例如节点维护、kubectl drain:

pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: demoapp-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: demoapp

注意:PDB 不阻止节点故障、OOM、删除 Pod 等非自愿中断。

2.4.8.2. HorizontalPodAutoscaler

hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: demoapp-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: deployment-demo
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

HPA 需要 metrics-server 或自定义指标适配器,详见 调度与弹性。

2.4.9. 实验:一次失败的滚动发布

  1. 部署 deployment-demo,确认 4 个副本 Ready;

  2. 执行:

kubectl set image deploy/deployment-demo demoapp=ikubernetes/demoapp:v9.9
kubectl rollout status deploy/deployment-demo --timeout=60s
  1. 观察 kubectl get pods:新 Pod 处于 ImagePullBackOff,旧 Pod 仍然可用(取决于 maxUnavailable)。

  2. 回滚:

kubectl rollout undo deploy/deployment-demo
kubectl rollout status deploy/deployment-demo
  1. 记录:发布过程中 kubectl get rs 出现了几个 ReplicaSet?各自副本数是多少?

2.4.10. 排错表

现象

原因

处理

Deployment 不创建 Pod

selector 与 template labels 不匹配

kubectl describe deploy

滚动更新卡住

新 Pod 不 Ready / 资源不足

rollout status、describe pod

回滚后仍是新版本

回滚到了错误的 revision

rollout history 对照

StatefulSet Pod 一直 Pending

PVC 未绑定 / StorageClass 不存在

describe pvc

StatefulSet 只起了 0 号

0 号未 Ready,后续不创建

先修 0 号

DaemonSet 少了某节点

污点未容忍

describe node 的 Taints

Job 一直不完成

completions 大于可并行数 / 任务失败

describe job、Pod 日志

CronJob 不触发

schedule 时区 / startingDeadlineSeconds

kubectl get cj -o yaml 看 lastScheduleTime

PDB 阻止 drain

minAvailable 太高

调整 PDB 或先扩容

2.4.11. 进阶挑战

  1. 把 Deployment 的 maxSurge 设为 0、maxUnavailable 设为 1,观察发布期间可用副本数。

  2. 给 StatefulSet 配置 podManagementPolicy: Parallel,对比启动顺序。

  3. 用 kubectl rollout restart 触发一次滚动重启,解释它与改镜像的区别。

  4. 创建一个失败的 Job,观察 backoffLimit 与 activeDeadlineSeconds 谁先生效。

  5. 用 PDB + kubectl drain 验证「自愿中断」被阻止的场景。

2.4.12. 小结

  • 无状态用 Deployment,有状态用 StatefulSet,节点级用 DaemonSet,任务用 Job/CronJob。

  • Deployment 的核心是 ReplicaSet 历史与滚动策略;StatefulSet 的核心是稳定身份与独立存储。

  • 发布不是 apply 完就结束,必须用 rollout status 验证,并准备好回滚。

  • 蓝绿发布用两个 ReplicaSet + Service selector 切换;回滚是切回,不是重建。

2.4.13. 延伸阅读