Kubernetes 基础体系 · 第 17/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

StatefulSet:稳定身份、顺序、PVC、更新和分布式系统边界

StatefulSet 是 Kubernetes apps/v1 中用于管理有状态 Pod 的工作负载控制器。它与 Deployment 都负责维持副本数量、创建 Pod 和执行更新,但两者对 Pod 的身份处理不同:

  • Deployment 管理的是一组可互换的 Pod;
  • StatefulSet 管理的是一组具有稳定序号、稳定网络名称和稳定存储关联的 Pod。

这里的“稳定”不是指 Pod 进程永远不重启,也不是指数据一定安全,而是指 Kubernetes 会尽量让某个逻辑成员在 Pod 被替换后继续对应相同的身份和存储声明。

StatefulSet 解决的是 Kubernetes 对象层面的身份、顺序和存储关联问题;它不会自动解决数据库复制、选主、共识、事务一致性、备份恢复或跨故障域可用性。


一、先区分 Deployment 与 StatefulSet 的语义

Deployment 通常创建带有随机后缀的 ReplicaSet 和 Pod。例如:

web-7d8f9c6f5b-mxq2k
web-7d8f9c6f5b-p7v4n

某个 Pod 被删除后,Deployment 只需要创建一个新的 Pod 来补足副本数。新 Pod 可以具有不同的名称,也可以运行在不同节点上。对无状态 HTTP 服务而言,客户端通常只关心“有一个可用实例”,而不关心实例的具体身份。

StatefulSet 则为副本分配稳定的序号:

db-0
db-1
db-2

如果 db-1 被删除,StatefulSet 会重新创建名为 db-1 的 Pod。新的 Pod 具有新的 UID,容器进程也可能运行在另一台节点上,但它仍然是逻辑上的 db-1,并会重新关联 db-1 对应的 PVC。

因此需要区分三种身份:

身份 是否稳定 说明
Pod 名称 在满足控制器语义时稳定 例如 db-1
Pod UID 不稳定 Pod 重建后一定变化
容器进程身份 不稳定 可能重启、迁移或重新挂载存储

StatefulSet 提供的是逻辑成员身份,不是进程级永久性。


二、StatefulSet 的核心对象关系

一个典型 StatefulSet 通常与以下对象协作:

flowchart LR
    C[kubectl apply] --> S[StatefulSet]
    S --> P0[Pod db-0]
    S --> P1[Pod db-1]
    S --> P2[Pod db-2]

    S --> PVC0[PVC data-db-0]
    S --> PVC1[PVC data-db-1]
    S --> PVC2[PVC data-db-2]

    PVC0 --> PV0[PV 0]
    PVC1 --> PV1[PV 1]
    PVC2 --> PV2[PV 2]

    HS[Headless Service db] --> P0
    HS --> P1
    HS --> P2

其中:

  • StatefulSet 负责 Pod 的副本、序号、更新和生命周期协调;
  • Headless Service 负责提供稳定的 DNS 发现入口;
  • volumeClaimTemplates 为每个序号生成独立 PVC;
  • PVC 再通过 StorageClass 动态绑定到 PV,或绑定到预先创建的 PV。

这些职责不能互相替代。Headless Service 不会提供数据复制,PVC 不会提供数据库一致性,StatefulSet 也不会因为创建了三个 Pod 就自动形成三副本数据库。


三、稳定身份:序号、名称与 DNS

1. Pod 序号

设 StatefulSet 名称为 db,副本数为 N,则通常会得到序号集合:

I={0,1,2,,N1}I = \{0, 1, 2, \ldots, N-1\}

序号为 ii 的 Pod 名称为:

db-i

例如副本数为 3 时:

db-0
db-1
db-2

缩容会移除较大的序号。将副本数从 3 改为 2,通常移除 db-2,而不是 db-0

这并不代表应用一定应该把 db-2 视为“最不重要的节点”。StatefulSet 只按序号执行控制器层面的生命周期操作,数据库该如何安全移除成员,仍然由数据库自身的集群协议决定。

