当前位置: 首页 > news >正文

从零开始的云原生之旅(四):K8s 工作负载完全指南

从零开始的云原生之旅(四):K8s 工作负载完全指南

终于搞懂 Deployment、StatefulSet、DaemonSet、CronJob 的区别了!

📖 文章目录

  • 前言
  • 一、为什么需要多种工作负载?
    • 1.1 我在 v0.1 遇到的局限
    • 1.2 真实场景的多样性
  • 二、K8s 的 4 种核心工作负载
    • 2.1 快速对比
    • 2.2 选择决策树
  • 三、Deployment:无状态应用之王
    • 3.1 什么是"无状态"?
    • 3.2 核心特性
    • 3.3 适用场景
  • 四、StatefulSet:有状态应用的正确姿势
    • 4.1 什么是"有状态"?
    • 4.2 为什么需要 StatefulSet?
    • 4.3 核心特性
    • 4.4 与 Deployment 的关键区别
  • 五、DaemonSet:每个节点都要有
    • 5.1 什么场景需要?
    • 5.2 核心特性
    • 5.3 调度策略
  • 六、Job 和 CronJob:一次性任务和定时任务
    • 6.1 为什么不用系统 cron?
    • 6.2 Job vs CronJob
    • 6.3 核心特性
  • 七、实战对比:部署 Redis 的三种方式
    • 7.1 方式一:用 Deployment(不推荐)
    • 7.2 方式二:用 StatefulSet(推荐)
    • 7.3 对比总结
  • 八、选择工作负载的 5 个原则
  • 结语

前言

大家好,我是一个正在学习云原生的 Go 开发者。

在 v0.1 中,我学会了用 Deployment 部署无状态的 API 服务,感觉很爽:

  • ✅ 声明 2 个副本,K8s 自动维护
  • ✅ 滚动更新,零停机发布
  • ✅ Pod 挂了自动重启

但很快我就遇到问题了:

老板:"我们要部署 Redis 做缓存"
我:"好,用 Deployment 部署!"
老板:"Redis 需要持久化数据,你这样会丢数据!"
我:"啊?为啥?"运维:"每个节点都要跑日志采集器"
我:"Deployment replicas: 3?"
运维:"不是!是每个节点一个,新节点加入也要自动部署"
我:"???"产品:"每天凌晨 3 点清理过期数据"
我:"我写个脚本,crontab 定时执行?"
产品:"服务器重启了咋办?而且要在 K8s 集群里执行"
我:"......"

我意识到:Deployment 不是万能的!

于是我花了一周时间,系统学习了 K8s 的 4 种核心工作负载:

  1. Deployment - 无状态应用
  2. StatefulSet - 有状态应用
  3. DaemonSet - 节点级守护进程
  4. Job/CronJob - 批处理和定时任务

这篇文章记录我的学习过程,重点不是罗列概念,而是:

  • ✅ 为什么需要这么多工作负载?
  • ✅ 它们解决什么问题?
  • ✅ 实际场景中怎么选?
  • 我踩过的坑!

一、为什么需要多种工作负载?

1.1 我在 v0.1 遇到的局限

v0.1 中,我用 Deployment 部署了一个 Go API:

apiVersion: apps/v1
kind: Deployment
metadata:name: api-server
spec:replicas: 2template:spec:containers:- name: apiimage: cloudnative-go-api:v0.1

这个方案很适合我的 API,因为:

  • API 是无状态的(任何一个 Pod 都能处理请求)
  • Pod 可以随意删除重建(不丢数据)
  • 横向扩展很简单(加副本就行)

但当我想部署 Redis 时,问题来了:

❌ Redis 需要持久化数据(RDB/AOF 文件)
❌ Deployment 的 Pod 重建后,数据会丢失!
❌ 多个 Redis Pod 之间需要固定的网络标识(主从复制)
❌ Deployment 的 Pod 名称是随机的(api-server-7f8d9c-abcde)

