⚙️ 第09课:DaemonSet与Job

📌 课程阶段:存储与调度(4/5)|预计时间:60分钟|难度:⭐⭐⭐☆☆

一、DaemonSet——每节点一个Pod

DaemonSet确保每个(或特定)节点上运行一个Pod副本。节点加入集群时自动创建Pod,节点移除时自动清理。典型场景:日志采集、监控Agent、网络插件。

┌───────── DaemonSet部署模式 ──────────┐
│                                        │
│   Node1        Node2        Node3      │
│  ┌──────┐    ┌──────┐    ┌──────┐    │
│  │ Pod  │    │ Pod  │    │ Pod  │    │
│  │ fluent│    │ fluent│    │ fluent│    │
│  └──────┘    └──────┘    └──────┘    │
│     ↑            ↑            ↑       │
│  ┌──┴────────────┴────────────┴──┐    │
│  │        DaemonSet              │    │
│  │  spec.template: fluentd       │    │
│  └───────────────────────────────┘    │
│                                        │
│  典型用途:                             │
│  🔹 日志采集:Fluentd/Filebeat         │
│  🔹 监控Agent:Node Exporter           │
│  🔹 网络插件:Calico/Flannel           │
│  🔹 存储插件:CSI Node Plugin          │
│  🔹 安全Agent:Falco                   │
└────────────────────────────────────────┘

1.1 DaemonSet完整YAML

# daemonset-fluentd.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  # 更新策略
  updateStrategy:
    type: RollingUpdate           # RollingUpdate | OnDelete
    rollingUpdate:
      maxUnavailable: 1           # 最多1个节点同时更新
  # 修订历史限制
  revisionHistoryLimit: 10
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      # 容忍Master节点Taint(在Master上也运行)
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        effect: NoSchedule
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      
      serviceAccountName: fluentd
      
      containers:
      - name: fluentd
        image: fluent/fluentd:v1.16
        env:
        - name: FLUENTD_CONF
          value: "fluent.conf"
        resources:
          limits:
            cpu: "200m"
            memory: "256Mi"
          requests:
            cpu: "100m"
            memory: "128Mi"
        volumeMounts:
        - name: varlog
          mountPath: /var/log          # 宿主机日志目录
        - name: containers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: config
          mountPath: /fluentd/etc
      
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: containers
        hostPath:
          path: /var/lib/docker/containers
      - name: config
        configMap:
          name: fluentd-config

# ✅ 验证通过
kubectl apply -f daemonset-fluentd.yaml
kubectl get daemonset -n kube-system fluentd
# NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
# fluentd   3         3         3       3            3           <none>          30s

# 每个节点一个Pod
kubectl get pods -n kube-system -l app=fluentd -o wide
# NAME             NODE
# fluentd-abc12    k8s-master
# fluentd-def34    k8s-worker1
# fluentd-ghi56    k8s-worker2

1.2 DaemonSet节点过滤

# 只在特定标签节点运行
spec:
  template:
    spec:
      nodeSelector:
        type: compute              # 只在compute节点运行

# 或使用nodeAffinity
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: type
                operator: In
                values: [compute, gpu]

1.3 DaemonSet滚动更新

# 更新DaemonSet
kubectl set image daemonset/fluentd fluentd=fluent/fluentd:v1.17 -n kube-system

# 查看更新状态
kubectl rollout status daemonset/fluentd -n kube-system

# OnDelete策略:手动删除Pod才触发更新
# spec.updateStrategy.type: OnDelete
kubectl delete pod fluentd-abc12 -n kube-system  # 删除后才创建新Pod

# 回滚
kubectl rollout undo daemonset/fluentd -n kube-system

二、Job——一次性任务

Job创建一个或多个Pod,确保指定数量的Pod成功完成。适用于批处理、数据迁移、备份等一次性任务。

2.1 简单Job

# job-simple.yaml - 一次性任务
apiVersion: batch/v1
kind: Job
metadata:
  name: data-migration
spec:
  # 完成条件
  completions: 1               # 需要成功完成1次
  parallelism: 1               # 并行执行1个Pod
  backoffLimit: 3              # 最大重试次数
  activeDeadlineSeconds: 300   # 最长运行5分钟
  ttlSecondsAfterFinished: 86400  # 完成后24小时自动清理
  
  template:
    metadata:
      name: data-migration
    spec:
      restartPolicy: Never     # Job必须设为Never或OnFailure
      containers:
      - name: migrate
        image: python:3.12-slim
        command: ['python', '-c']
        args:
        - |
          import time
          print("Starting data migration...")
          for i in range(10):
              print(f"Processing batch {i+1}/10")
              time.sleep(1)
          print("Migration completed!")

# ✅ 验证通过
kubectl apply -f job-simple.yaml
kubectl get jobs
# NAME             COMPLETIONS   DURATION   AGE
# data-migration   1/1           12s        30s

kubectl logs job/data-migration
# Starting data migration...
# Processing batch 1/10
# ...
# Migration completed!

2.2 并行Job

