StatefulSet 是 Kubernetes 中专门用于管理有状态应用的工作负载控制器。与 Deployment 不同,它为每个 Pod 提供稳定的网络标识、持久的存储以及有序的部署和扩缩容能力,非常适合部署 MySQL、Redis Cluster、ZooKeeper、Kafka 这类对身份和数据一致性敏感的服务。本文将从核心概念、YAML 编写、部署验证到更新策略,完整讲解 StatefulSet 的使用方法。

一、StatefulSet 与 Deployment 的核心区别
要理解 StatefulSet,先要明白它和 Deployment 的差异。Deployment 管理的 Pod 是完全等价的:名字随机生成、IP 地址随时可能变化、Pod 可以被任意一个新副本替换,这对无状态的 Web 服务毫无影响。但有状态应用就不一样了,比如 MySQL 主从集群中,主节点和从节点承担的角色不同,如果从节点 Pod 被替换后丢失了自己的身份,整个集群的复制关系就会混乱。
StatefulSet 针对这些问题提供了三个核心保证。第一是稳定的网络标识:每个 Pod 拥有一个基于序号的稳定主机名,格式为 pod名称-序号,例如 mysql-0、mysql-1,无论 Pod 被调度到哪个节点、重建多少次,这个名字都不会变。第二是稳定的存储:通过 volumeClaimTemplates 为每个 Pod 绑定独立的 PersistentVolumeClaim,Pod 重建后依然挂载同一块持久卷。第三是有序性:Pod 的创建、删除、滚动更新都严格按照序号顺序执行,默认从 0 号开始逐个创建,删除时则从最大序号开始倒序执行。
另外需要注意,StatefulSet 必须配合 Headless Service 使用。Headless Service 即 clusterIP: None 的 Service,它不为集群提供负载均衡 IP,而是直接将 Service 名称解析到各个 Pod 的 DNS 记录上,这样其他服务就能通过 pod-name.service-name.namespace.svc.cluster.local 这样的域名精准访问到某个特定实例。
二、编写 StatefulSet 的完整 YAML 示例
下面以部署一个三节点的 MySQL 集群为例,展示 StatefulSet 的典型写法。完整资源包含三部分:Headless Service、ConfigMap 和 StatefulSet 本体。
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
labels:
app: mysql
spec:
clusterIP: None # Headless Service,关键配置
selector:
app: mysql
ports:
- name: mysql
port: 3306
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
log-bin=mysql-bin
server-id=1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless # 必须指定 Headless Service 名称
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "Root@123456"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
- name: config
mountPath: /etc/mysql/conf.d
volumeClaimTemplates: # 存储模板,每个 Pod 生成独立 PVC
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gi
这份配置中有几个关键点需要理解。serviceName 字段是 StatefulSet 特有的必填项,指向前面定义的 Headless Service。volumeClaimTemplates 定义了存储模板,控制器会为每个 Pod 自动创建一个独立的 PVC,命名为 data-mysql-0、data-mysql-1、data-mysql-2,这些 PVC 与 Pod 序号一一对应,Pod 重建后仍然挂载原来的卷,数据得以保留。
删除 Pod 时也要注意,StatefulSet 的 PVC 不会被自动删除。即使缩容把副本数从 3 降到 1,被删掉的 data-mysql-2 这条 PVC 依然存在,再次扩容时新 Pod 会重新绑定它并恢复原有数据。这是数据安全的保护机制,但也会带来存储浪费,长期不用时需要手动清理不再需要的 PVC。
三、部署验证与 Pod 间通信方式
执行 kubectl apply -f mysql-statefulset.yaml 后,可以用 kubectl get pods -w 观察创建过程。你会看到 Pod 是严格按顺序创建的:先启动 mysql-0,等它完全 Ready 之后才会创建 mysql-1,最后是 mysql-2。这个特性对于需要先启动主节点、再启动从节点的集群化应用非常重要。如果某个 Pod 一直无法 Ready,后面的 Pod 都不会被创建,排查时要重点检查卡住的序号最小的那个 Pod。
验证网络标识也很简单。进入任意一个测试 Pod,执行 DNS 查询即可确认:
# 查看某个具体 Pod 的 DNS 解析 nslookup mysql-0.mysql-headless.default.svc.cluster.local # 查看整个服务的所有 Pod 地址 nslookup mysql-headless.default.svc.cluster.local # 通过稳定域名直接连接指定实例 mysql -h mysql-0.mysql-headless.default.svc.cluster.local -uroot -p
第一条命令返回单个 Pod 的 IP,第二条返回所有 Pod 的 IP 列表。这种 DNS 机制让应用层可以自己实现服务发现逻辑,例如 MySQL 从节点在配置复制时可以固定指向 mysql-0 作为主库地址,而不依赖任何写死的 IP。此外,StatefulSet 的 Pod 还支持一条 DNS 记录格式简化写法:pod-name.service-name,在同一命名空间内可以直接用 mysql-0.mysql-headless 访问,非常方便。
四、更新策略与扩缩容实战技巧
StatefulSet 默认的更新策略是 RollingUpdate,滚动更新时从最大序号的 Pod 开始倒序逐个更新,并且会保证前一个 Pod 已经 Ready 才继续下一个。这种顺序保证了数据库类应用的更新安全。你还可以设置 partition 参数进行灰度发布:
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只有序号大于等于 2 的 Pod 会更新
上面的配置表示只有 mysql-2 会使用新镜像,序号 0 和 1 的 Pod 保持不变。验证新版本稳定后,逐步降低 partition 值即可完成全量升级。如果不想自动更新,可以把 type 改为 OnDelete,只有手动删除 Pod 时才会触发重建并使用新配置,适合需要精确控制更新时机的场景。
扩缩容方面,执行 kubectl scale statefulset mysql --replicas=5 扩容时,新 Pod 按序号递增顺序创建,并自动生成对应的新 PVC。缩容则是倒序删除,从最大序号开始。这里有一个常见的坑:如果集群中有节点 NotReady,StatefulSet 可能出现 Pod 卡在 Terminating 状态无法正常删除,此时可以用强制删除命令 kubectl delete pod mysql-2 --force --grace-period=0 处理,但要谨慎使用,强制删除前应确认该节点上的数据已经不再需要写入,否则可能造成数据分裂。
总结一下,使用 StatefulSet 的正确姿势是:始终搭配 Headless Service 提供稳定网络标识,用 volumeClaimTemplates 保证存储持久化,利用有序性保证集群类应用的启动顺序,并通过 partition 策略降低发布风险。掌握这些要点后,无论是部署 MySQL、Redis 还是 Kafka,都能在 Kubernetes 上稳定运行有状态服务。
KubernetesStatefulSet有状态服务修改时间:2026-09-05 17:21:00