我第一反应:挂载 Volume 不就行了?

试了一下:

spec:template:spec:containers:- name: redisimage: redis:7.4volumeMounts:- name: datamountPath: /datavolumes:- name: dataemptyDir: {}  # 临时目录

结果:

❌ 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 快速对比(一张表看懂)

特性DeploymentStatefulSetDaemonSetJob/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 核心特性

① 自动维护副本数
spec:replicas: 3

K8s 会确保始终有 3 个 Pod 运行:

  • Pod 挂了?立即创建新的
  • 手动删除 Pod?自动补上
  • 节点宕机?在其他节点重建 Pod

我的测试:

# 查看 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 个副本!


② 滚动更新(零停机部署)
spec:strategy:type: RollingUpdaterollingUpdate: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 在服务
  • ✅ 用户无感知
  • ✅ 发现问题可以随时回滚

③ 快速扩缩容
# 扩容到 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=80

3.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 名称不固定

# 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:存储对应不上

# Deployment + PV
spec:template:spec:volumes:- name: datapersistentVolumeClaim:claimName: redis-pvc  # 所有 Pod 共享一个 PVC

问题:

  • 多个 Redis Pod 写同一个 PV → 数据冲突!
  • 给每个 Pod 分配独立 PVC?Pod 重建后对应不上

✅ StatefulSet 的解决方案:

apiVersion: apps/v1
kind: StatefulSet
metadata:name: redis
spec:serviceName: redis-service  # 必须指定 Headless Servicereplicas: 3template:spec:containers:- name: redisimage: redis:7.4volumeMounts:- name: datamountPath: /datavolumeClaimTemplates:  # 自动为每个 Pod 创建 PVC- metadata:name: dataspec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 1Gi

创建后:

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          1mkubectl 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        RWO

Pod 重启后:

  • Pod 名称还是 redis-0
  • PVC 还是 data-redis-0
  • 数据不丢失!

4.3 核心特性

① 固定的 Pod 名称
Deployment:redis-7f8d9c-abcde  ← 随机后缀redis-7f8d9c-fghijredis-7f8d9c-klmnoStatefulSet:redis-0  ← 从 0 开始递增redis-1redis-2

规则:

  • 名称格式:$(statefulset-name)-$(ordinal)
  • Ordinal 从 0 开始
  • Pod 删除重建后,名称不变

② 固定的网络标识

StatefulSet 必须配合 Headless Service 使用:

# Headless Service
apiVersion: v1
kind: Service
metadata:name: redis-service
spec:clusterIP: None  # 这是关键!selector:app: redisports:- 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

好处:

// 应用可以直接通过固定域名访问特定 Pod
masterAddr := "redis-0.redis-service:6379"  // 永远是主节点
slaveAddr := "redis-1.redis-service:6379"   // 永远是从节点

③ 独立的持久化存储

volumeClaimTemplates 会自动:

  1. 为每个 Pod 创建独立的 PVC
  2. PVC 名称:$(volumeClaimTemplate-name)-$(pod-name)
  3. Pod 删除后,PVC 不删除(数据保留)
  4. Pod 重建后,自动绑定原来的 PVC

示例:

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-1redis-2 是从节点

有序启动:

  • 先启动 redis-0(主节点)
  • 再启动 redis-1redis-2(从节点连接主节点)

有序停止:

  • 先停止 redis-2redis-1(从节点)
  • 最后停止 redis-0(主节点)

保证数据不丢失!


4.4 与 Deployment 的关键区别

特性DeploymentStatefulSet
Pod 名称name-随机name-0, name-1, …
Pod 标识不固定固定(DNS 记录)
存储共享或临时每个 Pod 独立 PVC
启动顺序并行顺序(0→1→2)
停止顺序并行倒序(2→1→0)
扩缩容随意有序
适用场景无状态应用有状态应用

记忆口诀:

