K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

StatefulSet、DaemonSet、Job — 不同场景的工作负载

在动手之前先建立直觉——什么场景用什么控制器: | 场景 | 控制器 | 原因 | |------|--------|------| | 无状态 Web/API 服务 | **Deployment** | 副本可互换,随时扩缩 | | 数据库、缓存、消息队列 | **StatefulSet** | 需要稳定身份、持久存储、有序启停 | | 每节点运行一份(日

Markdownphase-1/03-statefulset-daemonset-job.md2026年4月9日 11:36

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 的核心保证

  1. 稳定的网络身份 — Pod 名称和 DNS 不变(重建后 web-0 还叫 web-0)
  2. 有序部署和删除 — 按序号创建,逆序删除
  3. 稳定的持久存储 — 每个 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-5T4: 全部 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

04-rbac.md