jimyag's Blog

Kubernetes Deployment、StatefulSet 与 DaemonSet:如何选择工作负载控制器

Kubernetes 不直接把一组长期运行的 Pod 当作一个应用管理,而是通过工作负载控制器持续比较“期望状态”和“实际状态”。Deployment、StatefulSet 与 DaemonSet 都能根据 Pod 模板创建 Pod,但三者维护的期望状态不同:Deployment 维护指定数量的可互换副本,StatefulSet 维护带稳定身份的副本,DaemonSet 则在每个符合条件的节点上维护一个副本。

本文以 Kubernetes v1.37 官方文档为基准,比较三种控制器的副本模型、身份、存储、调度、更新和扩缩容语义,并回答 PVC、Pod IP、Headless Service 与 HPA 等常见问题。Job、CronJob 等一次性任务控制器不在本文范围内。

先根据期望状态选择控制器

三种控制器最重要的区别不是 YAML 字段,而是它们分别在维持什么:

1
2
3
Deployment:  我需要 N 个可以互相替代的 Pod
StatefulSet: 我需要 N 个身份不能互换的 Pod
DaemonSet:   每个符合条件的节点都需要一个 Pod
维度DeploymentStatefulSetDaemonSet
副本目标指定 replicas指定 replicas由符合条件的节点数量决定
Pod 是否可互换否,每个 Pod 有 ordinal通常与所在节点绑定用途
Pod 名称带随机后缀固定为 <name>-<ordinal>带随机后缀
稳定网络身份不提供Pod 名称和 DNS 身份稳定不提供稳定 Pod 名称
每副本独立 PVC不支持顶层 volumeClaimTemplates支持不支持顶层 volumeClaimTemplates
默认更新方式滚动更新按 ordinal 逆序滚动更新按节点滚动更新
HPA支持支持,但应用必须能安全扩缩容不支持
常见用途Web、API、无状态服务数据库、消息系统、有状态成员日志、监控、网络和存储节点代理

“有持久化数据”不等于必须使用 StatefulSet。Deployment 也可以挂载 PVC;真正需要 StatefulSet 的通常是稳定的副本身份、每个副本独立且可复用的存储,或者有序创建、更新和删除。

共同基础:三者最终都在管理 Pod

三种资源都使用 .spec.template 描述 Pod,所以容器镜像、CPU 和内存、探针、ConfigMap、Secret、ServiceAccount、亲和性、拓扑分布约束等 Pod 级能力基本相同。

控制器并不会修复原来的 Pod。如果一个 Pod 消失或模板发生变化,控制器创建的是替代 Pod。由此产生几个共同边界:

  • Pod UID 和 Pod IP 都可能变化。
  • 应用是否可以安全终止,取决于探针、preStopterminationGracePeriodSeconds 和应用自身行为。
  • 控制器保证的是 Kubernetes 对象状态,不会自动完成数据库选主、分片再平衡或数据复制。
  • Service 负责为一组 Pod 提供访问入口,不负责创建或扩缩 Pod。

Deployment:管理可互换的副本

Deployment 适合任意一个副本都能处理同类请求的服务。它不会直接长期管理 Pod,而是创建 ReplicaSet,再由 ReplicaSet 维持 Pod 数量:

1
2
3
4
5
6
7
8
9
Deployment
    └── ReplicaSet(当前版本)
          └── Pods

更新 Pod 模板后:

Deployment
    ├── ReplicaSet(旧版本,逐步缩容)
    └── ReplicaSet(新版本,逐步扩容)

下面的 Deployment 维持三个 Web 副本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: apps/v1 # Deployment 使用 apps/v1 API。
kind: Deployment # 管理一组可互换的 Pod。
metadata:
  name: web
