Kubernetes StatefulSet 完全指南(适用于 Kubernetes v1.35)

文档说明

  • 适用版本:Kubernetes v1.35.4(apiVersion: apps/v1)
  • 前置知识:了解 Pod、Deployment、PV/PVC 的基本概念
  • 目标:从零掌握 StatefulSet 的原理、配置与使用

第一章:为什么需要 StatefulSet?

1.1 无状态 vs 有状态

在 Kubernetes 中,Deployment 和 ReplicaSet 是为无状态应用设计的:

  • Pod 是可互换的,任何一个 Pod 都可以被另一个完全相同的 Pod 替换
  • Pod 名称是随机生成的(如 nginx-7d8f9b8c-xyz
  • Pod 重启后 IP 会变化,名称也会变化
  • 不提供持久化存储

但很多应用是有状态的:

  • 数据库(MySQL、PostgreSQL、Cassandra):每个实例有自己的数据,不能随意替换
  • 分布式系统(ZooKeeper、Etcd、Kafka):节点有固定的集群角色和标识
  • 消息队列(RabbitMQ):需要稳定的网络标识进行集群通信

一句话总结:Deployment 的 Pod 像流水线上的工人——谁来了都能干一样的活;StatefulSet 的 Pod 像公司的正式员工——每个人有自己的工号、工位和档案,不能随便替换。

1.2 StatefulSet 是什么?

StatefulSet 是 Kubernetes 中专门用于管理有状态应用的工作负载 API 对象。它运行一组 Pod,并为每个 Pod 保留一个稳定的、粘性的标识

StatefulSet 提供的核心能力

能力说明
稳定的网络标识每个 Pod 拥有唯一的、持久的 DNS 名称
稳定的持久存储每个 Pod 绑定独立的 PersistentVolume,Pod 重建后自动重连
有序的部署和扩缩Pod 按顺序创建和删除(0→1→2…,缩容时反向)
有序的滚动更新按顺序逐个更新 Pod,降低风险

第二章:StatefulSet 的工作原理

2.1 稳定的网络标识 —— “你是谁”永远不会变

StatefulSet 为每个 Pod 分配一个固定的序号(Ordinal)

Pod 命名规则<statefulset-name>-<ordinal>

例如,一个名为 web 的 StatefulSet 有 3 个副本,Pod 名称分别为:

  • web-0
  • web-1
  • web-2

这些名称是永久的——即使 Pod 被删除并重新创建,新 Pod 依然叫 web-0

配合 Headless Service 实现稳定的 DNS

StatefulSet 必须搭配 Headless Service(无头服务,clusterIP: None)使用。每个 Pod 会获得一个固定的 DNS 域名:

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

例如 web-0.nginx.default.svc.cluster.local

这意味着什么? 即使 web-0 这个 Pod 挂了又重建,IP 变了,但其他 Pod 依然可以通过这个 DNS 名称找到它。

2.2 有序的部署和扩缩 —— “先来后到”有规矩

创建/扩容(从 0 到 N) :按序号从小到大依次创建

  1. 先创建 web-0,等它 RunningReady
  2. 再创建 web-1,等它 RunningReady
  3. 再创建 web-2

删除/缩容(从 N 到 0) :按序号从大到小依次删除

  1. 先删除 web-2,等它完全终止后
  2. 再删除 web-1,等它完全终止后
  3. 再删除 web-0

为什么需要有序? 对于数据库集群,主从复制往往要求主节点先启动(通常是 -0),从节点按顺序加入集群。乱序启动可能导致集群初始化失败。

2.3 稳定的持久存储 —— “你的数据永远是你的”

StatefulSet 通过 volumeClaimTemplates每个 Pod 自动创建独立的 PVC,实现“一个 Pod 一份专属存储”。

工作原理

  1. 你在 StatefulSet 中定义 volumeClaimTemplates
  2. 创建 web-0 时,自动创建 PVC data-web-0 并绑定 PV
  3. 创建 web-1 时,自动创建 PVC data-web-1 并绑定 PV
  4. 即使 web-0 被删除重建,新 Pod 会自动挂载同一个 PVC data-web-0

关键点:删除 StatefulSet 不会删除关联的 PVC 和 PV。这是为了保护数据安全——你需要手动清理 PVC 才会真正删除数据。

2.4 组件架构图

┌─────────────────────────────────────────────────────────────────┐
│                      Kubernetes 集群                            │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │              Headless Service (无头服务)                 │   │
│  │         为每个 Pod 提供稳定的 DNS 域名                   │   │
│  │    web-0.nginx.default.svc.cluster.local               │   │
│  │    web-1.nginx.default.svc.cluster.local               │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                  │
│              ┌───────────────┼───────────────┐                 │
│              ▼               ▼               ▼                 │
│  ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│
│  │     web-0        │ │     web-1        │ │     web-2        ││
│  │  (稳定的名称)     │ │  (稳定的名称)     │ │  (稳定的名称)     ││
│  └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘│
│           │                    │                    │          │
│           ▼                    ▼                    ▼          │
│  ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│
│  │  PVC: data-web-0 │ │  PVC: data-web-1 │ │  PVC: data-web-2 ││
│  │  (专属存储)       │ │  (专属存储)       │ │  (专属存储)       ││
│  └──────────────────┘ └──────────────────┘ └──────────────────┘│
└─────────────────────────────────────────────────────────────────┘

第三章:StatefulSet vs Deployment —— 核心区别

对比维度DeploymentStatefulSet
适用场景无状态应用有状态应用
Pod 名称随机生成(如 nginx-7d8f9-xyz固定序号(如 web-0
网络标识无稳定标识,IP 变化稳定的 DNS 名称
存储不提供持久化存储每个 Pod 独立 PVC
创建/删除顺序并行有序(0→1→2,缩容反向)
更新策略滚动更新,可自动回滚有序滚动更新,需谨慎
Pod 管理通过 ReplicaSet直接管理

什么时候用 StatefulSet?

  • 数据库(MySQL、PostgreSQL、Cassandra、MongoDB)
  • 分布式协调服务(ZooKeeper、Etcd)
  • 消息队列(Kafka、RabbitMQ)
  • 任何需要稳定标识持久化存储的应用

第四章:StatefulSet YAML 详解

4.1 API 版本

apiVersion: apps/v1          # Kubernetes v1.35 稳定版本
kind: StatefulSet

4.2 完整 YAML 结构

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web                  # StatefulSet 名称
  namespace: default
spec:
  replicas: 3                # 副本数
  
  # 1. 必须:指定 Headless Service 名称
  serviceName: nginx         # 必须存在对应的 Headless Service
  
  # 2. 选择器:匹配 Pod 标签
  selector:
    matchLabels:
      app: nginx
  
  # 3. Pod 模板
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  
  # 4. 持久存储模板(核心特性)
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: standard
      resources:
        requests:
          storage: 1Gi
  
  # 5. Pod 管理策略(可选)
  podManagementPolicy: OrderedReady   # 默认:OrderedReady / Parallel
  
  # 6. 更新策略(可选)
  updateStrategy:
    type: RollingUpdate                # RollingUpdate(默认)/ OnDelete
    rollingUpdate:
      partition: 0                     # 分区更新,控制更新范围

4.3 核心字段详解

serviceName —— 必须指定

指定管理该 StatefulSet 的 Headless Service 名称。这个 Service 必须提前创建,StatefulSet 依赖它来生成 Pod 的 DNS 记录。

volumeClaimTemplates —— StatefulSet 的灵魂

这是 StatefulSet 区别于 Deployment 最核心的字段

  • 每个 Pod 自动创建一个独立的 PVC
  • PVC 命名规则:<volumeClaimTemplates.metadata.name>-<statefulset-name>-<ordinal>
  • 例如:data-web-0data-web-1data-web-2
podManagementPolicy —— Pod 管理策略
策略说明
OrderedReady(默认)有序创建/删除,前一个 Ready 后操作下一个
Parallel并行创建/删除,不等待前一个 Ready
updateStrategy —— 更新策略
策略说明
RollingUpdate(默认)按序号从大到小逐个更新 Pod(先更新 web-2,再 web-1,最后 web-0)
OnDelete只有手动删除 Pod 时才会用新模板重建
partition —— 分区更新(重要)

RollingUpdate 策略下,可以通过 partition 控制更新范围:

updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    partition: 2
  • 序号 ≥ partition 的 Pod 会被更新(如 web-2、web-3…)
  • 序号 < partition 的 Pod 保持旧版本(如 web-0、web-1)

用途灰度发布 / 金丝雀发布。先更新一部分 Pod 验证,确认没问题后再调整 partition 更新剩余 Pod。

第五章:完整实战示例 —— 部署 Nginx 有状态应用

5.1 创建 Headless Service

# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None              # 关键:Headless Service
  selector:
    app: nginx

5.2 创建 StatefulSet

# statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: nginx           # 引用上面的 Headless Service
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

5.3 部署与验证

# 1. 创建 Headless Service
kubectl apply -f headless-service.yaml

# 2. 创建 StatefulSet
kubectl apply -f statefulset.yaml

# 3. 查看 Pod 创建顺序(web-0 → web-1 → web-2)
kubectl get pods -w

# 输出示例
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          10s
web-1   1/1     Running   0          25s
web-2   1/1     Running   0          40s

# 4. 查看 PVC(每个 Pod 有独立的 PVC)
kubectl get pvc

# 输出示例
NAME          STATUS   VOLUME                                     CAPACITY   AGE
data-web-0    Bound    pvc-xxx-xxx-xxx                           1Gi        1m
data-web-1    Bound    pvc-yyy-yyy-yyy                           1Gi        1m
data-web-2    Bound    pvc-zzz-zzz-zzz                           1Gi        1m

# 5. 验证稳定的 DNS(进入一个 Pod 解析其他 Pod)
kubectl exec -it web-0 -- nslookup web-1.nginx.default.svc.cluster.local

# 6. 验证稳定的身份——删除 web-0,看它重建后是否还叫 web-0
kubectl delete pod web-0
kubectl get pods -w
# web-0 会被重新创建,名称不变

5.4 扩缩容

# 扩容到 5 个副本(按顺序创建 web-3、web-4)
kubectl scale statefulset web --replicas=5

# 缩容到 2 个副本(按顺序删除 web-4、web-3)
kubectl scale statefulset web --replicas=2

# 注意:缩容不会删除 PVC,数据依然保留
kubectl get pvc
# data-web-3 和 data-web-4 依然存在

5.5 滚动更新

# 更新镜像版本
kubectl patch statefulset web -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.26"}]}}}}'

# 观察更新顺序(从大到小:web-2 → web-1 → web-0)
kubectl get pods -w

# 查看更新状态
kubectl rollout status statefulset web

# 回滚
kubectl rollout undo statefulset web

第六章:高级场景 —— 分区更新(金丝雀发布)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  # ... 其他配置 ...
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2              # 只更新序号 ≥ 2 的 Pod

效果

  • web-2web-3… 会使用新版本(金丝雀)
  • web-0web-1 保持旧版本(稳定)

发布流程

  1. 设置 partition: 2,只更新 web-2
  2. 验证 web-2 运行正常
  3. 调整 partition: 0,更新所有 Pod

第七章:StatefulSet 的限制与注意事项

限制说明
必须使用 Headless ServiceStatefulSet 依赖无头服务提供网络标识
存储需提前准备PVC 需要 StorageClass 动态制备或管理员静态制备
删除 StatefulSet 不会删除 PVC数据安全优先,需手动清理 PVC
删除 StatefulSet 不保证 Pod 优雅终止建议先缩容到 0 再删除
滚动更新可能卡住如果 Pod 一直处于 Pending 或 CrashLoopBackOff,更新会卡住

第八章:最佳实践总结

  1. 用对场景只有真正需要稳定标识或持久化存储的应用才用 StatefulSet。能用 Deployment 解决的,别用 StatefulSet。

  2. 提前规划存储:确保集群有可用的 StorageClass 或预先创建好 PV。

  3. Headless Service 先创建:StatefulSet 依赖它,必须先存在。

  4. 谨慎缩容:缩容不会删除 PVC,如果不再需要记得手动清理,避免浪费存储资源。

  5. 善用分区更新:生产环境使用 partition 做灰度发布,降低风险。

  6. 删除前先缩容:删除 StatefulSet 前先缩容到 0,确保 Pod 优雅终止。

  7. 监控 PVC 使用:StatefulSet 的 PVC 是持久化的,注意监控存储容量,避免写满。

附录:快速索引

需求命令
创建 StatefulSetkubectl apply -f statefulset.yaml
查看 StatefulSetkubectl get statefulset
查看 StatefulSet 详情kubectl describe statefulset <name>
查看 Pod(含序号)kubectl get pods -l app=nginx
查看 PVCkubectl get pvc
扩容kubectl scale statefulset <name> --replicas=5
更新镜像kubectl set image statefulset/<name> <container>=<new-image>
查看滚动更新状态kubectl rollout status statefulset <name>
回滚kubectl rollout undo statefulset <name>
删除 StatefulSet(保留 PVC)kubectl delete statefulset <name>
删除 StatefulSet 和 PVCkubectl delete statefulset <name> --cascade=orphan 然后手动删 PVC

文档版本:v1.0
适用环境:Kubernetes v1.35+ / apps/v1
前置条件:集群已配置 StorageClass 或静态 PV

更多推荐