从零开始的云原生之旅(四):K8s 工作负载完全指南
终于搞懂 Deployment、StatefulSet、DaemonSet、CronJob 的区别了!
📖 文章目录
- 前言
- 一、为什么需要多种工作负载?
- [1.1 我在 v0.1 遇到的局限](#1.1 我在 v0.1 遇到的局限)
- [1.2 真实场景的多样性](#1.2 真实场景的多样性)
 
- [二、K8s 的 4 种核心工作负载](#二、K8s 的 4 种核心工作负载)
- [2.1 快速对比](#2.1 快速对比)
- [2.2 选择决策树](#2.2 选择决策树)
 
- 三、Deployment:无状态应用之王
- [3.1 什么是"无状态"?](#3.1 什么是"无状态"?)
- [3.2 核心特性](#3.2 核心特性)
- [3.3 适用场景](#3.3 适用场景)
 
- 四、StatefulSet:有状态应用的正确姿势
- [4.1 什么是"有状态"?](#4.1 什么是"有状态"?)
- [4.2 为什么需要 StatefulSet?](#4.2 为什么需要 StatefulSet?)
- [4.3 核心特性](#4.3 核心特性)
- [4.4 与 Deployment 的关键区别](#4.4 与 Deployment 的关键区别)
 
- 五、DaemonSet:每个节点都要有
- [5.1 什么场景需要?](#5.1 什么场景需要?)
- [5.2 核心特性](#5.2 核心特性)
- [5.3 调度策略](#5.3 调度策略)
 
- [六、Job 和 CronJob:一次性任务和定时任务](#六、Job 和 CronJob:一次性任务和定时任务)
- [6.1 为什么不用系统 cron?](#6.1 为什么不用系统 cron?)
- [6.2 Job vs CronJob](#6.2 Job vs CronJob)
- [6.3 核心特性](#6.3 核心特性)
 
- [七、实战对比:部署 Redis 的三种方式](#七、实战对比:部署 Redis 的三种方式)
- [7.1 方式一:用 Deployment(不推荐)](#7.1 方式一:用 Deployment(不推荐))
- [7.2 方式二:用 StatefulSet(推荐)](#7.2 方式二:用 StatefulSet(推荐))
- [7.3 对比总结](#7.3 对比总结)
 
- [八、选择工作负载的 5 个原则](#八、选择工作负载的 5 个原则)
- 结语
前言
大家好,我是一个正在学习云原生的 Go 开发者。
在 v0.1 中,我学会了用 Deployment 部署无状态的 API 服务,感觉很爽:
- ✅ 声明 2 个副本,K8s 自动维护
- ✅ 滚动更新,零停机发布
- ✅ Pod 挂了自动重启
但很快我就遇到问题了:
老板:"我们要部署 Redis 做缓存"
我:"好,用 Deployment 部署!"
老板:"Redis 需要持久化数据,你这样会丢数据!"
我:"啊?为啥?"
运维:"每个节点都要跑日志采集器"
我:"Deployment replicas: 3?"
运维:"不是!是每个节点一个,新节点加入也要自动部署"
我:"???"
产品:"每天凌晨 3 点清理过期数据"
我:"我写个脚本,crontab 定时执行?"
产品:"服务器重启了咋办?而且要在 K8s 集群里执行"
我:"......"我意识到:Deployment 不是万能的!
于是我花了一周时间,系统学习了 K8s 的 4 种核心工作负载:
- Deployment - 无状态应用
- StatefulSet - 有状态应用
- DaemonSet - 节点级守护进程
- Job/CronJob - 批处理和定时任务
这篇文章记录我的学习过程,重点不是罗列概念,而是:
- ✅ 为什么需要这么多工作负载?
- ✅ 它们解决什么问题?
- ✅ 实际场景中怎么选?
- ✅ 我踩过的坑!
一、为什么需要多种工作负载?
1.1 我在 v0.1 遇到的局限
v0.1 中,我用 Deployment 部署了一个 Go API:
            
            
              yaml
              
              
            
          
          apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: api
        image: cloudnative-go-api:v0.1这个方案很适合我的 API,因为:
- API 是无状态的(任何一个 Pod 都能处理请求)
- Pod 可以随意删除重建(不丢数据)
- 横向扩展很简单(加副本就行)
但当我想部署 Redis 时,问题来了:
❌ Redis 需要持久化数据(RDB/AOF 文件)
❌ Deployment 的 Pod 重建后,数据会丢失!
❌ 多个 Redis Pod 之间需要固定的网络标识(主从复制)
❌ Deployment 的 Pod 名称是随机的(api-server-7f8d9c-abcde)我第一反应:挂载 Volume 不就行了?
试了一下:
            
            
              yaml
              
              
            
          
          spec:
  template:
    spec:
      containers:
      - name: redis
        image: redis:7.4
        volumeMounts:
        - name: data
          mountPath: /data
      volumes:
      - name: data
        emptyDir: {}  # 临时目录结果:
❌ emptyDir 是临时的,Pod 重建后数据还是丢了
❌ 换成 PV,但多个 Redis Pod 共享同一个 PV 会数据冲突!
❌ 即使每个 Pod 独立 PV,重建后 PV 也对应不上了我陷入了困境:Deployment 真的不适合有状态应用!
1.2 真实场景的多样性
然后我调研了公司其他服务的需求:
| 服务类型 | 需求特点 | Deployment 能搞定吗? | 
|---|---|---|
| API 服务 | 无状态,可随意重启 | ✅ 完美 | 
| Redis/MySQL | 有状态,需要持久化数据 | ❌ 数据会丢 | 
| 日志采集器 | 每个节点必须有一个 | ❌ 不能保证每个节点 | 
| 监控 Agent | 所有节点都要安装 | ❌ 同上 | 
| 数据清理任务 | 每天凌晨执行一次 | ❌ 不是常驻服务 | 
| 数据导出任务 | 手动触发,跑完就退出 | ❌ 不需要一直运行 | 
我明白了:不同的应用模式,需要不同的部署方式!
K8s 提供了 4 种工作负载,就是为了覆盖这些场景:
┌─────────────────────────────────────────────────────┐
│                K8s 工作负载全景图                    │
├─────────────────────────────────────────────────────┤
│                                                     │
│  📦 Deployment      → 无状态应用(API、Web 前端)  │
│  🗄️  StatefulSet     → 有状态应用(数据库、缓存)  │
│  🤖 DaemonSet       → 节点守护进程(日志、监控)   │
│  ⏰ Job/CronJob     → 批处理任务(备份、清理)     │
│                                                     │
└─────────────────────────────────────────────────────┘接下来,我逐个搞懂它们!
二、K8s 的 4 种核心工作负载
2.1 快速对比(一张表看懂)
| 特性 | Deployment | StatefulSet | DaemonSet | Job/CronJob | 
|---|---|---|---|---|
| 用途 | 无状态应用 | 有状态应用 | 节点守护进程 | 批处理任务 | 
| Pod 名称 | 随机( name-xxx) | 有序( name-0,name-1) | 随机 | 随机 | 
| Pod 启动顺序 | 并行 | 顺序启动( 0→1→2) | 并行 | 根据需求 | 
| 网络标识 | 不固定 | 固定( name-0.service) | 不固定 | 不固定 | 
| 存储 | 共享或临时 | 每个 Pod 独立 PVC | 通常 hostPath | 临时或共享 | 
| 副本数 | 手动指定 | 手动指定 | 每个节点一个 | 不适用 | 
| 扩缩容 | 随意 | 按序( 2→1→0) | 跟随节点 | 不适用 | 
| 更新策略 | 滚动更新 | 滚动更新(有序) | 滚动更新 | 重新创建 Job | 
| 生命周期 | 长期运行 | 长期运行 | 长期运行 | 运行完退出 | 
| 典型场景 | API、Web 前端 | 数据库、消息队列 | 日志采集、监控 | 备份、清理、训练 | 
我的理解:
- Deployment:像外卖员,谁接单都行
- StatefulSet:像班级学生,有固定座位和学号
- DaemonSet:像路灯,每个路口都要有一个
- CronJob:像闹钟,时间到了就响一次
2.2 选择决策树
我整理了一个快速决策流程:
开始
  ↓
需要长期运行吗?
  ├─ 否 → 【Job/CronJob】
  │       ├─ 只跑一次 → Job
  │       └─ 定时执行 → CronJob
  │
  └─ 是 → 需要持久化数据吗?
          ├─ 否 → 【Deployment】
          │       (API、Web、代理等)
          │
          └─ 是 → 需要固定网络标识吗?
                  ├─ 否 → 【Deployment + PV】
                  │       (可以接受随机重启)
                  │
                  └─ 是 → 每个节点都要运行吗?
                          ├─ 是 → 【DaemonSet】
                          │       (日志采集、监控 Agent)
                          │
                          └─ 否 → 【StatefulSet】
                                  (数据库、缓存、消息队列)实战建议:
- 90% 的无状态服务用 Deployment
- 数据库、缓存用 StatefulSet
- 日志、监控用 DaemonSet
- 定时任务用 CronJob
三、Deployment:无状态应用之王
3.1 什么是"无状态"?
我的通俗理解:
无状态 = 所有 Pod 都是"一样的"
比如外卖系统:
  - 客户点餐请求可以被任何配送员接单
  - 配送员 A 请假了,配送员 B 顶上
  - 客户不关心是谁送的,只要送到就行
对应到 API:
  - 任何一个 API Pod 都能处理请求
  - Pod 重启后,不影响业务
  - 删掉一个 Pod,另一个 Pod 接管流量技术定义:
- Pod 不保存本地状态
- 所有状态保存在外部(数据库、缓存、对象存储)
- Pod 可以随意创建、删除、重启
3.2 核心特性
① 自动维护副本数
            
            
              yaml
              
              
            
          
          spec:
  replicas: 3K8s 会确保始终有 3 个 Pod 运行:
- Pod 挂了?立即创建新的
- 手动删除 Pod?自动补上
- 节点宕机?在其他节点重建 Pod
我的测试:
            
            
              powershell
              
              
            
          
          # 查看 Pod
kubectl get pods
# NAME                          READY   STATUS    RESTARTS   AGE
# api-server-7f8d9c-abcde       1/1     Running   0          5m
# api-server-7f8d9c-fghij       1/1     Running   0          5m
# api-server-7f8d9c-klmno       1/1     Running   0          5m
# 删掉一个 Pod
kubectl delete pod api-server-7f8d9c-abcde
# 立即查看,已经创建了新的 Pod
kubectl get pods
# NAME                          READY   STATUS    RESTARTS   AGE
# api-server-7f8d9c-fghij       1/1     Running   0          6m
# api-server-7f8d9c-klmno       1/1     Running   0          6m
# api-server-7f8d9c-pqrst       1/1     Running   0          3s  ← 新创建的太强了!K8s 自动保证 3 个副本!
② 滚动更新(零停机部署)
            
            
              yaml
              
              
            
          
          spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1  # 最多 1 个 Pod 不可用
      maxSurge: 1        # 最多多创建 1 个 Pod更新流程:
原有 3 个 Pod(v1.0):
  [Pod-1] [Pod-2] [Pod-3]
更新镜像到 v1.1:
  第1步:创建 1 个 v1.1 Pod
    [Pod-1 v1.0] [Pod-2 v1.0] [Pod-3 v1.0] [Pod-4 v1.1] ← 多一个
  第2步:删除 1 个 v1.0 Pod
    [Pod-2 v1.0] [Pod-3 v1.0] [Pod-4 v1.1]
  第3步:再创建 1 个 v1.1 Pod
    [Pod-2 v1.0] [Pod-3 v1.0] [Pod-4 v1.1] [Pod-5 v1.1]
  第4步:删除 1 个 v1.0 Pod
    [Pod-3 v1.0] [Pod-4 v1.1] [Pod-5 v1.1]
  ...依此类推
最终:
  [Pod-4 v1.1] [Pod-5 v1.1] [Pod-6 v1.1]好处:
- ✅ 始终有 Pod 在服务
- ✅ 用户无感知
- ✅ 发现问题可以随时回滚
③ 快速扩缩容
            
            
              bash
              
              
            
          
          # 扩容到 5 个副本
kubectl scale deployment api-server --replicas=5
# 缩容到 1 个副本
kubectl scale deployment api-server --replicas=1
# 自动扩缩容(HPA)
kubectl autoscale deployment api-server --min=2 --max=10 --cpu-percent=803.3 适用场景
✅ 适合 Deployment:
- Web 应用(前端、后端 API)
- 微服务(订单服务、用户服务)
- 无状态中间件(Nginx、API 网关)
- Serverless 函数
❌ 不适合 Deployment:
- 数据库(MySQL、PostgreSQL、MongoDB)
- 缓存(Redis、Memcached)
- 消息队列(Kafka、RabbitMQ)
- 分布式存储(Ceph、MinIO)
为什么不适合?因为这些应用需要:
- 固定的网络标识(主从复制、集群选举)
- 独立的持久化存储(每个实例存不同的数据)
- 有序的启动/停止(主节点先启动,从节点后启动)
这就需要 StatefulSet!
四、StatefulSet:有状态应用的正确姿势
4.1 什么是"有状态"?
我的通俗理解:
有状态 = 每个 Pod 都是"独特的"
比如班级座位:
  - 小明坐第 1 排第 2 座
  - 小红坐第 2 排第 3 座
  - 座位号是固定的,不能随便换
对应到 Redis:
  - redis-0 是主节点
  - redis-1 是从节点
  - redis-0 挂了,不能随便拿个 Pod 顶替,必须是原来的 redis-0技术定义:
- 每个 Pod 有固定的名称(name-0,name-1,name-2)
- 每个 Pod 有固定的网络标识(name-0.service)
- 每个 Pod 有独立的持久化存储(PVC)
- Pod 重建后,名称、标识、存储都不变
4.2 为什么需要 StatefulSet?
我用 Deployment 部署 Redis 时遇到的问题:
❌ 问题 1:Pod 名称不固定
            
            
              bash
              
              
            
          
          # Deployment 的 Pod 名称是随机的
kubectl get pods
# NAME                     READY   STATUS    RESTARTS   AGE
# redis-7f8d9c-abcde       1/1     Running   0          1m
# redis-7f8d9c-fghij       1/1     Running   0          1m
# Pod 重启后,名称变了!
# redis-7f8d9c-pqrst       1/1     Running   0          10s为什么这是问题?
假设我配置了 Redis 主从:
- redis-7f8d9c-abcde是主节点
- redis-7f8d9c-fghij是从节点,配置了- slaveof redis-7f8d9c-abcde
主节点重启后:
- 名称变成 redis-7f8d9c-pqrst
- 从节点还在连接 redis-7f8d9c-abcde(已经不存在了)
- 主从复制断了!
❌ 问题 2:存储对应不上
            
            
              yaml
              
              
            
          
          # Deployment + PV
spec:
  template:
    spec:
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: redis-pvc  # 所有 Pod 共享一个 PVC问题:
- 多个 Redis Pod 写同一个 PV → 数据冲突!
- 给每个 Pod 分配独立 PVC?Pod 重建后对应不上
✅ StatefulSet 的解决方案:
            
            
              yaml
              
              
            
          
          apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service  # 必须指定 Headless Service
  replicas: 3
  template:
    spec:
      containers:
      - name: redis
        image: redis:7.4
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:  # 自动为每个 Pod 创建 PVC
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi创建后:
            
            
              bash
              
              
            
          
          kubectl get pods
# NAME       READY   STATUS    RESTARTS   AGE
# redis-0    1/1     Running   0          1m  ← 固定名称
# redis-1    1/1     Running   0          1m
# redis-2    1/1     Running   0          1m
kubectl get pvc
# NAME           STATUS   VOLUME    CAPACITY   ACCESS MODES
# data-redis-0   Bound    pv-001    1Gi        RWO  ← 每个 Pod 独立 PVC
# data-redis-1   Bound    pv-002    1Gi        RWO
# data-redis-2   Bound    pv-003    1Gi        RWOPod 重启后:
- Pod 名称还是 redis-0
- PVC 还是 data-redis-0
- 数据不丢失!
4.3 核心特性
① 固定的 Pod 名称
Deployment:
  redis-7f8d9c-abcde  ← 随机后缀
  redis-7f8d9c-fghij
  redis-7f8d9c-klmno
StatefulSet:
  redis-0  ← 从 0 开始递增
  redis-1
  redis-2规则:
- 名称格式:$(statefulset-name)-$(ordinal)
- Ordinal 从 0 开始
- Pod 删除重建后,名称不变
② 固定的网络标识
StatefulSet 必须配合 Headless Service 使用:
            
            
              yaml
              
              
            
          
          # Headless Service
apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  clusterIP: None  # 这是关键!
  selector:
    app: redis
  ports:
  - port: 6379每个 Pod 有独立的 DNS 记录:
redis-0.redis-service.default.svc.cluster.local
redis-1.redis-service.default.svc.cluster.local
redis-2.redis-service.default.svc.cluster.local好处:
            
            
              go
              
              
            
          
          // 应用可以直接通过固定域名访问特定 Pod
masterAddr := "redis-0.redis-service:6379"  // 永远是主节点
slaveAddr := "redis-1.redis-service:6379"   // 永远是从节点③ 独立的持久化存储
volumeClaimTemplates 会自动:
- 为每个 Pod 创建独立的 PVC
- PVC 名称:$(volumeClaimTemplate-name)-$(pod-name)
- Pod 删除后,PVC 不删除(数据保留)
- Pod 重建后,自动绑定原来的 PVC
示例:
            
            
              yaml
              
              
            
          
          volumeClaimTemplates:
- metadata:
    name: data  # PVC 名称前缀
  spec:
    accessModes: [ "ReadWriteOnce" ]
    resources:
      requests:
        storage: 1Gi生成的 PVC:
data-redis-0  →  绑定到 redis-0
data-redis-1  →  绑定到 redis-1
data-redis-2  →  绑定到 redis-2④ 有序部署和终止
部署顺序:
创建 3 个副本的 StatefulSet:
第1步:创建 redis-0,等待 Running
  [redis-0 Creating...]
第2步:redis-0 Ready 后,创建 redis-1
  [redis-0 Running] [redis-1 Creating...]
第3步:redis-1 Ready 后,创建 redis-2
  [redis-0 Running] [redis-1 Running] [redis-2 Creating...]
完成:
  [redis-0 Running] [redis-1 Running] [redis-2 Running]终止顺序(缩容):
从 3 个副本缩容到 1 个:
第1步:删除 redis-2
  [redis-0 Running] [redis-1 Running] [redis-2 Terminating]
第2步:redis-2 删除完成后,删除 redis-1
  [redis-0 Running] [redis-1 Terminating]
完成:
  [redis-0 Running]为什么要有序?
假设 Redis 主从:
- redis-0是主节点
- redis-1和- redis-2是从节点
有序启动:
- 先启动 redis-0(主节点)
- 再启动 redis-1、redis-2(从节点连接主节点)
有序停止:
- 先停止 redis-2、redis-1(从节点)
- 最后停止 redis-0(主节点)
保证数据不丢失!
4.4 与 Deployment 的关键区别
| 特性 | Deployment | StatefulSet | 
|---|---|---|
| Pod 名称 | name-随机 | name-0,name-1, ... | 
| Pod 标识 | 不固定 | 固定(DNS 记录) | 
| 存储 | 共享或临时 | 每个 Pod 独立 PVC | 
| 启动顺序 | 并行 | 顺序(0→1→2) | 
| 停止顺序 | 并行 | 倒序(2→1→0) | 
| 扩缩容 | 随意 | 有序 | 
| 适用场景 | 无状态应用 | 有状态应用 | 
记忆口诀:
Deployment = 随机的、一次性的、可替换的
StatefulSet = 固定的、有序的、不可替换的五、DaemonSet:每个节点都要有
5.1 什么场景需要?
我遇到的需求:
运维:"我们要在每个节点上部署日志采集器,收集 /var/log/ 的日志"
我的第一反应:用 Deployment?
            
            
              yaml
              
              
            
          
          apiVersion: apps/v1
kind: Deployment
metadata:
  name: log-collector
spec:
  replicas: 3  # 假设集群有 3 个节点运维:"不对!如果集群扩容到 5 个节点呢?你要手动改 replicas?"
我:"对哦!而且 Deployment 不保证每个节点都有一个 Pod!"
            
            
              bash
              
              
            
          
          # Deployment 可能把 3 个 Pod 都调度到同一个节点
kubectl get pods -o wide
# NAME                  NODE
# log-collector-xxx     node-1
# log-collector-yyy     node-1  ← 都在 node-1
# log-collector-zzz     node-1这就需要 DaemonSet!
5.2 核心特性
① 每个节点自动运行一个 Pod
            
            
              yaml
              
              
            
          
          apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-collector
spec:
  selector:
    matchLabels:
      app: log-collector
  template:
    metadata:
      labels:
        app: log-collector
    spec:
      containers:
      - name: collector
        image: log-collector:v1.0
        volumeMounts:
        - name: varlog
          mountPath: /var/log
          readOnly: true
      volumes:
      - name: varlog
        hostPath:
          path: /var/log  # 挂载宿主机的 /var/log创建后:
            
            
              bash
              
              
            
          
          kubectl get pods -o wide
# NAME                  NODE       READY   STATUS
# log-collector-aaa     node-1     1/1     Running
# log-collector-bbb     node-2     1/1     Running
# log-collector-ccc     node-3     1/1     Running
# 每个节点都有一个 Pod!集群扩容时:
            
            
              bash
              
              
            
          
          # 新加入一个 node-4
kubectl get nodes
# NAME     STATUS   AGE
# node-1   Ready    10d
# node-2   Ready    10d
# node-3   Ready    10d
# node-4   Ready    1m   ← 新节点
# DaemonSet 自动在 node-4 创建 Pod
kubectl get pods -o wide
# NAME                  NODE       READY   STATUS
# log-collector-aaa     node-1     1/1     Running
# log-collector-bbb     node-2     1/1     Running
# log-collector-ccc     node-3     1/1     Running
# log-collector-ddd     node-4     1/1     Running  ← 自动创建!太智能了!
② 节点下线,Pod 自动清理
            
            
              bash
              
              
            
          
          # node-2 下线
kubectl drain node-2
# node-2 上的 Pod 自动删除
kubectl get pods -o wide
# NAME                  NODE       READY   STATUS
# log-collector-aaa     node-1     1/1     Running
# log-collector-ccc     node-3     1/1     Running
# log-collector-ddd     node-4     1/1     Running③ 访问宿主机资源
DaemonSet 常用 hostPath 访问宿主机资源:
            
            
              yaml
              
              
            
          
          volumes:
- name: varlog
  hostPath:
    path: /var/log  # 宿主机的 /var/log
- name: docker-sock
  hostPath:
    path: /var/run/docker.sock  # 访问 Docker
- name: sys
  hostPath:
    path: /sys  # 访问系统信息典型场景:
- 日志采集 :读取 /var/log/
- 监控 Agent :读取 /proc/、/sys/
- 网络插件:操作宿主机网络
5.3 调度策略
① 默认:在所有节点运行
            
            
              yaml
              
              
            
          
          apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-collector
spec:
  template:
    spec:
      containers:
      - name: collector
        image: log-collector:v1.0结果:每个节点都运行一个 Pod
② 通过 nodeSelector 限制节点
            
            
              yaml
              
              
            
          
          spec:
  template:
    spec:
      nodeSelector:
        role: worker  # 只在有这个标签的节点运行
      containers:
      - name: collector
        image: log-collector:v1.0给节点打标签:
            
            
              bash
              
              
            
          
          # 给 node-1 和 node-2 打标签
kubectl label node node-1 role=worker
kubectl label node node-2 role=worker
# 只在 node-1 和 node-2 运行
kubectl get pods -o wide
# NAME                  NODE       READY   STATUS
# log-collector-aaa     node-1     1/1     Running
# log-collector-bbb     node-2     1/1     Running③ 通过 tolerations 在特殊节点运行
K8s 的 Master 节点默认有污点(Taint),不允许 Pod 调度:
            
            
              bash
              
              
            
          
          kubectl describe node master | Select-String "Taints"
# Taints: node-role.kubernetes.io/control-plane:NoSchedule如果要在 Master 上也运行 DaemonSet:
            
            
              yaml
              
              
            
          
          spec:
  template:
    spec:
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      containers:
      - name: collector
        image: log-collector:v1.0现在 Master 上也会运行 Pod!
典型应用场景
| 场景 | 说明 | 示例 | 
|---|---|---|
| 日志采集 | 收集每个节点的日志 | Fluentd, Filebeat | 
| 监控 Agent | 监控节点性能 | Node Exporter, Datadog Agent | 
| 网络插件 | 管理节点网络 | Calico, Flannel | 
| 存储插件 | 管理节点存储 | Ceph, GlusterFS | 
| 安全扫描 | 节点漏洞扫描 | Falco, Sysdig | 
六、Job 和 CronJob:一次性任务和定时任务
6.1 为什么不用系统 cron?
我遇到的需求:
产品:"每天凌晨 3 点清理 Redis 的过期键"
我的第一反应:
            
            
              bash
              
              
            
          
          # 在服务器上配置 crontab
0 3 * * * /usr/local/bin/cleanup-redis.sh产品:"不行!"
为什么不行?
❌ 服务器重启后,cron 可能没启动
❌ 任务失败了,没有重试机制
❌ 没有日志,不知道任务有没有执行
❌ K8s 集群里的 Redis,外部脚本连不上(网络隔离)
❌ 多台服务器,每台都要配置 cron?K8s 的 CronJob 解决了这些问题!
6.2 Job vs CronJob
| 特性 | Job | CronJob | 
|---|---|---|
| 执行时机 | 立即执行一次 | 定时执行 | 
| 适用场景 | 数据迁移、批处理 | 备份、清理、报表 | 
| 配置 | 简单 | 需要 cron 表达式 | 
| 示例 | 数据导入 | 每天备份数据库 | 
简单说:
- Job:跑一次就结束
- CronJob:定时重复跑
6.3 核心特性
Job 示例
            
            
              yaml
              
              
            
          
          apiVersion: batch/v1
kind: Job
metadata:
  name: data-import
spec:
  template:
    spec:
      containers:
      - name: importer
        image: data-importer:v1.0
        command: ["python", "import.py"]
      restartPolicy: OnFailure  # 失败自动重试执行流程:
            
            
              bash
              
              
            
          
          # 创建 Job
kubectl apply -f job.yaml
# 查看状态
kubectl get jobs
# NAME           COMPLETIONS   DURATION   AGE
# data-import    0/1           5s         5s
# 完成后
kubectl get jobs
# NAME           COMPLETIONS   DURATION   AGE
# data-import    1/1           30s        35s特点:
- Pod 运行完后,状态是 Completed
- Pod 不会自动删除(可以查看日志)
- 失败自动重试(根据 restartPolicy)
CronJob 示例
            
            
              yaml
              
              
            
          
          apiVersion: batch/v1
kind: CronJob
metadata:
  name: redis-cleanup
spec:
  schedule: "0 3 * * *"  # 每天 3:00
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cleanup
            image: redis-cleanup:v1.0
            env:
            - name: REDIS_HOST
              value: "redis-service:6379"
          restartPolicy: OnFailureCron 表达式:
 ┌───────────── 分钟 (0 - 59)
 │ ┌───────────── 小时 (0 - 23)
 │ │ ┌───────────── 日 (1 - 31)
 │ │ │ ┌───────────── 月 (1 - 12)
 │ │ │ │ ┌───────────── 星期 (0 - 6) (0 = 周日)
 │ │ │ │ │
 * * * * *常用示例:
0 3 * * *       # 每天 3:00
*/5 * * * *     # 每 5 分钟
0 */2 * * *     # 每 2 小时
0 0 * * 0       # 每周日 0:00
0 0 1 * *       # 每月 1 号 0:00查看执行历史
            
            
              bash
              
              
            
          
          # 查看 CronJob
kubectl get cronjobs
# NAME            SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
# redis-cleanup   0 3 * * *     False     0        12h             5d
# 查看历史 Job
kubectl get jobs
# NAME                       COMPLETIONS   DURATION   AGE
# redis-cleanup-28345670     1/1           45s        12h
# redis-cleanup-28345680     1/1           42s        36h
# redis-cleanup-28345690     1/1           48s        60h
# 查看 Pod 日志
kubectl logs redis-cleanup-28345670-xxxxx重要配置
            
            
              yaml
              
              
            
          
          spec:
  schedule: "0 3 * * *"
  successfulJobsHistoryLimit: 3  # 保留 3 个成功的 Job
  failedJobsHistoryLimit: 1      # 保留 1 个失败的 Job
  concurrencyPolicy: Forbid       # 并发策略并发策略:
- Allow:允许并发执行(默认)
- Forbid:禁止并发,上次未完成则跳过
- Replace:取消上次任务,启动新任务
我的建议:
- 数据库备份:用 Forbid(避免并发写入)
- 数据清理:用 Allow(清理多次也没事)
- 报表生成:用 Replace(只要最新的)
七、实战对比:部署 Redis 的三种方式
7.1 方式一:用 Deployment(不推荐)
            
            
              yaml
              
              
            
          
          apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: redis
        image: redis:7.4
        volumeMounts:
        - name: data
          mountPath: /data
      volumes:
      - name: data
        emptyDir: {}  # 临时目录测试:
            
            
              bash
              
              
            
          
          # 写入数据
kubectl exec -it redis-xxx -- redis-cli SET test-key "hello"
# OK
# 读取数据
kubectl exec -it redis-xxx -- redis-cli GET test-key
# "hello"
# 删除 Pod(模拟重启)
kubectl delete pod redis-xxx
# 等待新 Pod 创建
kubectl get pods
# NAME          READY   STATUS    RESTARTS   AGE
# redis-yyy     1/1     Running   0          10s
# 尝试读取数据
kubectl exec -it redis-yyy -- redis-cli GET test-key
# (nil)  ← 数据丢了!问题:
- ❌ emptyDir是临时的,Pod 重建后数据丢失
- ❌ 即使用 PV,多副本共享 PV 会数据冲突
7.2 方式二:用 StatefulSet(推荐)
            
            
              yaml
              
              
            
          
          apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  clusterIP: None  # Headless Service
  selector:
    app: redis
  ports:
  - port: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7.4
        ports:
        - containerPort: 6379
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi测试:
            
            
              bash
              
              
            
          
          # 写入数据
kubectl exec -it redis-0 -- redis-cli SET test-key "hello"
# OK
# 读取数据
kubectl exec -it redis-0 -- redis-cli GET test-key
# "hello"
# 删除 Pod
kubectl delete pod redis-0
# 等待重建
kubectl get pods
# NAME      READY   STATUS    RESTARTS   AGE
# redis-0   1/1     Running   0          10s  ← 名称还是 redis-0
# 读取数据
kubectl exec -it redis-0 -- redis-cli GET test-key
# "hello"  ← 数据还在!好处:
- ✅ Pod 名称固定(redis-0)
- ✅ PVC 自动绑定(data-redis-0)
- ✅ 数据持久化
- ✅ 可以配置主从复制
7.3 对比总结
| 特性 | Deployment + emptyDir | Deployment + PV | StatefulSet + PVC | 
|---|---|---|---|
| 数据持久化 | ❌ | ⚠️ | ✅ | 
| 多副本支持 | ❌ | ❌ | ✅ | 
| 固定标识 | ❌ | ❌ | ✅ | 
| 主从复制 | ❌ | ❌ | ✅ | 
| 生产可用 | ❌ | ❌ | ✅ | 
结论:
- 测试环境:可以用 Deployment + emptyDir(快速,不在乎数据)
- 开发环境:可以用 Deployment + PV(单副本)
- 生产环境:必须用 StatefulSet!
八、选择工作负载的 5 个原则
原则 1:优先考虑应用的状态
无状态应用 → Deployment
有状态应用 → StatefulSet判断标准:
- Pod 重启后,数据能丢吗?
- 能丢 → Deployment
- 不能丢 → StatefulSet
 
原则 2:考虑部署位置
特定节点 → nodeSelector / nodeAffinity
每个节点 → DaemonSet示例:
- GPU 训练任务 → nodeSelector: gpu=true
- 日志采集 → DaemonSet
原则 3:考虑运行时长
长期运行 → Deployment / StatefulSet / DaemonSet
一次性任务 → Job
定时任务 → CronJob原则 4:考虑网络需求
需要固定 DNS → StatefulSet + Headless Service
负载均衡 → Deployment + Service原则 5:不确定时,先用 Deployment
90% 的应用都是无状态的!
如果不确定,先用 Deployment,遇到问题再考虑:
- 数据丢失?→ StatefulSet
- 需要每个节点?→ DaemonSet
- 定时执行?→ CronJob
结语
这篇文章,我搞懂了:
✅ Deployment:无状态应用的首选
- 随机 Pod 名称
- 快速扩缩容
- 滚动更新
- 适合 API、Web 前端
✅ StatefulSet:有状态应用的必选
- 固定 Pod 名称(name-0)
- 固定网络标识(DNS)
- 独立持久化存储(PVC)
- 适合数据库、缓存
✅ DaemonSet:节点级守护进程
- 每个节点一个 Pod
- 节点扩缩容自动适应
- 可访问宿主机资源
- 适合日志、监控
✅ Job/CronJob:批处理和定时任务
- 运行完自动退出
- 失败自动重试
- 支持定时调度
- 适合备份、清理
最大的收获:
不是所有应用都适合 Deployment!
选择合适的工作负载,就像选择合适的工具:
锤子修不了灯泡,用对工具才能事半功倍!
下一步(v0.2):
在下一篇文章中,我会实战部署 Redis StatefulSet,包括:
- ✅ 配置 Headless Service
- ✅ 创建 StatefulSet
- ✅ 配置持久化存储
- ✅ 验证数据持久化
- ✅ 我踩过的所有坑!
敬请期待!
如果这篇文章对你有帮助,欢迎点赞、收藏、分享!
有问题欢迎在评论区讨论! 👇