spec:
  replicas: 3 # 期望始终有三个副本。
  selector:
    matchLabels:
      app: web # selector 必须匹配 Pod 模板的标签。
  strategy:
    type: RollingUpdate # 默认使用滚动更新。
    rollingUpdate:
      maxUnavailable: 0 # 更新期间不主动减少可用副本数。
      maxSurge: 1 # 最多额外创建一个新 Pod。
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.29-alpine # 修改镜像会触发新一轮发布。
          resources:
            requests:
              cpu: 100m # 为调度和基于 CPU 的 HPA 提供基准。
              memory: 64Mi
          readinessProbe: # 只有 Ready Pod 才会正常进入 Service 后端。
            httpGet:
              path: /
              port: 80
            periodSeconds: 5

Deployment 默认使用 RollingUpdate,通过 maxSurgemaxUnavailable 控制新旧 ReplicaSet 的扩缩节奏;也可以使用 Recreate,先终止旧版本 Pod,再创建新版本 Pod。

progressDeadlineSeconds 只能让控制器在发布长期没有进展时报告 ProgressDeadlineExceeded。它不会自动回滚,仍需要运维系统或操作者决定继续修复还是执行回滚。

Deployment 适合:

  • HTTP API、Web 服务和普通后端服务;
  • 可以通过队列、数据库或对象存储保存外部状态的 Worker;
  • 多个副本使用相同配置,并且调用方不关心请求落到哪个副本的应用。

StatefulSet:管理稳定且不可互换的身份

StatefulSet 为每个副本分配 ordinal。假设资源名为 db,副本会依次命名为 db-0db-1db-2。Pod 重建后仍使用原来的名称和 ordinal,但这不代表它会继续使用原来的 Pod IP。

ordinal 默认从 0 开始,也可以通过 .spec.ordinals.start 修改起始值。Kubernetes 还会为每个 Pod 添加 apps.kubernetes.io/pod-index 标签,值就是该 Pod 的 ordinal,可以用于日志和指标筛选,或者让 Service 选择特定序号的成员。

StatefulSet 通常同时连接三类对象:

1
2
3
4
5
6
7
StatefulSet db
    ├── Pod db-0 ── PVC data-db-0
    ├── Pod db-1 ── PVC data-db-1
    └── Pod db-2 ── PVC data-db-2

Headless Service db
    └── 为 db-0.db、db-1.db、db-2.db 提供稳定 DNS 身份

一个简化配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: v1 # Headless Service 使用 core/v1 API。
kind: Service
metadata:
  name: db
spec:
  clusterIP: None # 不分配 ClusterIP,DNS 返回后端 Pod 地址。
  selector:
    app: db # 选择 StatefulSet 创建的 Pod。
  ports:
    - name: database
      port: 5432 # 为稳定 DNS 和端口发现提供 Service 定义。
---
apiVersion: apps/v1 # StatefulSet 使用 apps/v1 API。
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db # 使用上面的 Headless Service 管理网络域名。
  replicas: 3 # 创建 db-0、db-1 和 db-2。
  selector:
    matchLabels:
      app: db # selector 必须匹配 Pod 模板标签。
  podManagementPolicy: OrderedReady # 默认按 ordinal 有序创建和删除。
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: postgres
          image: postgres:18-alpine
          env:
            - name: POSTGRES_PASSWORD # 密码来自预先创建的 Secret,不写入工作负载清单。
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
          ports:
            - name: database
              containerPort: 5432
          readinessProbe: # 当前成员可接受连接后才继续创建或更新下一个 ordinal。
            tcpSocket:
              port: database
            periodSeconds: 5
          volumeMounts:
            - name: data # 名称必须与 volumeClaimTemplates 对应。
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates: # 为每个 ordinal 创建并保留独立 PVC。
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOncePod # CSI 支持时,将读写挂载限制为单个 Pod。
        resources:
          requests:
            storage: 20Gi

默认的 OrderedReady 策略按 0 → N-1 创建 Pod,按 N-1 → 0 删除;滚动更新也从最大 ordinal 开始。只有当前 Pod Running 且 Ready,控制器才继续处理前一个 Pod。某个成员一直无法 Ready 时,发布可能停在中间。

如果应用不需要这种顺序,可以把 podManagementPolicy 设置为 Parallel。这只放宽扩缩容时的顺序,不会取消稳定名称、ordinal 和存储身份。

