---
url: /courses/kubernetes-cluster/07-workload-controllers/index.md
---
# 第七节 多类型控制器编排

::: tip 项目目标
根据业务运行语义选择适当的控制器：编写 `long-running.yaml` 部署 DaemonSet 与配合 Headless Service 的 StatefulSet，体验每节点守护进程与稳定网络身份；编写 `batch.yaml` 部署 Job 与 CronJob，掌握批处理完成语义、`restartPolicy` 约束与 `concurrencyPolicy` 并发控制策略，建立结构化的工作负载选型决策表。
:::

## 7.1 控制器选型：为什么不能所有工作负载都用 Deployment

在前一课中，我们学习了 Deployment，它假设所有的 Pod 都是**无状态且完全可互换的（Stateless & Interchangeable）**。

然而，在生产环境和复杂业务系统中，并非所有组件都符合无状态假设。不同业务具备截然不同的**运行语义（Operational Semantics）**：

* **每个节点都需要运行一份日志/监控采集代理**：无法用固定副本数的 Deployment 表达，需随节点增减自动扩缩。
* **数据库主从节点需要固定名称与有序启动**：Pod 副本不能完全互换，必须拥有固定的序号与 DNS 域名。
* **数据初始化脚本运行一次成功后即可退出**：要求 Pod 成功退出后终止，不能自动重启。
* **定时巡检与数据备份任务**：要求按 Cron 时间计划周期性创建和调度任务。

根据业务运行语义，Kubernetes 提供了多类型工作负载控制器（Workload Controllers）：

| 业务诉求与运行语义 | 控制器类型 | 核心行为特征 |
| --- | --- | --- |
| **每节点守护进程** | **DaemonSet** | 保证集群中每一个（或符合选择条件的）节点上都运行且仅运行一个 Pod 副本。 |
| **有状态有序应用** | **StatefulSet** | 为每个 Pod 提供稳定递增的网络序号（如 `pod-0`）、固定 DNS 域名和持久化存储绑定。 |
| **一次性批处理任务** | **Job** | 运行一个或多个 Pod，直到指定数量的 Pod 成功执行并退出（Exit Code 0）。 |
| **周期性定时任务** | **CronJob** | 按照 Cron 时间表达式，在指定时间点周期性创建 Job 对象。 |

***

## 7.2 长期运行控制器：DaemonSet 与 StatefulSet

### 7.2.1 DaemonSet 与 StatefulSet 配置文件：`long-running.yaml`

在项目根目录下直接创建配置文件 `long-running.yaml`：

```yaml title="long-running.yaml"
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
  namespace: yunfan-shop
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      containers:
        - name: agent
          image: busybox:1.36.1
          command: ["sh", "-c", "while true; do echo node-agent; sleep 60; done"]
          resources:
            requests:
              cpu: 10m
              memory: 8Mi
            limits:
              cpu: 50m
              memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
  name: catalog-headless
  namespace: yunfan-shop
spec:
  clusterIP: None
  selector:
    app: catalog-state
  ports:
    - name: http
      port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: catalog-state
  namespace: yunfan-shop
spec:
  serviceName: catalog-headless
  replicas: 2
  selector:
    matchLabels:
      app: catalog-state
  template:
    metadata:
      labels:
        app: catalog-state
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          resources:
            requests:
              cpu: 20m
              memory: 24Mi
            limits:
              cpu: 100m
              memory: 64Mi
```

在终端中部署该配置文件：

```bash
kubectl apply -f long-running.yaml
kubectl rollout status daemonset/node-agent -n yunfan-shop --timeout=120s
kubectl rollout status statefulset/catalog-state -n yunfan-shop --timeout=120s
```

### 7.2.2 底层机制与关键语法拆解

* **DaemonSet 的节点自动关联机制**：
  * 不需要配置 `replicas` 属性。DaemonSet 控制器会自动监听集群节点变化：当新工作节点加入集群时，自动在该节点拉起一个 `node-agent` Pod；当节点从集群中移除时，Pod 被自动清理。
  * DaemonSet 默认能容忍节点的 `unschedulable` 等控制平面污点，确保系统级代理在所有节点均能覆盖。
