4、工作负载管理:Deployment滚动更新与回滚、StatefulSet有状态应用、DaemonSet守护进程、Job与CronJob

工作负载管理,说白了就是告诉Kubernetes:「你的应用该怎么跑、跑几个、出问题了怎么办」。这是日常运维中最常打交道的内容。我刚开始接触K8s时,总觉得这些概念差不多,直到线上出了几次事故才明白——每个控制器都有自己的脾气,用错了地方,后果很严重。

4.1 Deployment:滚动更新与回滚

Deployment是我们最常用的工作负载。它管理无状态应用,比如Web服务、API网关。你想想看,这类应用的特点是:任何一个Pod都可以处理请求,挂了一个,再拉一个起来就行。

滚动更新的策略

滚动更新是Deployment的默认更新方式。它不会一次性杀掉所有旧Pod,而是逐步替换。这样做的好处很明显——服务不中断。

我个人习惯在更新前先检查当前状态:

kubectl get deployment my-app -o yaml

滚动更新的核心参数有两个:

  • maxSurge:更新时最多可以超出期望副本数的比例或数量。默认25%。
  • maxUnavailable:更新时最多可以有多少个Pod不可用。默认25%。

举个例子,如果你的副本数是4,maxSurge=25%,maxUnavailable=25%。那么更新时,K8s会先启动1个新Pod(4*25%=1),同时允许1个旧Pod被干掉。整个过程就像「先建后拆」,保证总容量不降。

我的经验: 对于高并发服务,我会把maxSurge设大一些,比如50%,这样新Pod能快速就绪。但要注意资源配额,别把集群撑爆了。

回滚操作

更新出问题怎么办?别慌,Deployment支持回滚。我曾经有一次更新了镜像版本,结果新版本有内存泄漏,Pod不断重启。当时我用了这个命令:

kubectl rollout undo deployment/my-app

如果想回滚到指定版本:

kubectl rollout undo deployment/my-app --to-revision=2

查看历史版本:

kubectl rollout history deployment/my-app
注意: 回滚时,Deployment会重新应用之前的配置。如果你修改过环境变量或资源限制,回滚后这些改动也会被覆盖。我建议回滚前先备份当前配置。

4.2 StatefulSet:有状态应用

StatefulSet是为有状态应用准备的。比如数据库、消息队列、缓存。这类应用的特点是:每个Pod都有独立的身份和存储。

你想想看,MySQL主从复制,主节点和从节点能互换吗?不能。每个节点都有自己的数据,不能随便杀掉重建。这就是StatefulSet存在的意义。

核心特性

  • 稳定的网络标识:每个Pod有固定的主机名,比如mysql-0、mysql-1。Pod重建后名字不变。
  • 稳定的存储:每个Pod绑定独立的PVC,Pod挂了,数据还在。
  • 有序的部署和伸缩:Pod按顺序启动和停止,从0到N-1。

我记得有一次部署Elasticsearch集群,用的就是StatefulSet。每个节点有独立的数据卷,节点挂了重建后,数据自动挂载回来,集群自动恢复。这种体验,Deployment给不了。

配置示例

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
关键点: volumeClaimTemplates是StatefulSet独有的。它会为每个Pod自动创建PVC,命名规则是:<volume-name>-<statefulset-name>-<ordinal>

4.3 DaemonSet:守护进程

DaemonSet保证每个节点上运行一个Pod。适合做节点级别的任务,比如日志收集、监控代理、网络插件。

说白了,你不需要关心副本数,K8s会自动根据节点数量创建Pod。新节点加入集群,DaemonSet自动在新节点上启动Pod。节点被删除,Pod也会被清理。

我曾经用DaemonSet部署Fluentd收集日志。每个节点一个Pod,采集所有容器的日志。配置很简单:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
spec:
  selector:
    matchLabels:
      name: fluentd
  template:
    metadata:
      labels:
        name: fluentd
    spec:
      containers:
      - name: fluentd
        image: fluent/fluentd:v1.14
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
小技巧: 如果只想在部分节点上运行DaemonSet,可以用nodeSelector或亲和性规则。比如只在GPU节点上部署监控代理。

4.4 Job与CronJob

Job和CronJob处理的是「一次性」或「定时」任务。比如数据迁移、批量处理、定时备份。

Job:一次性任务

Job保证Pod成功运行到完成。如果Pod失败,Job会自动重试。我常用Job做数据库迁移:

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  template:
    spec:
      containers:
      - name: migration
        image: myapp/migration:1.0
        command: ["/bin/sh", "-c", "python migrate.py"]
      restartPolicy: Never
  backoffLimit: 4

这里有个坑:restartPolicy必须设为NeverOnFailure。如果设为Always,Job会一直运行,永远不会完成。

CronJob:定时任务

CronJob就是在Job外面包了一层定时器。语法和Linux的crontab一样。我举个例子,每天凌晨2点备份数据库:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: myapp/backup:1.0
            command: ["/bin/sh", "-c", "pg_dump -h db -U admin mydb > /backup/db.sql"]
          restartPolicy: OnFailure
我曾经踩过的坑: CronJob的时间是基于K8s控制节点的时间。如果控制节点时间不准,任务可能不会按时执行。另外,如果任务执行时间超过间隔时间,CronJob默认会启动新实例,可能导致任务重叠。建议设置concurrencyPolicy: Forbid来禁止并发。

总结

四种工作负载,各有各的用途:

控制器 适用场景 关键特性
Deployment 无状态应用 滚动更新、回滚、弹性伸缩
StatefulSet 有状态应用 稳定网络标识、持久化存储、有序部署
DaemonSet 节点级守护进程 每个节点一个Pod、自动扩缩
Job/CronJob 一次性/定时任务 任务完成即退出、定时触发

选对控制器,运维省一半力气。嗯,这就是我这些年总结出来的经验。