K8s 运维系列 | 第 16 天:工作负载进阶——StatefulSet、DaemonSet 与 Job

在前面的文章中,我们重点学习了 Kubernetes 中最常见的无状态工作负载 Deployment,它凭借 ReplicaSet 与滚动更新机制,成为了绝大多数 Web 应用部署的首选。然而真实的生产环境远比”无状态服务”复杂得多:数据库需要稳定的网络标识与持久化存储,日志采集组件需要每个节点都运行一份,而批量任务则需要”跑完即退出”的一次性执行语义。这些场景下,单纯使用 Deployment 会显得捉襟见肘。

今天我们就来学习 Kubernetes 中另外三类进阶工作负载:StatefulSet(有状态应用)DaemonSet(节点级守护进程)Job/CronJob(一次性与定时任务)。掌握这三类对象之后,你将能够覆盖生产环境中绝大多数的应用编排形态,真正具备”用什么、怎么选”的判断能力。

K8s 运维 第16天

一、认识三类进阶工作负载

1.1 StatefulSet:为有状态应用而生

Deployment 管理的 Pod 是无状态的,它们共享同一个 Pod 模板,可以随意销毁重建,Pod 名称也是随机后缀。但对于 MySQL、Redis、Kafka、ZooKeeper 这类有状态应用来说,稳定身份、稳定存储、有序部署是刚需。StatefulSet 正是为此设计,它保证:

  • 稳定的网络标识:Pod 名称固定为 <statefulset-name>-<序号>,例如 mysql-0mysql-1,即使 Pod 重建名称也不会变。
  • 稳定的存储:每个 Pod 对应一个独立的 PVC(PersistentVolumeClaim),Pod 重建后依然绑定同一块存储卷。
  • 有序的部署与扩缩容:Pod 严格按照序号顺序创建与删除,mysql-0 就绪后才会创建 mysql-1

1.2 DaemonSet:每个节点运行一份

有些组件需要在集群的每一个节点上都运行一个副本,例如日志采集器 Fluentd、监控探针 Node Exporter、网络插件 Calico 的 calico-node。DaemonSet 会自动在新加入的节点上创建 Pod,并在节点下线时清理对应 Pod。它与 Deployment 的本质区别在于:副本数量不是由 replicas 指定,而是由节点数量决定。

1.3 Job 与 CronJob:一次性与定时任务

Job 用于运行一次性任务,任务完成后 Pod 退出并保持完成状态,例如数据迁移、模型训练、备份脚本。CronJob 则在 Job 之上增加了类似 Linux crontab 的定时调度能力,适合周期性的清理、报表生成、快照备份等场景。

二、实战步骤

2.1 部署一个 StatefulSet

下面以 Nginx 为例演示 StatefulSet 的稳定身份与存储。首先创建一个 Headless Service(无头服务),它是 StatefulSet 稳定网络标识的基础:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None
  selector:
    app: nginx

接着定义 StatefulSet,通过 volumeClaimTemplates 为每个 Pod 动态创建独立 PVC:

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.25
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: www
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

应用上述 YAML 并观察 Pod 的有序创建过程:

kubectl apply -f statefulset.yaml
kubectl get pods -w -l app=nginx
# 输出会看到 web-0、web-1、web-2 依次进入 Running 状态

kubectl get pvc
# 每个 Pod 都生成了独立的 PVC:www-web-0、www-web-1、www-web-2

2.2 部署一个 DaemonSet

下面部署一个使用 BusyBox 的 DaemonSet,它会自动在每个节点上运行一个 Pod:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-logger
  labels:
    app: node-logger
spec:
  selector:
    matchLabels:
      app: node-logger
  template:
    metadata:
      labels:
        app: node-logger
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: logger
          image: busybox:1.36
          args: ["sh", "-c", "while true; do echo $(hostname) $(date); sleep 60; done"]

注意上面的 tolerations 配置,它让 DaemonSet 也能调度到带有控制面污点的 master 节点上。查看部署结果:

kubectl apply -f daemonset.yaml
kubectl get ds node-logger
kubectl get pods -o wide -l app=node-logger
# 观察 DESIRED 与 READY 数量是否等于节点数量

2.3 运行 Job 与 CronJob

先运行一个简单的一次性 Job,用于计算圆周率并输出结果:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  backoffLimit: 4
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: pi
          image: perl:5.34
          command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]

再创建一个 CronJob,每天凌晨 2 点执行一次数据备份任务:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: alpine:3.18
              command: ["/bin/sh", "-c", "echo running backup; sleep 30"]

查看 Job 与 CronJob 的执行状态:

kubectl apply -f job.yaml -f cronjob.yaml
kubectl get jobs
kubectl get cronjobs
kubectl logs job/pi

三、常见问题

Q1:StatefulSet 的 Pod 删除了,数据会丢吗?

不会。StatefulSet 的 PVC 与 Pod 生命周期解耦,即使 Pod 被删除重建,它依然会重新绑定到原来那块 PVC,数据得以保留。只有当 PVC 本身被删除时,底层存储才会被回收(取决于 StorageClass 的回收策略)。

Q2:为什么 StatefulSet 必须配合 Headless Service?

Headless Service(clusterIP: None)不会做负载均衡,而是为每个 Pod 提供独立的 DNS 记录(如 web-0.nginx.default.svc.cluster.local),这是有状态应用实现主从复制、数据分片时互相定位的关键。

Q3:DaemonSet 可以只部署到部分节点吗?

可以,通过 nodeSelectornodeAffinity 来限定目标节点,例如只让日志采集器运行在标记了 logging=true 的节点上。

Q4:Job 的 restartPolicy 为什么不能设为 Always?

因为 Job 需要”任务完成后 Pod 退出”,而 Always 策略会导致容器失败时无限重启,任务永远无法判定完成。Job 的 Pod 只能使用 NeverOnFailure

四、总结

今天我们从”无状态”走向了”有状态、节点级、批处理”三类更复杂的编排形态。StatefulSet 用稳定身份与稳定存储支撑数据库等有状态服务;DaemonSet 保证守护组件覆盖集群每一个节点;Job 与 CronJob 则补齐了一次性与定时任务的能力。理解这三类对象与 Deployment 的差异,是判断”某个应用该用什么控制器部署”的基础,也是进阶到有状态应用运维与定时任务体系的关键一步。

下期预告:第 17 天我们将进入高可用集群的世界,深入剖析 etcd 集群与控制平面(kube-apiserver、kube-controller-manager、kube-scheduler)的 HA 部署方案,让你的集群告别单点故障。敬请期待!

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

昵称

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

    暂无评论内容