Skip to content

Kubernetes-StatefulSet

StatefulSet 是 Kubernetes 中用于管理有状态应用的核心控制器,它为分布式系统提供了稳定的网络标识、有序部署和持久化存储等关键特性。本文将全面解析 StatefulSet 的设计思想、工作原理、典型应用场景以及生产环境最佳实践,帮助您掌握这一强大的 Kubernetes 资源对象。

StatefulSet 核心概念与设计思想

什么是有状态应用

有状态应用(Stateful Application)是指需要维护客户端状态信息或依赖持久化存储的应用,其特点包括:

  • 实例间不对等:如主从关系、主备关系等拓扑结构
  • 数据持久性:应用实例在本地磁盘保存数据,重启后仍需访问相同数据
  • 稳定标识:需要固定的网络标识(如主机名)来确保服务发现和通信

典型的有状态应用包括数据库(MySQL、PostgreSQL)、消息队列(Kafka、RabbitMQ)和分布式存储系统(Elasticsearch、Cassandra)等

StatefulSet 设计理念

StatefulSet 的设计抽象了真实世界应用状态的两种表现形式:

  1. 拓扑状态:应用多个实例之间的启动顺序和网络标识要求
    • 主节点A必须先于从节点B启动
    • 删除后重建必须保持相同顺序和网络标识
  2. 存储状态:应用实例绑定的不同存储数据
    • Pod A 第一次读取和十分钟后读取的应该是同一份数据
    • 即使Pod被重建,仍需访问原有数据

StatefulSet 核心特性

稳定的网络标识

  • 唯一Pod名称:格式为<statefulset名称>-<序号>(如web-0, web-1)
  • 固定DNS子域名<pod-name>.<service-name>.<namespace>.svc.cluster.local
  • 持久IP地址:当底层网络支持时(如使用Headless Service),Pod IP在重新调度后保持不变

持久化存储

  • volumeClaimTemplates:为每个Pod动态创建独立的PVC(PersistentVolumeClaim)
  • 生命周期解耦:PVC/PV在Pod删除后保留,新Pod自动绑定原有存储
  • 数据独享:每个Pod有自己专属的数据目录,不与其他Pod共享

有序部署与扩展

  • 顺序创建:按索引递增顺序(0→1→2)部署Pod
  • 逆序删除:缩容时按索引递减顺序(2→1→0)终止Pod
  • 依赖保障:前序Pod进入Running+Ready状态后才创建后续Pod

与Headless Service的强绑定

StatefulSet必须关联一个Headless Service(clusterIP: None)来实现:

  • 直接Pod访问:DNS查询返回Pod IP而非Service VIP
  • 稳定发现机制:为每个Pod提供可解析的DNS记录
  • 拓扑状态维护:通过DNS记录保持Pod网络标识稳定性

必须绑定的 Service 类型

Service引用
yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql  # 这个名称将被StatefulSet引用
  namespace: default
spec:
  clusterIP: None  # 定义为Headless Service(关键配置)
  ports:
  - port: 3306
    name: mysql
  selector:
    app: mysql  # 必须匹配StatefulSet的Pod标签
StatefulSet 引用
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: "mysql"  # 必须与上述Service的metadata.name一致
  replicas: 3
  selector:
    matchLabels:
      app: mysql  # 必须与Service的selector匹配
  template:
    metadata:
      labels:
        app: mysql  # 必须与Service的selector匹配
    # ... 其他配置 ...

每个 Pod 提供可解析的 DNS 记录,格式为:

bash
<pod-name>.<service-name>.<namespace>.svc.cluster.local
# 例如:mysql-0.mysql.default.svc.cluster.local

StatefulSet 与 Deployment 的关键区别

特性DeploymentStatefulSet
适用场景无状态应用(Web服务、缓存)有状态应用(数据库、消息队列)
Pod标识随机名称,无固定标识唯一名称(web-0, web-1)
存储临时存储(Volume可共享)独立持久化存储(Volume与Pod绑定)
扩缩容顺序并行创建/删除顺序创建,逆序删除
网络标识通过Service负载均衡稳定的DNS记录(直接访问单个Pod)
更新策略滚动更新(并行)有序滚动更新(默认)或手动更新
服务依赖不需要特殊Service必须关联Headless Service

StatefulSet 典型应用场景