StatefulSet 的稳定性有明确边界:

  • 稳定的是 Pod 名称、ordinal、DNS 身份和对应 PVC,不是 Pod IP。
  • volumeClaimTemplates 为每个 Pod 创建独立 PVC,Pod 重建后会重新挂载同一 PVC。
  • 默认的 PVC 保留策略是 Retain,缩容或删除 StatefulSet 不会自动删除这些 PVC。
  • 可以配置 persistentVolumeClaimRetentionPolicy 改变删除或缩容时的行为,但启用自动删除前要明确数据恢复边界。
  • StatefulSet 不会替数据库决定主从关系,也不会因为增加一个 Pod 就自动完成数据复制和成员注册。

因此,复杂数据库常由 Operator 管理。Operator 可以在 StatefulSet 之上处理备份、选主、成员变更、版本升级和故障恢复;单独换成 StatefulSet 并不会让应用自动获得这些能力。

DaemonSet:按节点分布副本

DaemonSet 不使用 replicas 表示数量。它计算哪些节点满足 nodeSelector、node affinity、taint/toleration 等约束,然后在每个符合条件的节点上维护一个 Pod。新节点加入后会创建新 Pod;节点不再匹配时,对应 Pod 会被删除。

DaemonSet controller 负责计算目标节点,并在创建的 Pod 上写入指向该节点的 node affinity;默认 scheduler 通常仍负责完成绑定。如果节点资源不足,Pod 依然可能无法运行。对于 CNI、CSI 等关键节点组件,可以结合合适的 PriorityClass,让 scheduler 必要时优先为它们腾出资源。

下面的例子只在带有 observability=true 标签的 Linux 节点上运行日志代理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1 # DaemonSet 使用 apps/v1 API。
kind: DaemonSet
metadata:
  name: log-agent
spec:
  selector:
    matchLabels:
      app: log-agent # selector 必须匹配 Pod 模板标签。
  updateStrategy:
    type: RollingUpdate # 逐步替换各节点上的旧 Pod。
    rollingUpdate:
      maxUnavailable: 1 # 更新期间最多允许一个节点缺少可用代理。
  template:
    metadata:
      labels:
        app: log-agent
    spec:
      nodeSelector:
        kubernetes.io/os: linux # 只选择 Linux 节点。
        observability: "true" # 只选择显式启用采集的节点。
      containers:
        - name: agent
          image: fluent/fluent-bit:4.1
          resources:
            requests:
              cpu: 50m # 每个匹配节点都要为代理预留资源。
              memory: 64Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true # 只读取节点日志目录。
      volumes:
        - name: varlog
          hostPath:
            path: /var/log # hostPath 指向每个节点自己的目录。
            type: Directory

DaemonSet 常用于:

  • 日志采集、节点监控和安全代理;
  • CNI 节点组件、kube-proxy 等网络代理;
  • CSI node plugin、设备插件和其他节点级基础设施。

“每节点一个”只针对同一个 DaemonSet 的符合条件节点。滚动更新启用 maxSurge 时,一个节点可能短暂同时存在新旧 Pod;节点 taint、标签、资源不足和亲和性也可能使 Pod 无法正常运行。

DaemonSet 会自动为部分节点状态 taint 添加 toleration,例如 not-readyunreachableunschedulable。这不代表它可以忽略所有 taint。集群自定义 taint 或控制平面 taint 是否需要额外 toleration,仍要查看 Pod 模板和目标节点。

更新方式为什么不同

三种控制器都能滚动更新,但控制顺序来自不同的副本模型。

Deployment:控制可用数量

Deployment 同时调整新旧 ReplicaSet,重点是更新期间有多少可用 Pod、允许临时多出多少 Pod:

  • maxUnavailable 控制最多缺少多少副本;
  • maxSurge 控制最多临时增加多少副本;
  • readiness probe 决定新 Pod 何时可以计入可用数量;
  • 保留的旧 ReplicaSet 用于查看历史和回滚 Pod 模板。