Deployment = 随机的、一次性的、可替换的
StatefulSet = 固定的、有序的、不可替换的

五、DaemonSet:每个节点都要有

5.1 什么场景需要?

我遇到的需求:

运维:“我们要在每个节点上部署日志采集器,收集 /var/log/ 的日志”

我的第一反应:用 Deployment?

apiVersion: apps/v1
kind: Deployment
metadata:name: log-collector
spec:replicas: 3  # 假设集群有 3 个节点

运维:“不对!如果集群扩容到 5 个节点呢?你要手动改 replicas?”

我:“对哦!而且 Deployment 不保证每个节点都有一个 Pod!”

# 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
apiVersion: apps/v1
kind: DaemonSet
metadata:name: log-collector
spec:selector:matchLabels:app: log-collectortemplate:metadata:labels:app: log-collectorspec:containers:- name: collectorimage: log-collector:v1.0volumeMounts:- name: varlogmountPath: /var/logreadOnly: truevolumes:- name: varloghostPath:path: /var/log  # 挂载宿主机的 /var/log

创建后:

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!

集群扩容时:

# 新加入一个 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 自动清理
# 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 访问宿主机资源:

volumes:
- name: varloghostPath:path: /var/log  # 宿主机的 /var/log- name: docker-sockhostPath:path: /var/run/docker.sock  # 访问 Docker- name: syshostPath:path: /sys  # 访问系统信息

典型场景:

  • 日志采集:读取 /var/log/
  • 监控 Agent:读取 /proc//sys/
  • 网络插件:操作宿主机网络

5.3 调度策略

① 默认:在所有节点运行
apiVersion: apps/v1
kind: DaemonSet
metadata:name: log-collector
spec:template:spec:containers:- name: collectorimage: log-collector:v1.0

结果:每个节点都运行一个 Pod


② 通过 nodeSelector 限制节点
spec:template:spec:nodeSelector:role: worker  # 只在有这个标签的节点运行containers:- name: collectorimage: log-collector:v1.0

给节点打标签:

# 给 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 调度:

kubectl describe node master | Select-String "Taints"
# Taints: node-role.kubernetes.io/control-plane:NoSchedule

如果要在 Master 上也运行 DaemonSet:

spec:template:spec:tolerations:- key: node-role.kubernetes.io/control-planeoperator: Existseffect: NoSchedulecontainers:- name: collectorimage: 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 的过期键”

我的第一反应:

# 在服务器上配置 crontab
0 3 * * * /usr/local/bin/cleanup-redis.sh

产品:“不行!”

为什么不行?

❌ 服务器重启后,cron 可能没启动
❌ 任务失败了,没有重试机制
❌ 没有日志,不知道任务有没有执行
❌ K8s 集群里的 Redis,外部脚本连不上(网络隔离)
❌ 多台服务器,每台都要配置 cron?

K8s 的 CronJob 解决了这些问题!


6.2 Job vs CronJob

特性JobCronJob
执行时机立即执行一次定时执行
适用场景数据迁移、批处理备份、清理、报表
配置简单需要 cron 表达式
示例数据导入每天备份数据库

简单说:

  • Job:跑一次就结束
  • CronJob:定时重复跑

6.3 核心特性

Job 示例
apiVersion: batch/v1
kind: Job
metadata:name: data-import
spec:template:spec:containers:- name: importerimage: data-importer:v1.0command: ["python", "import.py"]restartPolicy: OnFailure  # 失败自动重试

执行流程:

# 创建 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 示例
apiVersion: batch/v1
kind: CronJob
metadata:name: redis-cleanup
spec:schedule: "0 3 * * *"  # 每天 3:00jobTemplate:spec:template:spec:containers:- name: cleanupimage: redis-cleanup:v1.0env:- name: REDIS_HOSTvalue: "redis-service:6379"restartPolicy: OnFailure