2. Headless Service 与稳定 DNS

StatefulSet 的 spec.serviceName 指向一个 Service。通常该 Service 使用:

clusterIP: None

这样的 Service 称为 Headless Service,即无 ClusterIP 的 Service。它不提供一个虚拟 IP 来负载均衡,而是让 Kubernetes DNS 为后端 Pod 发布记录。

稳定名称的一般形式是:

<pod-name>.<service-name>.<namespace>.svc.cluster.local

对于 StatefulSet db、Service db、命名空间 default

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

下面是一个完整的 Service 定义:

apiVersion: v1
kind: Service
metadata:
  name: db
  labels:
    app: demo-db
spec:
  clusterIP: None
  publishNotReadyAddresses: true
  selector:
    app: demo-db
  ports:
    - name: http
      port: 80
      targetPort: 80

selector 必须能够选中 StatefulSet 的 Pod。publishNotReadyAddresses: true 会让未处于 Ready 状态的 Pod 也可能出现在 DNS 端点中,这对某些需要“先发现成员、再完成集群初始化”的系统有用,但也会让客户端连接到尚未可用的实例。因此它不是通用的“高可用开关”。

如果未设置 publishNotReadyAddresses: true,通常只有 Ready 的 Pod 才会被视为可用端点。此时一个 Pod 可能已经创建并拥有稳定 DNS 名称,但在 readiness probe 通过前,客户端未必能通过 Service 发现它。

还要注意 DNS 不是瞬时同步的。CoreDNS、客户端 DNS 缓存和负缓存都可能造成短暂延迟。因此应用不能把“刚创建 Pod 后立即解析失败”简单等同于 StatefulSet 创建失败。


四、顺序:OrderedReady 与 Parallel

StatefulSet 的 podManagementPolicy 控制 Pod 创建和删除的协调方式:

  • OrderedReady:默认值;
  • Parallel:允许并行创建和删除。

1. OrderedReady 的行为

默认的 OrderedReady 大致遵循以下约束:

创建时:

db-0db-1db-2db\text{-}0 \rightarrow db\text{-}1 \rightarrow db\text{-}2

只有较小序号的 Pod 达到控制器认为的就绪条件后,才继续推进较大的序号。

删除或缩容时通常反向进行:

db-2db-1db-0db\text{-}2 \rightarrow db\text{-}1 \rightarrow db\text{-}0

因此,副本数从 3 缩到 1 时,控制器通常先删除 db-2,再删除 db-1

关键点在于:这里的“Ready”主要是 Kubernetes Pod 状态,而不是 StatefulSet 理解后的数据库业务健康状态。StatefulSet 不会解析数据库的复制延迟、选主状态或事务提交状态。

如果 db-0 的 readiness probe 永远失败,那么在 OrderedReady 下,db-1 可能根本不会被创建。这种停滞是顺序保证的直接结果。

2. Parallel 的行为

配置:

spec:
  podManagementPolicy: Parallel

后,StatefulSet 可以并行创建和删除多个 Pod。它仍然为 Pod 分配稳定序号,也仍然支持稳定 PVC,但不再等待低序号 Pod Ready 后才处理高序号 Pod。

这适合以下场景:

  • 应用可以自行处理成员发现;
  • 各 Pod 初始化相互独立;
  • 不需要通过创建顺序建立集群;
  • 希望缩短扩容和替换时间。

它不适合依赖严格启动顺序的应用,除非应用本身已经实现了等待、重试和成员协调。

3. 顺序保证不等于集群初始化协议

例如,某数据库要求:

  1. db-0 先初始化;
  2. db-1db-2 再加入;
  3. 集群形成多数派;
  4. 之后才允许业务请求。

StatefulSet 的 OrderedReady 最多能帮助控制 Pod 的创建顺序;它不会自动执行上述数据库命令,也不会判断“初始化是否已经提交”。这些逻辑需要数据库镜像、启动脚本或 Operator 完成。


五、PVC:每个序号对应独立存储

1. volumeClaimTemplates 的展开