发布尚未完成时再次修改 Pod 模板,Deployment 不会等待上一轮结束,而是创建新的 ReplicaSet,并把上一轮正在扩容的 ReplicaSet 归入旧版本。如果此时又发生扩容,控制器会按现有活跃 ReplicaSet 的规模进行 proportional scaling,新增副本不一定全部进入最新 ReplicaSet。

StatefulSet:控制成员顺序和身份

StatefulSet 默认从最大 ordinal 向最小 ordinal 更新,每次删除并重建对应身份的 Pod。partition 可以只更新 ordinal 大于等于某个值的成员,用于分阶段验证;OnDelete 则要求操作者删除 Pod 后才应用新模板。

有序更新不等于应用级安全更新。数据库是否允许某个成员先退出、主节点能否最后更新、不同版本是否可以同时组成集群,都需要结合应用协议或 Operator 判断。

如果新模板创建的 Pod 永远无法 Ready,OrderedReady 会让发布停住。此时只把模板改回正确版本仍可能不够:受影响的错误 Pod 可能继续阻塞回滚,需要在恢复模板后手动删除这些 Pod,让 StatefulSet 使用正确版本重新创建它们。

DaemonSet:控制节点覆盖率

DaemonSet 支持默认的 RollingUpdate 和需要手动删除旧 Pod 才更新的 OnDelete。滚动更新可以用 maxUnavailable 控制暂时缺少代理的节点数,也可以用 maxSurge 先在节点上创建新版 Pod。

启用 maxSurge 后,同一节点可能短暂运行新旧两个 Pod。对 CPU、内存或设备资源占用较大的节点代理,这可能让节点资源消耗接近翻倍,并触发调度失败或驱逐。对 CNI、存储插件等关键组件,错误的更新参数还可能直接影响节点 Ready、Pod 网络或卷挂载。

扩缩容和 HPA

Deployment 和 StatefulSet 都提供 scale 子资源,所以 HPA 可以调整它们的 .spec.replicas。DaemonSet 的副本数由符合条件的节点数决定,不能作为 HPA 目标。

即使 API 支持,StatefulSet 也不一定适合直接使用 HPA:

  • HPA 只改变副本数,不负责数据库成员注册和数据再平衡;
  • 默认缩容会先删除最大 ordinal,应用必须允许该成员安全退出;
  • 基于 CPU 利用率扩缩容时,Pod 应配置 CPU requests
  • HPA 与人工或其他控制器同时修改副本数时,需要避免多个控制环互相覆盖。

HPA 管理副本数后,声明式清单不应继续固定 .spec.replicas。否则再次执行 kubectl apply,或者由 GitOps 控制器同步清单时,静态值可能覆盖 HPA 当前计算的副本数。应明确只让一个控制环持续写入该字段。

DaemonSet 想增加处理能力时,通常调整单 Pod 资源、降低每个代理的工作量,或增加符合条件的节点,而不是修改副本数。

存储问题

Deployment 能不能挂载 PVC

可以。Deployment 可以在 Pod 模板的 volumes[].persistentVolumeClaim.claimName 中引用已有 PVC。限制是所有副本看到的是同一个 claimName,Deployment 不会像 StatefulSet 那样为每个 ordinal 自动生成并重新关联独立 PVC。

多个 Pod 能否同时挂载同一个 PVC,取决于存储驱动、访问模式和 Pod 所在节点:

  • ReadWriteOnce 表示单节点读写,同一节点上的多个 Pod 仍可能同时访问;
  • ReadWriteMany 允许多节点读写,但还要确认存储后端支持;
  • ReadWriteOncePod 才用于把读写挂载约束到集群中的单个 Pod,并且需要 CSI 支持。

Deployment 能不能使用 volumeClaimTemplates

不能使用 StatefulSet 顶层的 .spec.volumeClaimTemplates。Pod 规范中还有 Generic Ephemeral Volume 的 ephemeral.volumeClaimTemplate,它会为 Pod 创建临时 PVC,但 PVC 生命周期通常跟随 Pod,不适合保存需要跨 Pod 重建长期保留的数据。