# job-parallel.yaml - 并行处理
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-process
spec:
  completions: 10              # 需要成功完成10次
  parallelism: 3               # 同时3个Pod并行
  backoffLimit: 6
  completionMode: Indexed      # NonIndexed|Indexed
  template:
    spec:
      restartPolicy: OnFailure  # 失败后重启容器(而非新建Pod)
      containers:
      - name: worker
        image: busybox:1.36
        command: ['sh', '-c', 'echo "Worker $JOB_COMPLETION_INDEX processing" && sleep 5']
        env:
        - name: JOB_COMPLETION_INDEX  # Indexed模式自动注入
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

# ✅ 验证通过 - 3个Pod并行,总共10次完成
kubectl apply -f job-parallel.yaml
kubectl get pods -l job-name=batch-process
# NAME                  READY   STATUS      RESTARTS   AGE
# batch-process-xxx-0   0/1     Completed   0          20s
# batch-process-xxx-1   0/1     Completed   0          20s
# batch-process-xxx-2   0/1     Completed   0          20s

2.3 工作队列模式Job

# job-workqueue.yaml - 无需指定completions
apiVersion: batch/v1
kind: Job
metadata:
  name: work-queue
spec:
  # 不指定completions,由工作队列决定
  parallelism: 5
  completions: 5              # 每个Pod处理一部分
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: worker
        image: redis:7-alpine
        command: ['sh', '-c']
        args:
        - |
          # 从Redis队列取任务
          while true; do
            task=$(redis-cli -h redis LPOP task_queue)
            if [ -z "$task" ]; then
              echo "No more tasks, exiting"
              break
            fi
            echo "Processing: $task"
            sleep 1
          done

三、CronJob——定时任务

# cronjob-backup.yaml - 每天凌晨2点备份数据库
apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 2 * * *"              # Cron格式:分 时 日 月 星期
  timeZone: "Asia/Shanghai"          # K8s 1.27+支持时区
  concurrencyPolicy: Forbid          # Allow|Forbid|Replace
  successfulJobsHistoryLimit: 3      # 保留3个成功Job
  failedJobsHistoryLimit: 1          # 保留1个失败Job
  startingDeadlineSeconds: 200       # 错过执行时间的容忍秒数
  suspend: false                     # 暂停执行
  
  jobTemplate:
    spec:
      backoffLimit: 2
      ttlSecondsAfterFinished: 86400
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: mysql:8.0
            command: ['sh', '-c']
            args:
            - |
              echo "Starting backup at $(date)"
              mysqldump -h mysql-service -u$DB_USER -p$DB_PASS mydb | \
                gzip > /backup/db_$(date +%Y%m%d).sql.gz
              echo "Backup completed"
            env:
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: username
            - name: DB_PASS
              valueFrom:
                secretKeyRef:
                  name: db-creds
                  key: password
            volumeMounts:
            - name: backup-volume
              mountPath: /backup
          volumes:
          - name: backup-volume
            persistentVolumeClaim:
              claimName: backup-pvc

# ✅ 验证通过
kubectl apply -f cronjob-backup.yaml
kubectl get cronjobs
# NAME        SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
# db-backup   0 2 * * *     False     0        <none>          10s

# 手动触发
kubectl create job --from=cronjob/db-backup manual-backup

3.1 Cron表达式速查

表达式含义
*/5 * * * *每5分钟
0 * * * *每小时整点
0 2 * * *每天凌晨2点
0 0 * * 1每周一零点
0 0 1 * *每月1号零点
0,30 * * * *每小时的0分和30分

四、故障排查实战

4.1 DaemonSet Pod不在某节点运行

# 排查步骤
# 1. 检查节点Taint
kubectl describe node k8s-worker1 | grep Taints
# Taints: dedicated=special:NoSchedule

# 2. 检查DaemonSet是否有对应Toleration
kubectl get daemonset fluentd -n kube-system -o yaml | grep -A5 tolerations

# 3. 检查nodeSelector
kubectl get daemonset fluentd -n kube-system -o yaml | grep -A3 nodeSelector

4.2 Job一直不完成

# 查看Job状态
kubectl describe job data-migration
# Pods Statuses:  0 Running / 0 Succeeded / 1 Failed

# 查看失败Pod日志
kubectl logs data-migration-xxx --previous

# 常见原因:
# 1. 命令执行失败 → 检查command/args
# 2. backoffLimit已到 → 增大或修复命令
# 3. activeDeadlineSeconds超时 → 增大超时

# 删除失败Job重建
kubectl delete job data-migration
kubectl apply -f job-simple.yaml

4.3 CronJob没有按时执行

# 检查CronJob状态
kubectl describe cronjob db-backup
# 检查Last Schedule和Active Jobs

# 常见原因:
# 1. suspend: true → 改为false
# 2. 时区不对 → 配置timeZone
# 3. concurrencyPolicy: Forbid → 上一个Job还在运行
# 4. schedule格式错误 → 验证cron表达式

五、练习

  1. 创建DaemonSet部署Node Exporter,验证每个节点运行一个Pod
  2. 创建一个串行Job(completions=5, parallelism=1),观察逐个完成
  3. 创建一个并行Job(completions=10, parallelism=3),对比执行时间
  4. 创建CronJob每分钟执行一次,观察Job自动创建
  5. 配置CronJob的concurrencyPolicy,测试Forbid和Replace的区别

🏆 第09课成就解锁

下一课预告:第10课深入Ingress流量管理——七层负载均衡与路由规则。