StatefulSet 可以使用 volumeClaimTemplates

volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: standard
      resources:
        requests:
          storage: 10Gi

假设 StatefulSet 名称为 db,副本数为 3,模板名称为 data,则通常会创建:

data-db-0
data-db-1
data-db-2

对应关系可以写成:

PVCi=data+""+StatefulSetName+""+iPVC_i = \text{data} + "-" + \text{StatefulSetName} + "-" + i

这意味着 db-0db-1 默认不是共享同一个 PVC,而是分别使用自己的 PVC。

Pod 模板中需要通过相同的卷名称引用它:

spec:
  containers:
    - name: app
      image: nginx:1.27
      volumeMounts:
        - name: data
          mountPath: /var/lib/app

这里的 data 不是 PVC 名称,而是 volumeClaimTemplates.metadata.name。StatefulSet 控制器会将它解析为当前序号对应的 PVC。

2. PVC 与 PV 的生命周期不是 Pod 生命周期

删除 Pod:

kubectl delete pod db-1

通常只删除 Pod,不删除 data-db-1。新建的 db-1 会尝试重新挂载同一个 PVC。

因此,Pod 重建后的关键变化通常是:

旧 Pod: name=db-1, uid=旧值
新 Pod: name=db-1, uid=新值
PVC:    data-db-1,不变

删除 StatefulSet 时,PVC 默认也不会因为 StatefulSet 消失而自动删除。这是重要的安全默认值,因为误删 StatefulSet 不应该顺带无条件删除有价值的数据。

较新的 Kubernetes 版本支持 persistentVolumeClaimRetentionPolicy,可以显式控制 PVC 在 StatefulSet 删除或缩容时的保留策略,例如:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Retain

Retain 表示保留,Delete 表示由控制器删除对应 PVC。该字段的可用性和默认行为应以目标集群的 Kubernetes 版本为准;文章示例假设集群支持当前稳定的 apps/v1 能力。即使 PVC 被删除,PV 是否被回收还受 PV 的 persistentVolumeReclaimPolicy 影响,常见值包括 RetainDelete。这两个策略属于不同层次:

StatefulSet/PVC retention
        ↓
PVC 是否还存在
        ↓
PV reclaim policy
        ↓
底层卷是否释放或保留

3. PVC 模板不会替已有 PVC 自动迁移数据

修改 volumeClaimTemplates 中的容量、StorageClass 或其他字段,不等于自动迁移已有数据。

例如,将模板容量从 10Gi 改为 100Gi,至少涉及:

  1. 已存在的 PVC 是否被更新;
  2. StorageClass 和 CSI 驱动是否支持扩容;
  3. PV 是否允许扩容;
  4. 文件系统是否需要在线或离线扩展;
  5. 应用是否能观察到新的容量。

不能把“修改 StatefulSet YAML”当成完整的存储迁移方案。生产环境需要先验证存储驱动、PVC 状态和应用数据一致性。

4. 访问模式不是分布式锁

ReadWriteOnce(RWO)表示卷可以被一个节点以读写方式挂载;它不严格表示“整个集群中只有一个 Pod 能同时读取和写入”。在某些实现和调度情况下,同一节点上的多个 Pod 可能共同使用该卷。

如果要求更严格的“单 Pod 读写”,需要评估 ReadWriteOncePod(RWOP)以及目标 CSI 驱动是否支持。访问模式只是卷挂载约束,不是数据库锁,也不是跨进程一致性协议。


六、一个可运行的 StatefulSet 示例

下面的示例创建:

  • 一个 Headless Service;
  • 一个三副本 StatefulSet;
  • 每个 Pod 一个 1 GiB PVC;
  • 每个 Pod 把自己的主机名写入持久卷;
  • 使用 HTTP readiness probe。

该示例用于观察 StatefulSet 行为,不构成生产数据库部署方案。

apiVersion: v1
kind: Service
metadata:
  name: demo-db
  labels:
    app: demo-db
