Repository Reading Site
StatefulSet、DaemonSet、Job — 不同场景的工作负载
在动手之前先建立直觉——什么场景用什么控制器: | 场景 | 控制器 | 原因 | |------|--------|------| | 无状态 Web/API 服务 | **Deployment** | 副本可互换,随时扩缩 | | 数据库、缓存、消息队列 | **StatefulSet** | 需要稳定身份、持久存储、有序启停 | | 每节点运行一份(日
StatefulSet、DaemonSet、Job — 不同场景的工作负载
工作负载选型指南
在动手之前先建立直觉——什么场景用什么控制器:
| 场景 | 控制器 | 原因 |
|---|---|---|
| 无状态 Web/API 服务 | Deployment | 副本可互换,随时扩缩 |
| 数据库、缓存、消息队列 | StatefulSet | 需要稳定身份、持久存储、有序启停 |
| 每节点运行一份(日志/监控/网络) | DaemonSet | 自动跟随节点增减 |
| 一次性批处理任务 | Job | 跑完即退出 |
| 定时任务 | CronJob | 按 cron 表达式周期触发 Job |
StatefulSet — 有状态应用
Deployment 解决不了什么问题?
假设你要部署 3 个 Redis 副本做主从复制:
| 需求 | Deployment 能做吗? |
|---|---|
| 固定的 Pod 名称(redis-0 是主,redis-1/2 是从) | 不能,Pod 名称随机 |
| 有序启动(主先启动,从再启动) | 不能,并行启动 |
| 稳定的 DNS(从节点要通过固定地址找到主节点) | 不能,Pod IP 每次变 |
| 每个副本有独立的持久存储 | 不能,共享同一个 PVC |
StatefulSet 全部能做。
Headless Service — StatefulSet 的伴侣
apiVersion: v1
kind: Service
metadata:
name: web-headless
namespace: dev
spec:
clusterIP: None # ★ 关键:None 表示 Headless
selector:
app: web
ports:
- port: 80
普通 Service vs Headless Service:
| 特性 | ClusterIP Service | Headless Service |
|---|---|---|
| ClusterIP | 有(如 10.110.86.136) | None |
| DNS 解析返回 | ClusterIP(一个 IP) | 所有 Pod IP(多个 A 记录) |
| 负载均衡 | kube-proxy 做 | 客户端自己选 |
| 用途 | 无状态服务 | StatefulSet、需要直连 Pod |
我们创建的 StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: dev
spec:
serviceName: "web-headless" # ★ 必须关联 Headless Service
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
实际验证结果
Pod 名称是有序的(不是随机哈希):
web-0 → cp-3 (10.244.242.4)
web-1 → wk-1 (10.244.147.69)
web-2 → us590068728056 (10.244.119.197)
创建顺序:web-0 先 Ready → web-1 创建 → web-1 Ready → web-2 创建
删除顺序:反过来,web-2 先删
每个 Pod 有稳定的 DNS:
web-0.web-headless.dev.svc.cluster.local
web-1.web-headless.dev.svc.cluster.local
web-2.web-headless.dev.svc.cluster.local
从 web-0 访问 web-1:
$ curl http://web-1.web-headless
Welcome to nginx! ← 成功!
StatefulSet 的核心保证
- 稳定的网络身份 — Pod 名称和 DNS 不变(重建后 web-0 还叫 web-0)
- 有序部署和删除 — 按序号创建,逆序删除
- 稳定的持久存储 — 每个 Pod 绑定独立的 PVC,缩容后 PVC 保留(数据不丢)
面试高频题
Q: StatefulSet 的 Pod 被删除后,新 Pod 会分配到同一个节点吗?
不一定。Pod 名称和 PVC 绑定关系不变,但调度可能到不同节点。如果 PVC 使用的是 local PV(节点本地存储),那新 Pod 会被 nodeAffinity 约束到原节点。NFS 等网络存储则不受节点限制。
Q: StatefulSet 可以并行启动吗?
可以。设置 podManagementPolicy: Parallel(默认是 OrderedReady)。但主从复制等场景通常需要有序启动。
DaemonSet — 每节点一份
典型场景
- 日志收集 — Fluentd/Promtail 在每个节点收集容器日志
- 监控采集 — node-exporter 采集每个节点的系统指标
- 网络插件 — calico-node、kube-proxy 本身就是 DaemonSet
- 存储插件 — CSI node plugin
我们创建的 DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-collector
namespace: dev
spec:
selector:
matchLabels:
app: log-collector
template:
metadata:
labels:
app: log-collector
spec:
containers:
- name: collector
image: busybox:1.36
command: ["sh", "-c", "while true; do echo \"collecting logs on $(hostname)\"; sleep 60; done"]
resources:
requests:
cpu: 10m
memory: 16Mi
实际验证结果
log-collector-crk6f → cp-3 (LA Worker-2)
log-collector-k2vfp → us590068728056 (LA Worker-1)
log-collector-zw5pf → hk652699382121 (HK Worker-3)
log-collector-2c644 → wk-1 (HK Worker-4)
注意:Master (us480851516617a) 上没有!
原因:Master 有 Taint node-role.kubernetes.io/control-plane:NoSchedule
DaemonSet 默认也尊重 Taint。
如何让 DaemonSet 跑在 Master 上?
spec:
template:
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
加了这个 toleration,Pod 就能"容忍" Master 的 taint,被调度上去。
DaemonSet vs Deployment with nodeAffinity?
两者都能在指定节点上跑 Pod,但:
- DaemonSet 保证每个匹配节点恰好一个 Pod
- Deployment 需要手动设 replicas = 节点数,且新增节点时不会自动扩
- DaemonSet 在节点加入集群时自动部署,节点删除时自动清理
Job — 一次性任务
我们创建的 Job
apiVersion: batch/v1
kind: Job
metadata:
name: pi-calc
spec:
completions: 5 # 总共要完成 5 个任务
parallelism: 2 # 最多同时跑 2 个 Pod
backoffLimit: 3 # 失败重试次数上限
template:
spec:
containers:
- name: pi
image: busybox:1.36
command: ["sh", "-c", "echo scale=1000; 4*a(1) | bc -l"]
restartPolicy: Never # Job Pod 不自动重启(与 Deployment 不同)
执行流程
T0: Pod-1 ● Pod-2 ● (2 个并行)
T1: Pod-1 ✓ Pod-2 ● Pod-3 ● (1完成,补1个)
T2: Pod-1 ✓ Pod-2 ✓ Pod-3 ● Pod-4 ● (2完成,补1个)
T3: Pod-1 ✓ Pod-2 ✓ Pod-3 ✓ Pod-4 ● Pod-5 ●
T4: 全部 5 个完成 → Job 状态变为 Complete
CronJob — 定时 Job
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点
concurrencyPolicy: Forbid # 上一次没跑完就不启新的
startingDeadlineSeconds: 600 # 超过 10 分钟没启动就跳过
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: busybox
command: ["sh", "-c", "echo backing up..."]
restartPolicy: Never
concurrencyPolicy 三种模式:
| 模式 | 含义 |
|---|---|
Allow |
允许并发(可能多个 Job 同时跑) |
Forbid |
上一个没完成就跳过本次 |
Replace |
杀掉上一个,启动新的 |
面试题
Q: Job 的 restartPolicy 为什么不能是 Always?
因为 Job 的语义是"跑完就结束"。Always 意味着容器退出后永远重启——Job 永远完成不了。只能用 Never(失败就创建新 Pod 重试)或 OnFailure(失败在同一个 Pod 里重启)。
总结:工作负载控制器对比
| 特性 | Deployment | StatefulSet | DaemonSet | Job |
|---|---|---|---|---|
| Pod 命名 | 随机哈希 | 有序数字 | 随机哈希 | 随机哈希 |
| 扩缩容 | 任意 | 有序 | 自动跟节点 | N/A |
| 存储 | 共享 PVC | 每个独立 PVC | 通常 hostPath | 通常无 |
| 网络身份 | 无 | 稳定 DNS | 无 | 无 |
| 适用场景 | Web/API | DB/Cache/MQ | Agent/Monitor | Batch/ETL |