数据库集群

  • MySQL/PostgreSQL主从:确保主节点先于从节点启动
  • MongoDB分片集群:维护分片节点的稳定标识

消息队列系统

  • Kafka集群:Broker需要持久化存储消息数据
  • RabbitMQ节点:维护队列和交换机的持久状态

分布式存储系统

  • Elasticsearch数据节点:保障分片数据的持久性和可发现性
  • Cassandra环:节点需要稳定的网络标识加入集群

有状态中间件

  • ZooKeeper集群:维护选举和配置信息
  • Redis哨兵/集群:需要持久化存储和固定标识

StatefulSet 详细工作机制

Pod标识与DNS解析

StatefulSet控制器为每个Pod分配唯一标识:

  • 命名规则<statefulset-name>-<ordinal-index>(如redis-0, redis-1)
  • DNS记录:通过Headless Service暴露的格式为<pod-name>.<svc-name>.<namespace>.svc.cluster.local

示例访问方式:

bash
# DNS访问模板
<pod-name>.<svc-name>.<namespace>.svc.cluster.local

# 访问示例
redis-0.redis.default.svc.cluster.local  # 访问第一个Pod
redis-1.redis.default.svc.cluster.local  # 访问第二个Pod

持久化存储实现

通过volumeClaimTemplates为每个Pod自动创建PVC:

yaml
volumeClaimTemplates:
- metadata:
    name: data  # 卷名称
  spec:
    accessModes: [ "ReadWriteOnce" ]
    resources:
      requests:
        storage: 10Gi

PVC命名规则为<volumeClaimTemplate名称>-<pod名称>(如data-web-0)

有序调度流程

  1. 扩容时:严格按0→1→2顺序创建,等待前序Pod Ready后才继续
  2. 缩容时:按2→1→0逆序终止,确保关键节点(如主节点)最后删除
  3. 更新时:默认按N-1→0顺序滚动更新(RollingUpdate策略)

与Headless Service协作

Headless Service YAML示例:

yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None  # 定义为Headless Service
  selector:
    app: nginx

StatefulSet 配置详解

完整YAML示例

yaml
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  clusterIP: None  # Headless Service
  selector:
    app: redis
  ports:
  - port: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: "redis"  # 必须关联Headless Service
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:alpine
        ports:
        - containerPort: 6379
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:  # 存储模板
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

关键配置字段

  1. serviceName:必须指定关联的Headless Service名称
  2. volumeClaimTemplates:定义持久卷声明模板,每个Pod自动创建PVC
  3. updateStrategy:更新策略配置
    • RollingUpdate:有序滚动更新(默认)
    • OnDelete:手动删除Pod触发更新
  4. podManagementPolicy:Pod管理策略
    • OrderedReady:默认顺序创建
    • Parallel:并行创建(牺牲有序性)

更新策略详解

RollingUpdate策略

yaml
updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    partition: 2  # 只有序号≥2的Pod会被更新
    maxUnavailable: 0  # 不可用Pod最大数量
  • 按N-1→0顺序更新
  • 支持分区更新(partition),实现金丝雀发布

OnDelete策略

yaml
updateStrategy:
  type: OnDelete  # 需手动删除Pod触发更新
  • 适用于需要特殊维护过程的应用
  • 更新时需要手动介入,适合关键数据服务

StatefulSet 操作指南

创建 StatefulSet

bash
# 从 YAML 文件创建
kubectl apply -f mysql-statefulset.yaml

# 生成模板(带持久化卷声明)
kubectl create sts redis --image=redis:7 --replicas=3 --port=6379 --dry-run=client -o yaml > redis-sts.yaml

查看状态

bash
# 基础查看(显示 READY/AGE 等关键字段)
kubectl get sts [-n <namespace>]

# 扩展信息(显示选择器和服务名称)
kubectl get sts -o wide

# 按副本数排序
kubectl get sts --sort-by=.spec.replicas

# 筛选未就绪的 StatefulSet
kubectl get sts -o jsonpath='{.items[?(@.status.readyReplicas != @.spec.replicas)].metadata.name}'

输出字段解析

