StatefulSet 是 Kubernetes 中用于管理有状态应用的资源。它为每个 Pod 提供唯一的、稳定的标识符和专用的持久化存储,从而保证了 Pod 的部署、扩缩容和删除都是有序且可预测的

为什么需要 StatefulSet?

要理解 StatefulSet,我们先看它的对立面:Deployment。

  • Deployment 适用于无状态应用:
  • Pod 是完全相同、可随意替换的。
  • 杀掉一个 Pod,Deployment 会创建一个新的、名称和 IP 都随机的 Pod 来替代它。
  • 存储是临时的,Pod 重启后数据就丢失了。
  • 例子:Web 服务器、无状态的 API 服务。

但有些应用是“有状态”的,它们需要:

  • 稳定的、唯一的网络标识符:即使 Pod 重启或重新调度,它的主机名和地址也不能变。这对于像数据库、ZooKeeper、Etcd 这样的需要形成集群的应用至关重要。
  • 稳定的、持久的存储:每个 Pod 都需要挂载自己专属的存储卷,即使 Pod 被调度到另一个节点,它也必须能挂载回同一个数据卷,保证数据不丢失。
  • 有序的部署和扩展:创建 Pod 时,必须等前一个 Pod 完全运行并准备就绪后,才能创建下一个。这通常是为了主从选举或数据同步。
  • 有序的收缩和删除:删除 Pod 时,顺序与创建时相反

StatefulSet 就是为了满足这些需求而设计的

StatefulSet 的核心特性

稳定的网络标识符

StatefulSet 管理的 Pod 名称不是随机的,而是遵循一个固定的规则:<StatefulSet 名称>-<序号>
例如,一个名为 mysql 的 StatefulSet 会按顺序创建出三个 Pod:

  • mysql-0
  • mysql-1
  • mysql-2

这些 Pod 的主机名就是其名字(如 mysql-0)。此外,还会配套一个 Headless Service,用于为这些 Pod 提供固定的 DNS 记录。

  • Pod 的 DNS 格式为:...svc.cluster.local
  • 对于 mysql-0,它的 DNS 就是 mysql-0.mysql.default.svc.cluster.local。
  • 这样,集群内的其他应用就可以通过这个稳定的域名来精确地访问到特定的 Pod。

稳定的持久化存储

这是实现状态不丢失的最核心的机制。

  • StatefulSet 使用 PersistentVolumeClaim (PVC) 模板 来为每个 Pod 申请存储。
  • 当 StatefulSet 创建 mysql-0 时,它会自动地根据模板创建一个同名的 PVC(如 data-mysql-0)。这个 PVC 会动态或静态地绑定到一个 PersistentVolume (PV)。

关键点在于:

  • 当 Pod mysql-0 被删除并重新创建时,新的 mysql-0 Pod 会自动重新挂载之前名为 data-mysql-0 的 PVC 所对应的 PV。这就保证了数据不会因为 Pod 的重启或迁移而丢失。
  • 存储的生命周期与 Pod 的生命周期是解耦的。删除 Pod 或 StatefulSet 并不会自动删除其关联的 PVC/PV,这意味着数据是安全的。

有序的生命周期管理

有序部署与扩展:

  • 当创建或扩展(增加副本数)时,Pod 会按序号 从 0 到 N 依次创建。Kubernetes 会等待 Pod (N-1) 进入 Running 和 Ready 状态后,才会创建 Pod N。

有序收缩与删除:

  • 当收缩(减少副本数)或删除 StatefulSet 时,Pod 会按序号 从 N 到 0 逆序终止。

有序滚动更新:StatefulSet 支持两种更新策略:

  • RollingUpdate(默认):逆序地、逐个 Pod 进行优雅的更新。
  • OnDelete:只有在用户手动删除旧 Pod 后,才会创建新的 Pod。这提供了更手动的控制

使用StatefulSet的场景

需要稳定的、唯一的网络标识符
场景:应用集群中的每个实例都需要一个永久不变的身份标识,以便其他实例能够可靠地发现和连接它。

  • 分布式数据库集群: (MySQL Group Replication, MongoDB Replica Set, PostgreSQL HA)
  • 从库(Secondary)需要知道主库(Primary)的固定地址来建立复制关系。如果主库地址变了,复制会中断。
  • StatefulSet 解决方案:主库可以是 mysql-0.mysql-svc,从库可以通过这个固定域名永远找到它。
  • 服务发现与协调系统 (Zookeeper, Etcd, Consul)
  • 这些系统的集群成员列表是预先定义好的,每个节点都有固定的 ID 和地址。配置文件中会写死其他成员的地址。
  • StatefulSet 解决方案:zk-0.zk-svc, zk-1.zk-svc, zk-2.zk-svc 这些域名是稳定的,可以直接写在配置中。
  • 消息队列集群 (Kafka, RabbitMQ)
  • Broker 需要有固定的广告地址(Advertised Listeners),生产者和消费者需要知道如何连接到固定的 Broker。

需要稳定的、专用的持久化存储
场景:每个应用实例都有自己的数据,并且这些数据不能丢失,也不能与其他实例共享。

  • 每个实例拥有独立数据库 (Sharding/分片)
  • 例如,user-pod-0 负责存储用户 ID 1-1000 的数据,user-pod-1 负责存储 1001-2000 的数据。
  • StatefulSet 解决方案:user-pod-0 永远挂载 data-user-pod-0 卷,保证其专属数据不丢失、不混淆。
  • 需要持久化会话的应用
  • 例如,一个游戏服务器实例管理着特定的游戏房间,所有房间状态数据都保存在本地。必须保证玩家下次连接能回到同一个服务器实例和房间。
  • StatefulSet 解决方案:通过 Service 将用户请求路由到固定的 Pod,该 Pod 挂载着包含房间数据的专属存储。

需要有序的部署和扩缩容
场景:应用的启动和停止必须遵循严格的顺序。

  • 主从架构应用的初始化
  • 必须先启动并初始化主节点(通常为 -0),等待其完全就绪后,才能启动从节点。从节点需要向已经就绪的主节点注册或同步数据, 例子:MySQL 主从、Redis 主从。
  • 有依赖关系的集群
  • 在某些集群中,节点需要等待序号在前的节点启动并完成某些操作(如形成法定人数)后,自己才能启动。

需要有序的终止和滚动更新
场景:在关闭或更新应用时,需要以安全的顺序进行。

  • 保证主节点最后下线
  • 在缩容或更新时,应先从序号最大的 Pod(通常是从节点)开始终止,最后再处理序号最小的 Pod(通常是主节点),以避免脑裂或服务中断。
  • StatefulSet 解决方案:StatefulSet 默认的逆序终止(先终止 -2,再终止 -1,最后是 -0)保证了这一点。
  • 金丝雀发布或谨慎的滚动更新
  • 使用 OnDelete 策略,可以手动控制更新的节奏。先删除一个 Pod,等待新的版本验证无误后,再删除下一个

在这里插入图片描述

Example:

# 1. 定义一个 Headless Service,用于网络标识, 如果不需要固定的网络标识可以略过
apiVersion: v1
kind: Service
metadata:
  name: mysql-svc  # 这个名称用于组建DNS
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None  # 这就是 Headless Service 的定义
  selector:
    app: mysql     # 选择 StatefulSet 的 Pod
---



# 2. 定义 StatefulSet 本身
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: "mysql-svc" # 必须指向关联的 Headless Service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql # 必须匹配 spec.selector.matchLabels
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates: # PVC 模板,为每个 Pod 创建一个 PVC
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd" # 指定存储类,用于动态创建PV
      resources:
        requests:
          storage: 100Gi

更多推荐