StatefulSet 缩容后 PVC 会怎样

默认保留。把三个副本缩到一个时,data-db-2data-db-1 不会因为对应 Pod 消失就自动删除。以后扩回原来的 ordinal,可以继续使用这些 PVC。这样更安全,但也意味着操作者需要识别和清理确定不再需要的数据卷。

网络身份问题

StatefulSet Pod 的 IP 固定吗

不固定。Pod 被重建或调度到其他节点后,Pod IP 可能变化。稳定的是 db-0 这样的 Pod 名称,以及配合 Headless Service 得到的 DNS 名称,例如:

1
db-0.db.default.svc.cluster.local

DNS 会解析到当前 Pod IP,因此应用应使用稳定 DNS 或 Kubernetes API 发现成员,而不是把某次观察到的 Pod IP 当作永久地址。

Pod 已经 Running 后,稳定 DNS 也不一定立即可解析。如果客户端在 Pod 创建前查询过该名称,DNS 可能缓存之前的 NXDOMAIN 结果,直到负缓存过期后才返回新地址。需要立即发现成员的控制程序可以 watch Kubernetes API;修改 CoreDNS 缓存时间则需要同时评估 DNS 查询压力。

Deployment 和 DaemonSet 是否也能使用 Service

可以。Service 根据标签选择 Pod,与 Pod 来自哪种工作负载控制器没有直接关系:

  • Deployment 常配合普通 ClusterIP Service,为可互换副本提供统一入口;
  • StatefulSet 常同时使用 Headless Service 发现成员,以及普通 Service 访问当前主节点或所有可服务成员;
  • DaemonSet 可以通过普通 Service 随机访问一个代理,也可以使用 internalTrafficPolicy: Local 尽量只访问当前节点上的代理。

进一步理解控制器行为

Deployment 为什么还需要 ReplicaSet

ReplicaSet 负责维持某一份 Pod 模板对应的副本数,Deployment 负责协调不同版本的 ReplicaSet。更新 Pod 模板时,Deployment 创建或复用新版本 ReplicaSet,再同时缩小旧版本、扩大新版本。旧 ReplicaSet 还承载发布历史,因此可以用于回滚 Pod 模板。

StatefulSet 和 DaemonSet 不通过 ReplicaSet 管理 Pod。StatefulSet 使用 ControllerRevision 保存版本历史,并直接按照 ordinal 更新 Pod;DaemonSet 也直接围绕目标节点管理 Pod。

哪些修改会触发 Deployment 发布

只有 .spec.template 发生变化才会创建新的 Deployment revision,例如修改镜像、环境变量、Pod 标签或 Pod 模板 annotation。只调整 .spec.replicas 不会创建新 revision。

修改 Deployment 引用的 ConfigMap 或 Secret 内容,也不会改变 Pod 模板,因此不会自动重建 Pod。如果应用不能动态加载配置,可以把配置摘要写入 Pod 模板 annotation,摘要变化后再触发发布;也可以由明确的配置重载控制器处理。

暂停、回滚和历史版本分别控制什么

暂停 Deployment 可以连续修改多项 Pod 模板配置,恢复后再触发一次新发布。暂停只阻止模板变化触发 rollout,不会冻结现有 Pod,也不会阻止 HPA 修改副本数;暂停期间不检查 progressDeadlineSeconds,并且必须先恢复才能回滚。

Deployment 回滚的也是 Pod 模板,不会恢复历史副本数。历史模板保存在旧 ReplicaSet 中,revisionHistoryLimit 默认保留 10 个旧版本;设置为 0 后,完成发布的旧 ReplicaSet 会被清理,也就失去回滚到旧模板的能力。

maxSurgemaxUnavailable 如何共同工作

假设 Deployment 有三个副本,并设置:

1
2
3
4
5
strategy: # Deployment 的更新策略。
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1 # 非终止状态的新旧 Pod 最多比期望副本多一个。
    maxUnavailable: 0 # 更新时至少保持三个可用 Pod。