spec:
  clusterIP: None
  selector:
    app: demo-db
  ports:
    - name: http
      port: 80
      targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: demo-db
spec:
  serviceName: demo-db
  replicas: 3
  podManagementPolicy: OrderedReady
  selector:
    matchLabels:
      app: demo-db
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: demo-db
    spec:
      terminationGracePeriodSeconds: 10
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - name: http
              containerPort: 80
          command:
            - /bin/sh
            - -c
            - |
              echo "pod=$(hostname)" > /usr/share/nginx/html/identity.txt
              exec nginx -g 'daemon off;'
          readinessProbe:
            httpGet:
              path: /identity.txt
              port: http
            initialDelaySeconds: 2
            periodSeconds: 3
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi

应用并观察:

kubectl apply -f statefulset-demo.yaml

kubectl get pods -l app=demo-db -w
kubectl get pvc
kubectl get pods -l app=demo-db \
  -o custom-columns=NAME:.metadata.name,UID:.metadata.uid,READY:.status.containerStatuses[0].ready

在支持动态供应的集群中,预期可以看到:

demo-db-0
demo-db-1
demo-db-2

以及:

data-demo-db-0
data-demo-db-1
data-demo-db-2

如果 Pod 停在 Pending,常见原因不是 StatefulSet 顺序,而是 PVC 无法绑定,例如:

  • 集群没有默认 StorageClass;
  • storageClassName 不存在;
  • 没有符合条件的 PV;
  • 存储拓扑与节点不兼容;
  • CSI 驱动故障。

可以用以下命令查看原因:

kubectl describe pod demo-db-0
kubectl describe pvc data-demo-db-0
kubectl get events --sort-by=.lastTimestamp

从临时 Pod 查询稳定 DNS:

kubectl run dns-test \
  --rm -it \
  --restart=Never \
  --image=busybox:1.36 \
  -- nslookup demo-db-0.demo-db.default.svc.cluster.local

如果命名空间不是 default,需要替换 DNS 名称中的命名空间部分。

访问某个成员:

kubectl run curl-test \
  --rm -it \
  --restart=Never \
  --image=curlimages/curl:8.10.1 \
  -- curl -s http://demo-db-1.demo-db.default.svc.cluster.local/identity.txt

预期输出类似:

pod=demo-db-1

删除 demo-db-1 后再次检查:

kubectl delete pod demo-db-1
kubectl get pods -w
kubectl get pvc

新 Pod 的 UID 会变化,但名称和 PVC 名称保持为:

Pod: demo-db-1
PVC: data-demo-db-1

这证明的是 Kubernetes 身份和存储关联保持稳定,不证明应用数据已经正确复制或事务已经提交。


七、更新机制:RollingUpdate、partition 与 OnDelete

1. 默认 RollingUpdate

StatefulSet 默认使用:

updateStrategy:
  type: RollingUpdate

当 Pod 模板发生变化,例如镜像从 nginx:1.27 改为另一个版本,控制器会按照 StatefulSet 的顺序执行替换。常见过程是从较大的序号开始:

删除并重建 db-2
等待 db-2 Ready
删除并重建 db-1
等待 db-1 Ready
删除并重建 db-0
等待 db-0 Ready

实际推进受 Pod 管理策略、Ready 状态和控制器实现约束影响。默认更新不会因为容器进程已经启动就立即认为完成;readiness probe、启动失败、卷挂载失败都可能阻塞后续更新。

检查状态:

kubectl rollout status statefulset/demo-db
kubectl rollout history statefulset/demo-db
kubectl get controllerrevision

StatefulSet 会使用 ControllerRevision 记录修订版本,便于控制器判断 Pod 是否需要更新。它不是完整的应用发布系统,也不会替代数据库迁移和回滚策略。

2. partition:先更新一个高序号成员

partition 将 StatefulSet 的 Pod 划分为更新区间。假设:

updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    partition: 2

通常表示只更新序号满足:

ordinal2ordinal \ge 2

的 Pod。因此:

db-2:更新
db-1:保留旧模板
db-0:保留旧模板

