目录

k8s 工作负载管理

导读

Pod 是 Kubernetes 中最小的调度单元,但直接管理零散的 Pod 既无法保证副本数量,也难以应对滚动更新与故障恢复。工作负载控制器在 Pod 之上抽象出一层管理逻辑,把"期望状态"与"实际状态"的调和交给控制平面完成。掌握 Deployment、StatefulSet、DaemonSet、Job 四种核心控制器的适用边界与协作机制,是在生产集群中稳定运行业务的前提。

核心概念

Deployment 是最常用的无状态工作负载控制器,通过管理 ReplicaSet 间接管理 Pod。它声明期望的副本数与 Pod 模板,控制器持续比对实际状态,发现副本数偏差或 Pod 异常退出时自动创建新 Pod 补足。Deployment 的核心价值在于滚动更新与回滚:每次修改 Pod 模板会生成新的 ReplicaSet,旧 ReplicaSet 保留但副本数归零,从而保留版本历史。新版本出现问题时,可通过 kubectl rollout undo 快速回到上一个稳定版本。

StatefulSet 面向有状态应用,解决 Deployment 无法处理的三类需求:稳定的网络标识、有序的部署与扩缩容、持久存储与 Pod 的绑定。StatefulSet 创建的 Pod 拥有可预测的名称(如 web-0、web-1),配合 Headless Service 可获得稳定的 DNS 记录。部署时 Pod 按 0、1、2 的顺序逐个创建,销毁时按逆序回收,每个 Pod 绑定独立的 PVC,即使 Pod 重新调度,存储依然跟随。这些特性使 StatefulSet 成为运行 MySQL、Redis Cluster、ZooKeeper 等分布式存储与协调服务的首选。

DaemonSet 保证在每个(或符合条件的)节点上运行一个 Pod 副本。节点加入集群时控制器自动创建 Pod,节点移除时 Pod 自动回收。典型场景集中在节点级基础设施:日志采集(Fluentd、Filebeat)、网络插件(Calico、Cilium)、监控 Agent(Node Exporter)。这类组件需要靠近节点资源运行,用 Deployment 难以覆盖所有节点,DaemonSet 天然适配。

Job 与 CronJob 面向批处理与定时任务。Job 创建一个或多个 Pod,保证任务成功完成后才视为结束,失败按策略重试。CronJob 在 Job 之上叠加 cron 格式的调度计划,按周期触发 Job,适合数据库备份、报表生成等周期性任务。两者都不追求长期运行,而以"完成"为目标。

Deployment 的滚动更新是一个典型的状态机推进过程。控制器收到模板变更后,先创建新的 ReplicaSet,再按 maxSurge 与 maxUnavailable 参数交替扩容新 Pod 与缩容旧 Pod,直到旧副本归零、新副本达到目标数。整个过程对调用者透明,但理解其内部交互有助于排查卡在中间状态的更新。

sequenceDiagram participant U as 用户 participant DC as Deployment Controller participant RSN as 新 ReplicaSet participant RSO as 旧 ReplicaSet participant P as Pod U->>DC: kubectl apply 更新镜像 DC->>RSN: 创建新 ReplicaSet (replicas=0) DC->>RSN: 扩容新 RS (0→1) RSN->>P: 创建新 Pod (v2) P-->>DC: Ready DC->>RSO: 缩容旧 RS (3→2) DC->>RSN: 扩容新 RS (1→2) RSN->>P: 创建新 Pod (v2) P-->>DC: Ready DC->>RSO: 缩容旧 RS (2→1) DC->>RSN: 扩容新 RS (2→3) RSN->>P: 创建新 Pod (v2) P-->>DC: Ready DC->>RSO: 缩容旧 RS (1→0) DC-->>U: 滚动更新完成

图 1:Deployment 滚动更新的交互时序。控制器在扩容新 ReplicaSet 与缩容旧 ReplicaSet 之间交替推进,每一步等待新 Pod 就绪后才继续缩容旧副本,从而在更新过程中维持可用副本数下限。若新 Pod 始终无法就绪,更新会停滞在中间状态,此时需要结合 kubectl rollout status 与事件排查根因。

图解原理

四种控制器各有定位,选型错误往往是生产事故的源头。判断顺序应从应用是否需要状态切入:无状态服务优先选 Deployment,它简单可靠且支持弹性扩缩容;有状态服务需要稳定标识或持久存储,则必须用 StatefulSet。如果需求是每个节点都要运行一个副本,无论有状态与否,DaemonSet 是唯一合适的选择;而一次性或周期性任务,则交给 Job 与 CronJob。

flowchart TD A[管理工作负载] --> B{无状态服务?} B -->|是| C[推荐 Deployment] B -->|否| D{需要稳定网络标识<br/>或持久存储绑定?} D -->|是| E[推荐 StatefulSet] D -->|否| F{每个节点运行一个副本?} F -->|是| G[推荐 DaemonSet] F -->|否| H{一次性或周期性任务?} H -->|是| I[推荐 Job / CronJob] H -->|否| J[考虑裸 Pod 或自定义控制器] style C fill:#d4edda style E fill:#d4edda style G fill:#d4edda style I fill:#d4edda