控制器可以先创建第四个 Pod,等它满足可用条件后再减少一个旧 Pod。readiness probe 不通过时,新 Pod 不会成为可用副本,发布也可能停住。正在 Terminating 的 Pod 可能继续占用资源,因此节点上实际观察到的 Pod 数和资源占用仍可能暂时超过 replicas + maxSurge

replicasupdatedReplicasreadyReplicasavailableReplicas 有什么区别

  • replicas:当前由控制器管理的非终止 Pod 数量;
  • updatedReplicas:使用最新 Pod 模板的 Pod 数量;
  • readyReplicas:当前 Ready 的 Pod 数量;
  • availableReplicas:Ready 状态持续达到 minReadySeconds 的 Pod 数量。

因此,“三个 Pod 都创建出来了”不能证明发布完成。排查时要同时看新模板副本数、Ready 数、Available 数和 Deployment conditions。

手动删除一个 Pod 会发生什么

Pod 数量少于期望状态后,所属控制器会创建替代 Pod,但不会修复原来的 Pod。Deployment 或 DaemonSet 的替代 Pod 通常会有新的名称、UID 和 IP,也不保证回到原节点;emptyDir 等随 Pod 生命周期存在的数据会丢失。

StatefulSet 会重新创建相同名称和 ordinal 的 Pod,并重新关联原 PVC,但 Pod UID 和 IP 仍会变化。控制器重建能力不能替代持久化、优雅终止和数据恢复设计。

删除工作负载控制器后,Pod 和 PVC 会怎样

Deployment、StatefulSet 和 DaemonSet 创建的下级对象会通过 ownerReferences 记录归属。默认的后台级联删除会先接受控制器删除请求,再由垃圾回收器删除其下级对象;对 Deployment 而言,中间还包括 ReplicaSet。使用孤儿删除可以保留下级对象:

1
2
# 删除 Deployment,但保留其当前的 ReplicaSet 和 Pod。
kubectl delete deployment/<name> -n <namespace> --cascade=orphan

这些对象被保留后,不再由已经删除的控制器持续维护。孤儿删除不能替代备份,也不应该依赖重建同名控制器自动接管所有旧对象;实际归属还要检查 selector、标签和 ownerReferences

StatefulSet 通过 volumeClaimTemplates 创建的 PVC 需要单独判断。默认的 persistentVolumeClaimRetentionPolicyRetain,删除 StatefulSet 或缩容时不会自动删除对应 PVC;也可以把 whenDeletedwhenScaled 配置为 Delete。这和控制器对 Pod 的级联删除是两套生命周期,PVC 删除后底层存储是否保留,还取决于 PV 的回收策略和存储驱动。

两个控制器可以使用重叠的 selector 吗

不应该。selector 重叠可能让控制器错误计算符合条件的 Pod,或者产生归属和副本管理冲突。Service selector 与工作负载 selector 可以相同,因为 Service 只选择流量后端;多个负责创建 Pod 的控制器则应使用能够区分归属的标签。

Deployment 的 .spec.selectorapps/v1 中创建后不可修改,因此标签体系应在创建资源前确定。如果必须更换 selector,通常需要重建控制器,并提前处理级联删除、Pod 归属和停机风险。

PDB 能不能限制工作负载的滚动更新

不能。PodDisruptionBudget 主要约束通过 Eviction API 发起的自愿中断,例如节点排空。Deployment 和 StatefulSet 自己执行滚动更新时不会受 PDB 限制;直接删除 Pod 或删除控制器也会绕过 PDB。

PDB 可以降低集群维护同时中断过多副本的风险,但发布期间的可用性仍要通过 Deployment 的 maxUnavailablemaxSurge,或者 StatefulSet 的更新顺序和 maxUnavailable 控制。StatefulSet 的 maxUnavailable 在 Kubernetes v1.37 中为 Beta;节点故障等非自愿中断也无法被 PDB 阻止。

StatefulSet 为什么通常需要 Headless Service

