DaemonSet、Job 与 CronJob:节点级与任务型工作负载
Deployment 和 StatefulSet 管理的是"长期运行的服务",但 K8s 中还有两类不同的场景:每个节点都必须跑一个实例(采集日志、监控节点),以及跑完就结束的一次性或定时任务。DaemonSet、Job 和 CronJob 分别覆盖这两类场景。
DaemonSet:确保每个节点运行一个 Pod#
概念与场景#
DaemonSet 的语义很简单:集群中有 N 个节点,就运行 N 个 Pod——每个节点恰好一个。新节点加入集群时,DaemonSet 自动在上面创建 Pod;节点移除时,Pod 随之被清理。
典型场景:
| 场景 | 说明 |
|---|---|
| 日志采集 | Fluentd/Filebeat 每个节点采集容器日志,推送到中心存储 |
| 监控 Agent | Prometheus Node Exporter/Prometheus Agent 采集节点指标 |
| 网络插件 | Calico/Flannel 在每个节点配置容器网络 |
| 存储插件 | CSI Daemon Pod 在每个节点挂载存储卷 |
这些 Agent 必须在每个节点运行,用 Deployment 无法保证这一点——Deployment 的 Pod 数由 replicas 决定,不一定等于节点数,而且多个 Pod 可能调度到同一节点。
YAML 示例#
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
labels:
app: fluentd
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
tolerations: # 关键:容忍 master 节点的 taint,否则不会在 master 上运行
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd:v1.16
volumeMounts:
- name: varlog
mountPath: /var/log # 采集节点系统日志
- name: containers
mountPath: /var/log/pods # 采集容器日志(containerd 默认日志目录,Docker 时代是 /var/lib/docker/containers)
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containers
hostPath:
path: /var/log/podsnodeSelector:限定特定节点#
默认情况下,DaemonSet 在所有节点上运行。有时你只想在特定节点上部署,比如只在 GPU 节点上运行监控 Agent:
spec:
template:
spec:
nodeSelector:
gpu: "true" # 只在打了 gpu=true 标签的节点上运行也可以用 affinity.nodeAffinity 实现更灵活的节点选择(如按标签正则匹配)。DaemonSet 还自动容忍某些 taint(如 node.kubernetes.io/not-ready),确保在节点就绪前就启动关键 Agent。
tolerations 是 DaemonSet YAML 中常见配置。默认情况下,K8s master 节点打了
node-role.kubernetes.io/control-plane:NoSchedule的 taint,普通 Pod 不会调度到 master。但日志采集等 Agent 需要覆盖所有节点,所以必须加对应的 toleration。
Job:一次性任务#
概念与参数#
Job 管理的是"跑完就结束"的任务。不同于 Deployment 的 Pod 需要一直运行,Job 的 Pod 成功执行完成后(退出码 0),Job 标记为完成。
关键参数:
| 参数 | 含义 | 默认值 |
|---|---|---|
completions | 需要成功完成多少个 Pod 才算 Job 完成 | 1 |
parallelism | 同时运行多少个 Pod(并行数) | 1 |
backoffLimit | 失败重试上限,超过后 Job 标记为 Failed | 6 |
completions=1, parallelism=1:跑 1 个 Pod,最简单的场景completions=5, parallelism=2:总共要 5 个 Pod 成功完成,同时最多跑 2 个- 不设
completions、parallelism=3:3 个 Pod 并行从工作队列取任务,任一 Pod 成功后就不再新建 Pod,待所有 Pod 退出后 Job 完成(工作队列模式)。注意:若把completions显式设为 1,实际同一时刻只会跑 1 个 Pod(并行度受completions - 已成功数限制),无法真正并行
实战:数据迁移任务#
假设你需要把一批数据从旧存储迁移到新存储,任务可以并行执行但总量固定:
apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
completions: 10 # 总共 10 个子任务需要完成
parallelism: 3 # 同时跑 3 个 Pod
backoffLimit: 4 # 失败重试最多 4 次
template:
spec:
containers:
- name: migrator
image: my-migration-tool:v1
command: ["python", "migrate.py"]
env:
- name: BATCH_SIZE
value: "1000"
restartPolicy: OnFailure # Job 必须设置 OnFailure 或 Never,不能用 AlwaysrestartPolicy 说明:
OnFailure:Pod 内容器失败时,在同一 Pod 内重启容器(不新建 Pod)Never:容器失败时,创建新 Pod 重试(旧 Pod 保留用于排查)
查看 Job 状态:
kubectl get jobs
# NAME COMPLETIONS DURATION AGE
# data-migration 3/10 2m 2m
kubectl describe job data-migration
# 查看 Events 了解 Pod 创建和失败情况清理完成的 Job:
kubectl delete job data-migrationJob 完成后 Pod 默认保留,方便排查日志。但大量 Job 会堆积已完成 Pod,建议设置
ttlSecondsAfterFinished自动清理(如ttlSecondsAfterFinished: 3600,完成 1 小时后自动删除)。
CronJob:定时任务#
概念与参数#
CronJob 在 Job 之上加了时间调度,按 crontab 格式的 schedule 定期创建 Job。
schedule 格式与 Linux crontab 一致:
# ┌───────── 分钟 (0-59)
# │ ┌───────── 小时 (0-23)
# │ │ ┌───────── 日 (1-31)
# │ │ │ ┌───────── 月 (1-12)
# │ │ │ │ ┌───────── 星期 (0-6, 0=周日)
# │ │ │ │ │
# * * * * *例如:
0 2 * * *——每天凌晨 2 点*/30 * * * *——每 30 分钟0 0 1 * *——每月 1 号零点
并发策略(concurrencyPolicy)#
如果上一次 Job 还没跑完,下一次调度时间又到了,怎么处理?
| 值 | 行为 |
|---|---|
Allow | 允许新旧 Job 同时运行(默认) |
Forbid | 如果上一次 Job 还在运行,跳过本次调度 |
Replace | 如果上一次 Job 还在运行,终止旧 Job,创建新 Job |
选择建议:备份类任务用 Forbid(避免同时写两个备份);清理类任务用 Replace(旧任务可能卡住,强制替换更合理)。
实战:定时数据库备份#
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点执行
concurrencyPolicy: Forbid # 上一次没跑完就跳过
successfulJobsHistoryLimit: 3 # 只保留最近 3 次成功的 Job
failedJobsHistoryLimit: 1 # 只保留最近 1 次失败的 Job
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
containers:
- name: backup
image: mysql:8.0
command:
- bash
- "-c"
- "mysqldump -h mysql-0.mysql-headless -u root -p${MYSQL_ROOT_PASSWORD} --all-databases > /backup/all.sql"
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeMounts:
- name: backup-volume
mountPath: /backup
volumes:
- name: backup-volume
persistentVolumeClaim:
claimName: backup-pvc # 预先创建的 PVC,用于持久化备份文件
restartPolicy: OnFailure要点解读:
concurrencyPolicy: Forbid——备份任务不能并发,否则两个 mysqldump 同时锁表successfulJobsHistoryLimit: 3——只保留最近 3 次成功记录,避免 Job 堆积failedJobsHistoryLimit: 1——保留 1 次失败记录用于排查secretKeyRef——数据库密码从 Secret 读取,不硬编码在 YAML 中mysql-0.mysql-headless——引用 StatefulSet 的 Headless Service DNS 名,定向连接 master
CronJob 不适合秒级/毫秒级精度的定时场景——控制器约每 10 秒轮询一次调度,任务实际触发通常有几秒延迟。另外,控制平面重启或错过调度窗口(超过
startingDeadlineSeconds,或累计错过 100 次)时可能漏调——如果你的定时任务不允许漏调,需要在应用层面做补偿机制。
实战要点#
- DaemonSet 的 tolerations——要覆盖 master 节点,必须加
node-role.kubernetes.io/control-plane: NoSchedule的 toleration;否则 master 上的 taint 会阻止调度 - Job 的 restartPolicy——Job 模板中
restartPolicy只能是OnFailure或Never,不能是Always(Always 是 Deployment 等长期运行工作负载的默认值) - Job 清理策略——生产环境务必设置
ttlSecondsAfterFinished或手动清理已完成 Job,否则 Pod 会无限堆积 - CronJob 并发策略——备份等不允许并发的任务务必设
Forbid,避免重复执行导致数据冲突 - DaemonSet 更新——DaemonSet 支持滚动更新,且
updateStrategy默认就是RollingUpdate(改 Pod 模板后自动逐节点更新,可用maxUnavailable/maxSurge控制节奏);另一种OnDelete策略才需要手动删除 Pod 才触发更新
小结#
DaemonSet、Job 和 CronJob 是 K8s 中三类面向特殊场景的工作负载控制器。DaemonSet 保证每个节点恰好一个 Pod,适用于节点级 Agent(日志、监控、网络插件)。Job 管理一次性任务,通过 completions/parallelism/backoffLimit 控制完成数、并行度和失败重试。CronJob 在 Job 上叠加定时调度,concurrencyPolicy 控制并发行为。三者覆盖了"每个节点都要有"和"跑完就走"两类 Deployment 无法满足的场景。掌握它们,你的 K8s 工作负载知识图谱就完整了。