图 2:工作负载选型决策树。按"无状态 → 有状态 → 每节点运行 → 批处理任务"的顺序逐层判断,每一层命中即终止,避免混淆控制器的职责边界。实际项目中可能存在交叉需求,例如 DaemonSet 运行的 Agent 也可能需要持久配置,但只要核心诉求是覆盖所有节点,就应优先满足这一约束。

StatefulSet 虽然功能强大,但运维成本高于 Deployment:扩缩容速度受顺序约束、滚动更新串行推进、存储回收需要手动干预。在能用 Deployment 解决的场景下,不要为了看起来更严谨而选用 StatefulSet。同理,Job 的并行度与重试策略需要根据任务特性精细配置,默认值并不总是合适。

动手实操

Deployment 基本操作

创建一个 Nginx Deployment,副本数为 3:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80

应用并查看状态:

kubectl apply -f nginx-deploy.yaml
kubectl get deploy,rs,pod -l app=nginx

触发滚动更新,将镜像升级到 1.26:

kubectl set image deployment/nginx-deploy nginx=nginx:1.26
kubectl rollout status deployment/nginx-deploy

查看历史版本并回滚到指定修订:

kubectl rollout history deployment/nginx-deploy
kubectl rollout undo deployment/nginx-deploy --to-revision=1

StatefulSet 部署

StatefulSet 必须配合 Headless Service 提供稳定网络标识。以下示例运行三副本 Redis,每个 Pod 绑定独立 PVC:

apiVersion: v1
kind: Service
metadata:
  name: redis-headless
spec:
  clusterIP: None
  selector:
    app: redis
  ports:
  - port: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-headless
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7
        ports:
        - containerPort: 6379
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

部署后观察 Pod 的有序创建与命名规则:

kubectl apply -f redis-statefulset.yaml
kubectl get pod -l app=redis -w
# 输出按 redis-0、redis-1、redis-2 顺序依次就绪
kubectl run dns-test --image=busybox --rm -it -- nslookup redis-0.redis-headless

DaemonSet 部署

以下 DaemonSet 在每个节点运行一个 Fluentd 日志采集器,挂载节点日志目录:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      containers:
      - name: fluentd
        image: fluent/fluentd:v1.16
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

查看每个节点的 Pod 分布情况:

kubectl apply -f fluentd-daemonset.yaml
kubectl get pod -n kube-system -l app=fluentd -o wide

Job 与 CronJob

Job 示例,执行一次数据库迁移并允许失败重试:

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  completions: 1
  parallelism: 1
  backoffLimit: 4
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: migrate
        image: myapp/migrate:latest
        command: ["./migrate", "up"]

CronJob 示例,每天凌晨 2 点执行数据库备份:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: myapp/backup:latest
            command: ["./backup.sh"]

提交并观察任务执行结果:

kubectl apply -f db-migrate-job.yaml
kubectl get job
kubectl logs job/db-migrate

kubectl apply -f db-backup-cronjob.yaml
kubectl get cronjob

常见问题与避坑

滚动更新卡住

新 Pod 长时间未就绪会阻塞整个更新流程。根因通常是 readinessProbe 配置过严或镜像拉取失败,用 kubectl describe pod 查看事件可以定位。不要直接删除卡住的 ReplicaSet,应通过 kubectl rollout undo 回退到上一个版本。必要时调大 progressDeadlineSeconds 让控制器及时判定更新失败。

StatefulSet 存储残留

StatefulSet 删除后 PVC 默认保留,反复部署测试会累积大量孤儿卷。生产环境这是保护机制,测试环境可配置 persistentVolumeClaimRetentionPolicy 在删除时自动回收。缩容有状态服务前需确认数据同步完成,否则逆序销毁 Pod 可能导致数据不一致。例如 MySQL 主从架构中,缩容前应先验证从库复制延迟归零。

Job 不退出

任务逻辑未正确返回退出码或陷入死循环时,Job 永远不会标记为完成。务必为 Job 设置执行时长上限,通过 activeDeadlineSeconds 限制单次运行时间,同时配置 backoffLimit 防止无限重试。CronJob 还需关注 concurrencyPolicy,默认 Allow 会叠加执行多个任务实例,对不支持并发的业务应改为 Forbid。

DaemonSet 资源争抢

DaemonSet Pod 与业务 Pod 共享节点资源,若不设置资源请求与限制,日志或监控 Agent 在高负载时可能挤占业务容器。建议为 DaemonSet Pod 配置较低优先级的 PriorityClass,或使用 Guaranteed QoS 配合合理 limit。通过 nodeSelectortolerations 控制覆盖范围,避免在不该运行的节点上启动。

小结与进阶

工作负载控制器的本质是把期望状态声明出来,由控制平面持续调谐实际状态,选对控制器是稳定运行的第一步。下一篇将进入服务暴露与网络通信,讨论 Service、Ingress 与 DNS 如何把分散的 Pod 组织成可访问的服务。