StatefulSet 的 serviceName 与 Headless Service 共同组成每个成员的稳定 DNS 名称。Headless Service 不提供 ClusterIP,也不替客户端选择某一个成员;它把当前 endpoint 地址发布到 DNS,使应用可以通过 db-0.dbdb-1.db 等名称识别具体成员。

如果调用方只需要访问任意一个健康副本,可以另外创建普通 Service。Headless Service 负责成员发现,普通 Service 负责统一入口,两者可以同时存在。

为什么要谨慎强制删除 StatefulSet Pod

当节点失联时,控制面无法立即确认旧 Pod 是否已经停止。如果强制删除 API 对象,StatefulSet 可能创建一个具有相同名称和身份的新 Pod,而旧节点上的进程仍在运行。对于数据库或需要独占卷的应用,这可能造成重复身份、卷附件冲突,甚至两个成员同时写入。

处理失联成员前,应先确认旧节点和进程已经停止,或者完成存储侧 fencing。强制删除只是移除 API 对象,不会远程保证原进程退出。

删除 StatefulSet 会按 ordinal 逆序终止吗

不保证。缩容会按照从最大 ordinal 到最小 ordinal 的顺序删除 Pod,但直接删除 StatefulSet 不保证所有 Pod 有序、优雅地终止。如果应用要求成员严格按顺序退出,应先把副本数逐步缩到 0,确认 Pod 和应用成员完成退出后再删除 StatefulSet。

节点被 cordon 后,DaemonSet Pod 会消失吗

不会因为 cordon 自动消失。DaemonSet Pod 会自动获得 node.kubernetes.io/unschedulable:NoSchedule toleration,因此控制器仍可以在被标记为不可调度的目标节点上维持节点级 Pod。kubectl drain 默认也不会像普通业务 Pod 那样驱逐 DaemonSet Pod,通常需要使用 --ignore-daemonsets 继续排空节点。

这对网络、存储和监控代理很重要:节点停止接收普通工作负载时,节点级基础设施往往仍需要继续运行。

DaemonSet 的状态字段怎么看

  • desiredNumberScheduled:应该运行 Daemon Pod 的节点总数;
  • currentNumberScheduled:目标节点中已经运行 Daemon Pod 的节点数;
  • updatedNumberScheduled:已经运行最新模板 Pod 的节点数;
  • numberReady:Pod 已经 Ready 的目标节点数;
  • numberAvailable:Pod Ready 时间达到 minReadySeconds 的目标节点数;
  • numberUnavailable:目标节点中还没有可用 Pod 的节点数;
  • numberMisscheduled:不应该运行但仍运行着 Daemon Pod 的节点数。

排查时不能只看 DESIREDCURRENTUPDATEDREADYAVAILABLEMISSCHEDULED 分别对应模板更新、探针、最短可用时间和节点选择条件,能把问题缩小到不同控制环。

常见选择误区

有状态应用都应该使用 StatefulSet 吗

不一定。判断重点不是进程是否写数据,而是副本是否需要稳定身份和独立存储。应用把状态保存在外部数据库、对象存储或共享服务中,本身的 Pod 可以互换时,Deployment 通常更合适。

StatefulSet 比 Deployment 更可靠吗

不是。两者提供不同的控制语义。StatefulSet 的有序行为和稳定身份对部分系统必要,但也可能让发布因为一个 Pod 未 Ready 而停住。可靠性仍取决于副本数、反亲和性、拓扑分布、PDB、存储、应用复制协议和故障恢复设计。

三个副本就代表高可用吗

不代表。没有调度约束时,多个副本可能集中在同一节点或可用区,一次故障就会同时影响它们。通常需要使用 Pod 反亲和性或 topologySpreadConstraints,按 kubernetes.io/hostnametopology.kubernetes.io/zone 等故障域分散副本,并确认集群确实有足够的节点和容量。

调度分散只是基础条件。PDB 主要约束自愿中断,不能阻止节点或可用区故障;有状态系统还要检查存储拓扑、数据副本和应用仲裁机制。严格使用 DoNotSchedule 时,故障域不足会让 Pod 保持 Pending;使用 ScheduleAnyway 则只会尽量分散,需要根据可用性目标选择。