* **StatefulSet 与 Headless Service (无头服务)**：
  * **`clusterIP: None` (Headless Service)**：指定该 Service 不分配虚拟 ClusterIP。CoreDNS 不会为该 Service 返回单个 IP，而是会为 StatefulSet 中的每一个 Pod 分配一条**固定的全限定域名 (FQDN)**：
    `catalog-state-0.catalog-headless.yunfan-shop.svc.cluster.local`
  * **稳定递增序号**：查看创建的 Pod，名称必定为 `catalog-state-0` 和 `catalog-state-1`。即使 `catalog-state-0` 发生崩溃重建，新 Pod 的名称依然保持为 `catalog-state-0`！
  * **有序部署机制**：StatefulSet 默认按照 `0 -> N-1` 的顺序依次创建 Pod，前一个 Pod 达到 Ready 后才会创建下一个；销毁时按照 `N-1 -> 0` 的逆序依次优雅终止。

运行以下命令，验证 DaemonSet 与 StatefulSet 的实际 Pod 名称：

```bash
kubectl get daemonset,statefulset,pod -n yunfan-shop -o wide
```

***

## 7.3 批处理与定时控制器：Job 与 CronJob

观察下面的批处理任务生命周期动画，理解有状态应用与批处理任务在运行时间与完成语义上的差异：

### 7.3.1 Job 与 CronJob 配置文件：`batch.yaml`

在项目根目录下直接创建配置文件 `batch.yaml`：

```yaml title="batch.yaml"
apiVersion: batch/v1
kind: Job
metadata:
  name: catalog-bootstrap
  namespace: yunfan-shop
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: task
          image: busybox:1.36.1
          command: ["sh", "-c", "echo catalog bootstrap completed"]
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: catalog-audit
  namespace: yunfan-shop
spec:
  schedule: "*/5 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: audit
              image: busybox:1.36.1
              command: ["sh", "-c", "date; echo catalog audit completed"]
```

在终端中应用该批处理配置文件：

```bash
kubectl apply -f batch.yaml
kubectl wait -n yunfan-shop --for=condition=Complete job/catalog-bootstrap --timeout=120s
```

### 7.3.2 完成语义与定时并发参数详解

* **Job 的 `restartPolicy` 硬性约束**：
  * 在 Job 中，Pod 模板的 `restartPolicy` **绝不能写为 `Always`**（否则容器成功退出后又会自动无限重启，违反了 Job 运行至完成的语义）。必须显式设置为 **`Never`**（容器失败时创建新 Pod 重试）或 **`OnFailure`**（容器失败时在原 Pod 内重启容器）。
* **`backoffLimit: 2`（失败重试上限）**：
  * 如果批处理任务命令退出码非 0，Job 控制器最多重试 2 次。超越重试上限后，Job 状态将被标记为 `Failed` 并停止重试。
* **CronJob 时间表达式与 `concurrencyPolicy`**：
  * **`schedule: "*/5 * * * *"`**：标准 5 字段 Cron 表达式（依次为：分 时 日 月 周）。`*/5 * * * *` 表示每隔 5 分钟触发一次。
  * **`concurrencyPolicy`（并发策略）**：
    * `Allow` (默认)：允许新 Job 与尚未运行完毕的旧 Job 重叠并发运行。
    * `Forbid`：**禁止并发**。如果上一个 5 分钟触发的 Job 尚未运行完，跳过本次触发。
    * `Replace`：**强行替代**。用新创建的 Job 终止并替换尚未运行完的旧 Job。
  * **历史清理限制**：`successfulJobsHistoryLimit: 2` 自动保留最近 2 次成功的 Job 记录，避免垃圾对象无限堆积。

### 7.3.3 验证 Job 与手动触发 CronJob

查看 Job 运行完成日志：

```bash
kubectl logs -n yunfan-shop job/catalog-bootstrap
```

**预期输出**：`catalog bootstrap completed`。

运行以下命令查看 CronJob 调度状态：

```bash
kubectl get cronjob,job,pod -n yunfan-shop
```

若不想等待 5 分钟的 Cron 触发点，可以命令式从 CronJob 模板手动派生生成一个测试 Job：

```bash
# 手动从 CronJob 派生创建一个单次 Job 资源进行测试
kubectl create job -n yunfan-shop --from=cronjob/catalog-audit catalog-audit-manual
kubectl wait -n yunfan-shop --for=condition=Complete job/catalog-audit-manual --timeout=120s
kubectl logs -n yunfan-shop job/catalog-audit-manual
```

***

## 7.4 控制器架构对比决策矩阵

在进行 Kubernetes 架构设计时，应当根据业务运行语义选择最契合的控制器：