这可以用于金丝雀式验证:先只让 db-2 运行新版本,确认兼容性后再逐步降低 partition。

例如:

kubectl patch statefulset demo-db --type=merge \
  -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'

随后修改 Pod 模板镜像,观察只有 demo-db-2 先被替换。确认后将 partition 改为 0:

kubectl patch statefulset demo-db --type=merge \
  -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'

不同 Kubernetes 版本对可选更新字段和行为细节可能存在差异,应以目标集群的 OpenAPI schema 和官方版本文档为准。

3. OnDelete:控制器不主动替换旧 Pod

配置:

updateStrategy:
  type: OnDelete

后,修改 Pod 模板只会更新 StatefulSet 的期望模板;已有 Pod 不会因为模板变化而自动删除。只有手动删除 Pod,控制器才会按新模板重建它。

这给应用运维更强的控制权,但也增加了风险:StatefulSet 可能长期处于“模板已更新、部分 Pod 仍使用旧版本”的状态。需要明确记录每个序号的实际版本:

kubectl get pods -l app=demo-db \
  -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image

八、更新为什么会卡住

StatefulSet 的顺序更新是有意保守的。以下场景都可能导致更新卡住:

1. Readiness probe 永远失败

kubectl describe pod demo-db-2
kubectl logs demo-db-2

应重点查看:

  • Readiness probe failed
  • 应用监听地址或端口错误;
  • 应用启动时间超过探针容忍范围;
  • 探针依赖的外部服务不可用。

如果 db-2 没有 Ready,后续 db-1db-0 可能不会继续更新。

2. PVC 无法挂载

典型事件包括:

FailedAttachVolume
FailedMount
Multi-Attach error

可能原因:

  • 上一个 Pod 仍处于 Terminating;
  • 云盘无法同时挂载到多个节点;
  • 节点故障后卷的附件状态尚未清理;
  • StorageClass 的拓扑约束与调度结果冲突。

此时不应直接强制删除 Pod。强制删除可能让 Kubernetes 很快创建同名新 Pod,而底层存储或旧进程仍未真正退出,形成两个进程同时操作同一数据的风险。应先确认旧进程、卷附件状态以及数据库是否安全停止。

3. 应用层拒绝启动

容器处于 Running 不代表应用已经能作为集群成员工作。例如:

  • 新版本配置格式不兼容;
  • 节点发现地址错误;
  • 数据目录来自旧版本;
  • 集群要求先执行迁移;
  • 节点启动后发现自己不是合法成员。

Kubernetes 只能观察进程、端口和探针结果,不能替应用完成协议升级。


九、缩容、删除与 PVC 保留的边界

执行:

kubectl scale statefulset demo-db --replicas=1

在默认策略下,通常会删除:

demo-db-2
demo-db-1

并保留:

demo-db-0
data-demo-db-0
data-demo-db-1
data-demo-db-2

之后重新扩容到 3:

kubectl scale statefulset demo-db --replicas=3

控制器通常会重新创建 demo-db-1demo-db-2,并尝试使用原来的 PVC。

这说明缩容并不等同于删除数据。它更接近于:

停止并移除高序号成员
保留这些成员的存储声明

对于数据库而言,缩容前仍然需要执行应用层操作,例如:

  1. 从集群成员列表中移除目标节点;
  2. 确认副本或分片已经迁移;
  3. 确认剩余节点仍满足法定人数;
  4. 再缩小 StatefulSet 副本数。

直接执行 Kubernetes 缩容,可能让数据库认为成员突然失联,甚至破坏多数派。

删除 StatefulSet 时,可以先查看对象和 PVC:

kubectl get statefulset demo-db
kubectl get pvc -l app=demo-db

如果目标是删除控制器但保留数据,应显式确认 PVC 保留策略,并避免把删除 PVC 的命令与删除 StatefulSet 混在一起。恢复前还要确认旧 Pod 已经真正停止、卷没有被其他实例使用。


十、StatefulSet 不会自动形成分布式系统