DaemonSet 一定会在所有节点运行吗

它只在符合条件的节点创建 Pod。需要同时检查节点标签、node affinity、taint/toleration、资源和调度结果。控制器显示的 DESIRED 数量已经是计算约束后的期望节点数,不一定等于集群节点总数。

可以用 Deployment 模拟 DaemonSet 吗

可以组合副本数、亲和性和拓扑约束接近“分散到不同节点”,但节点增加后不会自然得到严格的每节点覆盖语义。节点级代理应直接使用 DaemonSet。

DaemonSet 和 Static Pod 有什么区别

DaemonSet 是由 API 对象和控制器统一管理的节点级工作负载,支持节点选择、滚动更新、状态统计和声明式变更。它仍依赖控制面中的 DaemonSet controller 计算目标节点并创建 Pod。

Static Pod 则由每台节点上的 kubelet 直接读取本地目录或 Web 地址中的清单并管理,不依赖 Deployment、DaemonSet 等控制器。API Server 中看到的是 kubelet 创建的 mirror Pod;删除 mirror Pod 不会停止真正的 Static Pod,kubelet 还会重新创建 mirror Pod。kubeadm 常用 Static Pod 运行控制平面组件,因为这些组件需要在普通工作负载控制器可用之前启动。

普通日志、监控、网络或存储节点代理通常优先使用 DaemonSet。只有组件需要独立于控制面启动,并且能够承担逐节点维护清单、缺少控制器级滚动发布等运维成本时,才考虑 Static Pod。

可以同时使用这三种控制器吗

可以,而且很常见。例如,业务 API 使用 Deployment,数据库由 StatefulSet 或数据库 Operator 管理,每个节点的日志与网络代理使用 DaemonSet。它们解决的是不同层次的问题,不是互斥的集群选项。

排查时先看控制器维护的对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 同时查看三种控制器的期望数量和实际状态。
kubectl get deployment,statefulset,daemonset -A

# 查看 Deployment 发布进度及其管理的 ReplicaSet。
kubectl rollout status deployment/<name> -n <namespace>
kubectl get replicaset -n <namespace>

# 按标签查看 StatefulSet Pod,再单独核对每个 ordinal 对应的 PVC。
kubectl get pod -n <namespace> -l app=<label> -o wide
kubectl get pvc -n <namespace>

# 比较 DaemonSet 的期望节点数、Ready 数和不可用数。
kubectl get daemonset/<name> -n <namespace> -o wide

# Events 通常能说明调度、镜像、探针或挂载失败原因。
kubectl describe pod/<pod-name> -n <namespace>

常见现象可以先映射到对应控制环:

现象优先检查
Deployment 发布卡住readiness probe、镜像、资源配额、调度和 ProgressDeadlineExceeded
StatefulSet 后续 Pod 不创建前一个 ordinal 是否 Running 且 Ready、PVC 是否 Bound
StatefulSet 更新停在某个 Pod该 Pod 的探针、应用启动和新旧版本兼容性
DaemonSet 数量少于节点数nodeSelector、node affinity、taint/toleration 和节点资源
Pod 重建后连不上原地址是否错误依赖 Pod IP,Service 或 Headless Service DNS 是否正确
PVC 挂载失败access mode、CSI 事件、卷拓扑和旧节点附件是否释放

如何选择

选择时可以依次问三个问题:

  1. 每个符合条件的节点是否都必须运行一个副本?如果是,使用 DaemonSet。
  2. 每个副本是否需要稳定名称、独立存储或有序生命周期?如果是,考虑 StatefulSet,并确认应用能利用这些语义。
  3. 其余长期运行且副本可互换的服务,通常使用 Deployment。

不要从“应用听起来是否有状态”开始判断,而要从控制器必须长期维持的身份、存储和分布关系开始判断。

参考资料

#Kubernetes #Deployment #StatefulSet #DaemonSet