| 评价维度 | Deployment | DaemonSet | StatefulSet | Job | CronJob |
| --- | --- | --- | --- | --- | --- |
| **运行时间语义** | 长期持续运行 | 长期持续运行 | 长期持续运行 | **运行至成功即终止** | **周期性创建 Job** |
| **副本扩展依据** | 显式指定 `replicas` | **自动适配集群节点数** | 显式指定 `replicas` | 指定完成数 `completions` | 依时间计划触发 |
| **Pod 身份与名称** | 随机 Hash 无状态 | 与节点主机名关联 | **固定递增序号 (`-0`, `-1`)** | 临时随机任务名称 | 按时间派生新名称 |
| **网络标识** | 共享入口 (ClusterIP) | 节点端口/HostPort | **固定 DNS FQDN 域名** | 无需稳定网络入口 | 无需稳定网络入口 |
| **典型应用场景** | Web API、微服务 | 日志采集、监控 Agent | Redis, MySQL, Kafka | 数据初始化、SQL 迁移 | 数据库备份、定时巡检 |

***

## 7.5 阶段验收与排错速查

### 7.5.1 本课提交成果清单

完成本课实训后，提交以下四项成果即可：

1. **长期运行配置文件**：项目根目录下的 `long-running.yaml`。
2. **批处理配置文件**：项目根目录下的 `batch.yaml`。
3. **第一张截图 `07-daemonset-statefulset.png`**：同一终端画面中包含 `kubectl get daemonset,statefulset,pod -n yunfan-shop -o wide`。
4. **第二张截图 `07-job-cronjob.png`**：同一终端画面中包含 `kubectl get cronjob,job,pod` 以及手动派生 Job 成功的日志输出。

{{reflection-checkpoint:workload-controllers-check}}

### 7.5.2 常见错误与排错矩阵

| 故障现象 | 可能原因 | 修复排查路径 |
| --- | --- | --- |
| DaemonSet 数量少于集群节点数 | 节点存在污点（Taint）或设置了 `nodeSelector` | 运行 `kubectl describe daemonset` 查看 Events，确认节点匹配条件或补充 Tolerations。 |
| StatefulSet 第二个 Pod 卡在 `Pending` | 前一个 Pod 尚未达到 `Ready` 状态 | StatefulSet 默认采用顺序更新，前一个 Pod 成功就绪后才会启动下一个。查看 `pod-0` 的日志。 |
| Job 不断重复创建新的 Pod | 容器内部命令退出码非 0 报错 | 运行 `kubectl describe job` 确认 `FailuresNum`；用 `kubectl logs` 查看容器失败的具体错误堆栈。 |
| CronJob 到时间未触发 Job | Cron 时间表达式语法错误或时区偏差 | 运行 `kubectl get cronjob` 查看 `SCHEDULE` 列与 `LAST SCHEDULE` 时间戳；检查 `concurrencyPolicy` 设置。 |

::: details 挑战任务：探秘 Job 的失败退避机制
尝试修改 `batch.yaml` 中的 Job 命令，将其改为 `command: ["sh", "-c", "exit 1"]` 并重新应用。观察 Pod 的创建频率与重试过程，记录 `backoffLimit` 是如何拉长重试间隔时间的。验证完成后恢复原 YAML。
:::

### 7.5.3 清理练习资源

完成验收后，清理本课创建的练习对象（保留第 6 课的 `yunfan-web` 供下一课使用）：

```bash
kubectl delete -f batch.yaml
kubectl delete -f long-running.yaml
```

***

## 本章小测

{{assessment:workload-controllers}}

## 本章小结

完成本课后，你已经掌握了：

1. **五大控制器选型逻辑**：理解了根据运行时间语义、副本扩展依据与身份要求选择不同控制器的必要性。
2. **DaemonSet 节点守护原理**：掌握了 DaemonSet 随节点增减自动维护 Pod 副本的机制。
3. **StatefulSet 稳定身份**：理清了 StatefulSet 的递增序号名称、顺序部署/销毁规则以及配合 Headless Service 产生固定 DNS 域名的原理。
4. **Job 完成语义与约束**：理解了 Job 运行至成功退出的规则，以及 `restartPolicy` 绝不能设为 `Always` 的硬性约束。
5. **CronJob 周期控制**：掌握了标准 5 字段 Cron 时间表达式、`concurrencyPolicy` 3 种并发策略以及手动派生 Job 的调试技巧。

下一课中，我们将为 `yunfan-web` 构建 **Service 服务发现与访问暴露**，深入 ClusterIP、NodePort 与 EndpointSlice 的数据包转发全路径！
