K8s StatefulSet 详解:有状态应用的"身份证"与"固定住址"
前面我们聊了 Nacos 集群为什么用 StatefulSet,以及 Service 编排如何配合。但 StatefulSet 本身到底是什么?它和 Deployment 有什么本质区别?这篇文章把它讲清楚。
1. 一句话定义
StatefulSet 是 Kubernetes 中用于管理有状态应用的控制器。它保证一组 Pod 拥有稳定的身份、稳定的网络标识和稳定的存储。
如果用一个比喻:
- Deployment 管的是一群"临时工":名字随机、工位不固定、走了就换人,适合无状态服务。
- StatefulSet 管的是一组"正式员工":每个人都有工号、固定工位、专属储物柜,走了再回来还是同一个人,适合数据库、消息队列、Nacos 这类有状态集群。
2. 从 Deployment 的不足说起
在 K8s 里,Deployment 是最常用的工作负载控制器。它适合无状态服务:
- Pod 名随机,如
my-app-7d9f8-abcde; - Pod IP 不固定,重建就变;
- 实例之间不需要互相认识;
- Service 自动负载均衡;
- 扩缩容只改
replicas就够了。
但 Nacos 集群不行。Nacos 节点需要:
- 知道彼此是谁;
- 有固定地址写进
nacos.member.list; - 能选主、同步数据、维持一致性;
- 重建后还能以原身份加入集群。
这些需求,Deployment 满足不了。于是就需要 StatefulSet。
3. StatefulSet 的四大"稳定"
StatefulSet 的核心价值,可以概括为四个稳定。
3.1 稳定的 Pod 名
StatefulSet 创建的 Pod 名字不是随机的,而是:
<statefulset-name>-<ordinal>
例如:
nacos-cluster-0
nacos-cluster-1
nacos-cluster-2
即使 nacos-cluster-0 被删除重建,新 Pod 的名字仍然是 nacos-cluster-0。这就是"稳定身份"。
3.2 稳定的网络标识
StatefulSet 必须配合 Headless Service 使用。Pod 会获得稳定的 DNS 记录:
<pod-name>.<serviceName>.<namespace>.svc.cluster.local
例如:
nacos-cluster-0.nacos-cluster-hs.gateway-default.svc.cluster.local
nacos-cluster-1.nacos-cluster-hs.gateway-default.svc.cluster.local
nacos-cluster-2.nacos-cluster-hs.gateway-default.svc.cluster.local
注意:Pod 的 IP 可能变,但 DNS 名不变。所以 Nacos 的 member.list 可以写死这些 FQDN,节点启动时就能互相发现。
3.3 稳定的存储
StatefulSet 可以通过 volumeClaimTemplates 为每个 Pod 创建独立的 PVC:
yaml
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
每个 Pod 会得到自己的 PVC,命名规则是:
<volumeClaimTemplateName>-<statefulsetName>-<ordinal>
例如:
data-nacos-cluster-0
data-nacos-cluster-1
data-nacos-cluster-2
这样,nacos-cluster-0 重建后,仍然会挂载 data-nacos-cluster-0,数据不会丢。
默认情况下,删除 StatefulSet 不会删除 PVC,缩容也不会删除 PVC,需要手动清理。
3.4 稳定的顺序
StatefulSet 默认使用 OrderedReady 策略:
- 创建时:按
0 → 1 → 2顺序,前一个 Ready 后才创建下一个; - 删除时:按
2 → 1 → 0顺序; - 滚动更新时:默认从最大序号到最小序号。
也可以设置为 Parallel,让所有 Pod 并行创建或删除。Nacos 配置里就用了:
yaml
podManagementPolicy: Parallel
因为 Nacos 集群节点需要尽量一起启动,否则先启动的节点会一直等其他节点,拖慢集群组建。
4. StatefulSet 的核心字段
一个简化的 StatefulSet 示例:
yaml
apiVersion: v1
kind: Service
metadata:
name: web-hs
spec:
clusterIP: None
selector:
app: web
ports:
- port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web-hs
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
关键字段:
| 字段 | 作用 |
|---|---|
serviceName |
必须指向一个 Headless Service,用于提供稳定 DNS |
replicas |
副本数 |
selector |
选择要管理的 Pod |
template |
Pod 模板 |
volumeClaimTemplates |
为每个 Pod 创建独立 PVC |
podManagementPolicy |
OrderedReady(默认)或 Parallel |
updateStrategy |
RollingUpdate(默认)或 OnDelete,支持 partition 金丝雀 |
这个例子会创建:
- Pod:
web-0、web-1、web-2 - DNS:
web-0.web-hs.default.svc.cluster.local等 - PVC:
data-web-0、data-web-1、data-web-2
5. StatefulSet 与 Deployment 对比
| 维度 | Deployment | StatefulSet |
|---|---|---|
| 适用场景 | 无状态服务 | 有状态应用 |
| Pod 名 | 随机后缀 | 固定序号,如 web-0 |
| 网络标识 | 无稳定 DNS | 稳定 FQDN |
| 存储 | 共享或临时 | 每 Pod 独立 PVC |
| 启动顺序 | 并行 | 默认有序,可并行 |
| 更新顺序 | 任意滚动 | 默认逆序滚动 |
| 扩缩容 | 只改 replicas | 需考虑集群成员、存储、选主 |
| 典型应用 | Web、API | MySQL、Kafka、ZooKeeper、etcd、Nacos |
6. StatefulSet 不做什么?
这一点非常重要:StatefulSet 只解决 K8s 层的有状态编排,不解决应用层的数据一致性。
它不负责:
- 数据复制;
- 选主;
- 数据备份;
- 高可用;
- 网络直连;
- 保证 Pod 永不重建;
- 保证应用一定能组成集群。
这些仍然由应用自己实现。比如 Nacos 自己负责 Raft 选主、Distro 数据同步、服务注册表复制。StatefulSet 只是给 Nacos 节点提供了稳定身份、稳定 DNS 和稳定存储。
7. 回到 Nacos 集群
在你们的配置中:
yaml
kind: StatefulSet
metadata:
name: nacos-cluster
spec:
serviceName: nacos-cluster-hs
podManagementPolicy: Parallel
replicas: 3
StatefulSet 扮演的角色是:
- 创建
nacos-cluster-0/1/2三个固定身份的 Pod; - 通过
nacos-cluster-hsHeadless Service 提供稳定 DNS; - 让
nacos.member.list可以写死三条 FQDN; - 用
Parallel加快三节点启动; - 配合 PDB
minAvailable: 2保护多数派。
而 Nacos 自己负责:
- 集群成员发现;
- Raft 选主;
- 配置和服务数据同步;
- 客户端注册与健康检查。
所以,StatefulSet 是 Nacos 集群在 K8s 上的"身份与网络底座",不是 Nacos 集群本身。
8. 什么时候该用 StatefulSet?
适合:
- 数据库:MySQL、PostgreSQL、MongoDB;
- 消息队列:Kafka、RabbitMQ;
- 分布式协调:ZooKeeper、etcd;
- 注册配置中心:Nacos、Consul;
- 搜索与存储:Elasticsearch、ClickHouse。
不适合:
- 无状态 Web 服务;
- API 网关;
- 前端静态资源;
- 任务队列 Worker。
这些用 Deployment 更简单、更灵活。
9. 总结
一句话:
StatefulSet 是给 Pod 发"身份证 + 固定住址 + 专属储物柜"的控制器。
它保证:
- Pod 名字稳定;
- 网络标识稳定;
- 存储稳定;
- 启动、扩缩、更新顺序可控。
但它不保证应用层的数据一致性、选主和高可用。真正的分布式逻辑,仍然由 Nacos、MySQL、Kafka 这些应用自己实现。
所以,Nacos 集群需要 StatefulSet,是因为它需要固定身份和固定地址 来组成集群;而普通无状态服务只需要 Deployment,因为它们的副本之间互不相识,改 replicas 就够了。