这是最重要的边界。

假设 StatefulSet 创建了 3 个 Pod:

db-0
db-1
db-2

它只保证 Kubernetes 层面的若干事实:

  • 有 3 个期望副本;
  • 每个副本有稳定序号;
  • 每个副本可以关联独立 PVC;
  • 可以按一定顺序创建、删除和更新;
  • Pod 可以通过稳定名称被发现。

它不保证:

  • 三个 Pod 之间已经复制数据;
  • 数据已经持久化到多个故障域;
  • 任意两个 Pod 可以自动组成多数派;
  • db-0 一定是 Leader;
  • 删除 db-2 一定安全;
  • Pod Ready 就代表数据库可写;
  • 一个 PVC 的数据在其他 PVC 上存在副本;
  • 备份一定可恢复。

1. 多数派的形式化条件

以典型多数派协议为例,若集群有 nn 个成员,允许同时失效 ff 个成员而仍保留多数派,通常需要:

n2f+1n \ge 2f + 1

原因是多数派大小为:

q=n2+1q = \left\lfloor \frac{n}{2} \right\rfloor + 1

要在 ff 个成员失效后仍有多数派,需要:

nfqn-f \ge q

n=3n=3 时:

多数派 = 2
可容忍失效 = 1

但这只说明成员数量满足一个数学条件。若三个 Pod 都位于同一节点、同一可用区或同一个存储故障域,那么一次节点或区域故障仍可能同时丢失全部成员。

因此,Kubernetes 的副本数与分布式系统的容错副本不是同一个概念。还需要结合:

  • topology spread constraints;
  • pod anti-affinity;
  • StorageClass 的拓扑绑定;
  • 数据库自身的复制协议;
  • 备份和恢复流程。

2. Readiness 不等于数据库可用性

一个数据库 Pod 的 readiness probe 可能只检查 TCP 端口:

readinessProbe:
  tcpSocket:
    port: 5432

这只能证明端口可连接,不能证明:

  • 节点已经加入集群;
  • 节点数据已追平;
  • 节点可以接受写请求;
  • 当前节点不是只读副本;
  • 当前事务日志已持久化;
  • 集群仍有多数派。

更可靠的探针通常需要调用数据库提供的健康检查接口,并明确区分:

进程存活
实例已初始化
节点可接受连接
节点可接受写入
节点属于当前多数派

这些状态不能由 StatefulSet 自动推导。


十一、应用如何使用稳定身份

应用可以通过 Downward API 获得自己的 Pod 名称:

env:
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: POD_NAMESPACE
    valueFrom:
      fieldRef:
        fieldPath: metadata.namespace

容器内可能得到:

POD_NAME=demo-db-1
POD_NAMESPACE=default

应用可以从名称末尾解析序号,但这不是所有应用都应该采用的成员标识方案。更稳妥的做法是:

  • 应用明确记录成员 ID;
  • 启动时根据 Pod 名称和配置计算预期地址;
  • 发现其他成员时进行重试;
  • 对成员加入、退出和数据追赶实现幂等处理;
  • 不把“序号较小”误认为“拥有更高权限”。

例如,db-0 可以作为初始引导地址,但 Leader 身份应由数据库协议或选举机制决定,而不是由 StatefulSet 名称决定。


十二、Operator 与 StatefulSet 的分工

直接编写 StatefulSet 适合:

  • 学习和验证控制器行为;
  • 部署有简单身份需求的服务;
  • 应用已经自行实现集群初始化和成员管理;
  • 运维人员愿意手动处理升级、备份和恢复。

当系统涉及复杂数据库协议时,Operator 通常负责比 StatefulSet 更多的事情,例如:

  • 初始化集群;
  • 生成或轮换证书;
  • 执行安全扩缩容;
  • 处理 Leader 迁移;
  • 识别复制延迟;
  • 执行版本升级;
  • 创建备份和恢复任务;
  • 配置监控和告警;
  • 在故障后重建成员。