Cron 表达式:

 ┌───────────── 分钟 (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

查看执行历史
# 查看 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

重要配置
spec:schedule: "0 3 * * *"successfulJobsHistoryLimit: 3  # 保留 3 个成功的 JobfailedJobsHistoryLimit: 1      # 保留 1 个失败的 JobconcurrencyPolicy: Forbid       # 并发策略

并发策略:

  • Allow:允许并发执行(默认)
  • Forbid:禁止并发,上次未完成则跳过
  • Replace:取消上次任务,启动新任务

我的建议:

  • 数据库备份:用 Forbid(避免并发写入)
  • 数据清理:用 Allow(清理多次也没事)
  • 报表生成:用 Replace(只要最新的)

七、实战对比:部署 Redis 的三种方式

7.1 方式一:用 Deployment(不推荐)

apiVersion: apps/v1
kind: Deployment
metadata:name: redis
spec:replicas: 1template:spec:containers:- name: redisimage: redis:7.4volumeMounts:- name: datamountPath: /datavolumes:- name: dataemptyDir: {}  # 临时目录

测试:

# 写入数据
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(推荐)

apiVersion: v1
kind: Service
metadata:name: redis-service
spec:clusterIP: None  # Headless Serviceselector:app: redisports:- port: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: redis
spec:serviceName: redis-servicereplicas: 1selector:matchLabels:app: redistemplate:metadata:labels:app: redisspec:containers:- name: redisimage: redis:7.4ports:- containerPort: 6379volumeMounts:- name: datamountPath: /datavolumeClaimTemplates:- metadata:name: dataspec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 1Gi

测试:

# 写入数据
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 + emptyDirDeployment + PVStatefulSet + 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
  • ✅ 配置持久化存储
  • ✅ 验证数据持久化
  • 我踩过的所有坑!

敬请期待!


如果这篇文章对你有帮助,欢迎点赞、收藏、分享!

有问题欢迎在评论区讨论! 👇

http://www.dtcms.com/a/548846.html

相关文章:

  • 论文阅读笔记——自注意力机制
  • Xsens动作捕捉系统:训练、实时控制机器人
  • iOS 26 性能监控工具有哪些?多工具协同打造全方位性能分析体系
  • 做网站公司合同最新网站建设进程
  • 佳木斯做网站公司wordpress模板视频教程
  • viewerjs+vue3 using javascript
  • 2.游戏逆向-pxxx-分析UE源码中的GName
  • 编程与数学 03-007 《看潮资源管理器》项目开发 18 文件夹操作(2-2)
  • 服务器镜像安全:Docker 镜像漏洞扫描(Trivy)与基础镜像优化
  • Web安全深度实战:从漏洞挖掘到安全防护
  • 教育行业 | 信而泰推出Darsi通信网络与网络安全教育实验平台解决方案
  • 【Java笔记】定时器
  • ih5做的网站怎么上传seo企业网站优化
  • 刚建设的网站多久能在百度查到最美情侣高清视频播放
  • 【JavaEE初阶】TCP核心机制10——异常情况的处理
  • 阿里云ECS在线扩容磁盘
  • 私人兼职做网站开发麻章手机网站建设
  • 如何把ChatGPT嵌入到自己的应用中?
  • 豆包、元宝、Kimi等AI对话大模型会成为“带货”主流吗?
  • 仓颉语言中流式I/O的设计模式深度剖析
  • 51单片机基础-IO扩展(并转串 74HC165)
  • 【LeetCode】49. 字母异位词分组
  • 创建网站需要注意的问题成都最专业做网站的
  • 上海松江网站制作南京市规划建设展览馆网站
  • 好用的大屏互动哪个公司好
  • Rust 深度指南:从 0 到 1,不只是学习,更是“思维重塑”
  • 题解:P2519 [HAOI2011] problem a
  • Rust async/await 语法糖的展开原理:从状态机到零成本异步
  • 车联网网络安全防护定级备案:数字时代交通变革下的安全基石
  • 李宏毅机器学习笔记36