字段名称含义解析
NAMEStatefulSet 名称(遵循 <statefulset-name>-<ordinal>命名规则)
READY当前就绪 Pod 数/期望副本数(格式 current/desired
AGE创建时长
CONTAINERS主容器名称
IMAGES当前使用的镜像版本
SELECTORPod 选择器标签

自定义输出

bash
# 全量信息视图
kubectl get sts -A -o custom-columns=\
"NAME:.metadata.name,\
NAMESPACE:.metadata.namespace,\
READY:.status.readyReplicas+'/'+.spec.replicas,\
REPLICAS:.spec.replicas,\
SERVICE:.spec.serviceName,\
UPDATE_STRATEGY:.spec.updateStrategy.type,\
AGE:.metadata.creationTimestamp"

# 更新状态监控
kubectl get sts -o=custom-columns=\
"NAME:.metadata.name,\
DESIRED:.spec.replicas,\
READY:.status.readyReplicas,\
UPDATED:.status.updatedReplicas,\
REVISION:.status.updateRevision,\
PARTITION:.spec.updateStrategy.rollingUpdate.partition"

异常状态解读:

  • UPDATED < DESIRED:滚动更新进行中
  • PARTITION > 0:分阶段更新卡住

JSONPath字段提取

bash
kubectl get sts -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.status.readyReplicas}{"/"}{.spec.replicas}{"\t"}{.spec.serviceName}{"\t"}{.spec.updateStrategy.type}{"\t"}{.metadata.creationTimestamp}{"\n"}{end}' | column -t -N "NAMESPACE,NAME,READY,SERVICE,STRATEGY,AGE"

字段说明:

  • status.currentRevision:当前控制器版本(用于回滚定位)
  • spec.template.spec.containers[0].image:主容器镜像版本

滚动更新

bash
# 触发滚动更新(修改镜像版本)
kubectl patch sts web -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.25"}]}}}}'

# 查看更新进度
kubectl rollout status sts/web

# 暂停/恢复更新(金丝雀发布场景)
kubectl rollout pause sts/web
kubectl rollout resume sts/web

版本回滚

bash
# 查看历史版本
kubectl rollout history sts/web

# 回滚到指定版本
kubectl rollout undo sts/web --to-revision=2

副本数调整

bash
# 水平扩展(注意 PVC 自动按副本数创建)
kubectl scale sts/redis --replicas=5

# 优雅缩容(需确保有状态应用支持)
kubectl scale sts/redis --replicas=2

生产注意:缩容时会从最高序号的 Pod 开始删除,确保数据已同步。

持久化卷操作

bash
# 查看关联的 PVC(命名规则:<volumeClaimTemplateName>-<stsName>-<ordinal>)
kubectl get pvc -l app=redis

# 强制删除卡住的 PVC(数据会丢失!)
kubectl delete pvc redis-data-redis-2 --force --grace-period=0

存储扩容(需 StorageClass 支持)

bash
# 先修改 PVC 容量
kubectl edit pvc redis-data-redis-0  # 修改 spec.resources.requests.storage

# 再删除 Pod 触发重建(StatefulSet 控制器会自动处理)
kubectl delete pod redis-0

事件和日志分析

bash
# 查看 StatefulSet 事件
kubectl describe sts/redis | grep -A 10 Events

# 检查特定 Pod 日志(例如序号为 0 的 Pod)
kubectl logs redis-0 [-c containerName]

强制重建 Pod(慎用)

bash
# 删除 Pod 但保留 PVC(StatefulSet 会自动重建)
kubectl delete pod redis-0 --grace-period=0 --force

临时进入 Pod 诊断

bash
# 进入指定序号的 Pod(例如调试主节点)
kubectl exec -it redis-0 -- bash

# 检查数据一致性(分布式数据库场景)
kubectl exec redis-0 -- redis-cli INFO replication

资源监控

bash
# 查看资源使用情况(需 metrics-server)
kubectl top pod -l app=redis

故障排查

  1. Pod卡在Pending
    • 检查资源配额和节点资源是否充足
    • 验证StorageClass是否可用,PVC是否绑定成功
  2. Pod无法Ready
    • 检查容器日志:kubectl logs <pod-name>
    • 验证Readiness Probe配置
  3. DNS解析失败
    • 验证Headless Service是否存在且selector匹配
    • 检查CoreDNS是否正常运行
  4. 存储访问问题
    • 确认PVC/PV状态:kubectl get pvc,pv
    • 检查volumeMounts路径是否正确