但 Operator 也不是数据安全的替代品。它仍然依赖底层 PV、CSI、网络、备份存储和正确的恢复测试。安装一个数据库 Operator,不等于已经建立了可验证的备份责任链。


十三、故障诊断的路径

遇到 StatefulSet 异常时,应先判断故障属于哪一层。

1. 控制器和模板层

kubectl get statefulset demo-db -o yaml
kubectl describe statefulset demo-db

关注:

  • selector 是否匹配 Pod 标签;
  • serviceName 是否存在;
  • replicas 与 Ready 数量;
  • 更新策略和 partition;
  • 事件中的创建或删除失败。

2. Pod 调度与容器层

kubectl get pods -l app=demo-db -o wide
kubectl describe pod demo-db-1
kubectl logs demo-db-1 --all-containers

关注:

  • Pending:调度、PVC、资源或拓扑问题;
  • ContainerCreating:镜像、挂载、网络或 Secret 问题;
  • CrashLoopBackOff:进程启动失败;
  • Running 但 NotReady:探针或业务健康状态问题。

3. PVC、PV 与存储层

kubectl get pvc,pv
kubectl describe pvc data-demo-db-1
kubectl get storageclass

关注:

  • PVC 是否 Bound
  • PVC 请求的 StorageClass 是否存在;
  • PV 的访问模式和容量是否匹配;
  • CSI 驱动是否报告 Attach、Mount 或拓扑错误;
  • 文件系统扩容是否完成。

4. 应用集群层

即使 Kubernetes 对象全部正常,也要检查应用自身:

  • 集群成员列表;
  • Leader 或 Primary 状态;
  • 复制延迟;
  • 法定人数;
  • WAL、日志或分片状态;
  • 应用级备份和恢复验证。

这一步无法由 kubectl get pods 替代。


十四、常见误解与反例

误解一:三个 StatefulSet Pod 就是三副本数据

反例:三个 Pod 各自挂载三个独立 PVC,但应用只是把数据写到本地目录,没有复制协议。此时任意 PVC 损坏都可能导致对应数据丢失。

误解二:Pod 名称稳定,所以进程状态稳定

反例:db-1 被删除后,新 Pod 仍名为 db-1,但 UID、节点、IP、容器临时文件和内存状态都已经变化。应用必须从持久化数据和集群协议恢复,而不能依赖旧进程仍然存在。

误解三:OrderedReady 能自动完成数据库启动顺序

反例:db-0 已经 Ready,但它可能尚未完成数据库初始化,只是端口已经打开。StatefulSet 会继续创建 db-1,因为它只看 Kubernetes 的就绪条件。

误解四:缩容到一个副本就是安全删除其他数据库节点

反例:共识集群可能要求先执行成员移除。如果直接删除高序号 Pod,剩余成员可能仍把它视为投票成员,导致选举或提交受阻。

误解五:删除 StatefulSet 就删除了所有数据

默认情况下,StatefulSet 删除通常不会自动删除 PVC。但 PVC 的显式 retention policy、PV reclaim policy、云厂商存储控制器和人工清理命令都可能改变最终结果,必须逐层确认。


十五、什么时候不该使用 StatefulSet

以下场景通常不需要 StatefulSet:

  • Web、API、Worker 等实例完全可互换;
  • 应用不需要稳定主机名;
  • 应用不需要每个副本独立持久卷;
  • 副本启动和删除没有顺序要求;
  • 使用共享对象存储或外部数据库,而不是每个 Pod 独立磁盘。

此时 Deployment 更直接。强行使用 StatefulSet 不会自动带来更高可靠性,反而可能引入:

  • 更新被单个 Pod 阻塞;
  • PVC 长期残留;
  • 缩容操作复杂;
  • 错误地把 Pod 序号当成数据库角色;
  • 运维人员误以为副本已经具备数据冗余。

StatefulSet 适用于“实例身份和实例存储本身有意义”的系统,但它只是分布式系统的基础设施协调层。完整的有状态服务还需要应用协议、存储拓扑、备份恢复、升级兼容性和明确的责任边界共同成立。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。