在 Kubernetes 中,StatefulSet 是专门为 "有状态应用" 设计的工作负载控制器。与管理无状态应用的 Deployment 不同,它能为 Pod 提供 稳定的身份标识 、专属的持久化存储 和 有序的操作流程,完美适配数据库集群(如 MySQL、MongoDB)、分布式系统(如 Kafka、ZooKeeper)等需要固定身份和独立数据的场景。
1 什么是 "有状态应用"?为什么需要 StatefulSet?
1.1 有状态应用的 3 个核心需求
无状态应用(如 Nginx、API 服务)的 Pod 可以随意替换:名称随机(如nginx-7f9b8f4d9-2xq3p
)、IP 动态变化、数据无需持久化或可共享,用 Deployment 管理即可。
但有状态应用(如 MySQL 主从、ZooKeeper 集群)有 3 个特殊需求,Deployment 无法满足:
- 稳定的身份 :每个实例需要固定的名称(如
zk-0
、zk-1
)和网络标识,其他服务需通过固定标识访问(例如 "主库必须是mysql-0
")。 - 专属的存储 :每个实例的数据必须独立(如
mysql-0
的数据不能被mysql-1
覆盖),且 Pod 重建后数据必须保留。 - 有序的操作 :部署时需先启动
zk-0
,再启动zk-1
;扩容时需按顺序新增;更新时需逐个替换,避免集群故障。
1.2 StatefulSet 的核心价值
StatefulSet 正是为解决这些痛点而生,它为每个 Pod 提供:
- 固定不变的身份(名称、DNS 域名);
- 专属的持久化存储(每个 Pod 绑定独立的 PVC);
- 严格的操作顺序(部署 / 扩容 / 更新 / 删除都按序号执行)。
2 StatefulSet 的核心特性(必懂)
2.1 稳定的网络标识(固定名称 + DNS)
-
固定的 Pod 名称 :StatefulSet 的 Pod 名称格式为
[StatefulSet名称]-[序号]
(如web-0
、web-1
、web-2
),序号从 0 开始,唯一且固定(即使 Pod 重建,名称和序号也不变)。 -
稳定的 DNS 记录:需配合Headless Service(无头服务)使用。每个 Pod 会被分配一个固定的 DNS 域名:
plaintext<pod名称>.<无头服务名称>.<命名空间>.svc.cluster.local
例如:
web-0.nginx-service.default.svc.cluster.local
。其他 Pod 可通过该域名稳定访问特定实例(无需关心 IP 变化)。
2.2 稳定的持久化存储(专属 PVC)
通过 volumeClaimTemplates
(PVC 模板)为每个 Pod 自动创建专属的 PVC ,格式为 [模板名称]-[Pod名称]
(如data-web-0
、data-web-1
)。
- 每个 PVC 绑定独立的 PV,确保数据隔离(
web-0
的数据不会被web-1
访问)。 - 即使 Pod 被删除重建,新 Pod 仍会挂载原 PVC,数据不丢失。
2.3 有序的操作流程(按序号执行)
StatefulSet 的所有操作(部署、扩容、更新、删除)都按 "序号升序 / 降序" 严格执行,确保集群稳定性:
- 部署 :先创建
web-0
,成功后再创建web-1
,以此类推。 - 扩容 :新增实例时,按序号递增(如当前有
web-0
、web-1
,新增web-2
)。 - 更新 :默认按序号降序逐个更新(先更新
web-2
,成功后再更新web-1
,最后web-0
),避免集群中断。 - 删除 :按序号降序逐个删除(先删
web-2
,再删web-1
,最后web-0
)。
3 StatefulSet 的关键组件(依赖项)
StatefulSet 不能独立工作,必须配合两个核心组件:
3.1 Headless Service(无头服务)
作用:为 StatefulSet 的 Pod 提供稳定的 DNS 解析(核心!)。
与普通 Service 的区别:Headless Service 没有 ClusterIP,它会为每个关联的 Pod 创建 DNS A 记录(pod名称.服务名称
),让其他 Pod 能通过域名访问特定实例。
示例(创建 Headless Service):
yaml
# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service # 服务名称,用于DNS解析
spec:
selector:
app: nginx # 匹配StatefulSet的Pod标签
ports:
- port: 80
targetPort: 80
clusterIP: None # 关键:设为None,即无头服务
3.2 VolumeClaimTemplate(PVC 模板)
作用:为每个 Pod 自动创建专属的 PVC(无需手动创建),确保数据独立存储。
模板中定义 PVC 的需求(容量、访问模式、存储类),StatefulSet 会为每个 Pod 生成对应的 PVC。
示例(PVC 模板片段):
yaml
volumeClaimTemplates:
- metadata:
name: data # 模板名称,生成的PVC格式为data-<pod名称>
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi # 每个Pod分配1Gi存储
storageClassName: "standard" # 关联的存储类(需提前创建)
4 实操案例:部署一个 StatefulSet(Nginx 示例)
以 Nginx 为例(虽然 Nginx 是无状态的,但可用于演示 StatefulSet 的特性),完整步骤如下:
步骤 1:创建 Headless Service
yaml
# 保存为headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
clusterIP: None # 无头服务
bash
kubectl apply -f headless-service.yaml
步骤 2:创建 StatefulSet
yaml
# 保存为statefulset-nginx.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web # StatefulSet名称,用于生成Pod名称
spec:
serviceName: "nginx-service" # 关联的Headless Service名称(必须正确)
replicas: 3 # 3个实例
selector:
matchLabels:
app: nginx # 匹配Pod的标签
template:
metadata:
labels:
app: nginx # Pod标签,需与Service的selector一致
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
name: web
volumeMounts:
- name: data # 挂载名为data的卷(与PVC模板名称一致)
mountPath: /usr/share/nginx/html # 挂载到容器内的目录
# PVC模板:为每个Pod自动创建PVC
volumeClaimTemplates:
- metadata:
name: data # 卷名称,与volumeMounts.name一致
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi # 每个Pod请求1Gi存储
storageClassName: "standard" # 需提前创建名为standard的StorageClass(或用现有)
bash
kubectl apply -f statefulset-nginx.yaml
步骤 3:验证 StatefulSet 的特性
1. 查看 Pod(固定名称 + 有序部署)
bash
kubectl get pods -l app=nginx
输出(名称为web-0
、web-1
、web-2
,按序号创建):
plaintext
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2m
web-1 1/1 Running 0 1m50s # web-0成功后才创建
web-2 1/1 Running 0 1m40s # web-1成功后才创建
2. 查看自动创建的 PVC(专属存储)
bash
kubectl get pvc
输出(每个 Pod 对应一个 PVC,名称为data-web-0
、data-web-1
、data-web-2
):
plaintext
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-web-0 Bound pvc-xxx-xxx 1Gi RWO standard 2m
data-web-1 Bound pvc-yyy-yyy 1Gi RWO standard 1m50s
data-web-2 Bound pvc-zzz-zzz 1Gi RWO standard 1m40s
3. 验证稳定的 DNS 解析(通过 Headless Service)
在集群内任意 Pod 中,通过 DNS 域名访问web-0
:
bash
# 启动一个测试Pod
kubectl run -it --rm test --image=busybox:1.35 -- sh
# 在测试Pod中执行(解析web-0的IP)
nslookup web-0.nginx-service
输出(能稳定解析到web-0
的 IP,即使web-0
重建,域名也不变):
plaintext
Name: web-0.nginx-service
Address 1: 10.244.1.5 web-0.nginx-service.default.svc.cluster.local
4. 验证 Pod 重建后身份和存储不变
删除web-0
,观察重建后的 Pod:
bash
# 删除web-0
kubectl delete pod web-0
# 查看重建的Pod
kubectl get pods -l app=nginx
输出(新 Pod 仍叫web-0
,且挂载原 PVCdata-web-0
,数据不丢失):
plaintext
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 30s # 名称不变
web-1 1/1 Running 0 3m
web-2 1/1 Running 0 2m50s
5 StatefulSet vs Deployment(核心区别)
特性 | StatefulSet | Deployment |
---|---|---|
应用类型 | 有状态应用(数据库、分布式系统) | 无状态应用(Web 服务、API) |
Pod 名称 | 固定格式(名称-序号 ,如web-0 ) |
随机名称(如nginx-7f9b8f4d9-2xq3p ) |
网络标识 | 稳定 DNS 域名(依赖 Headless Service) | 依赖普通 Service(通过 ClusterIP 访问) |
存储 | 专属 PVC(通过 volumeClaimTemplates) | 共享 Volume 或无持久化 |
操作顺序 | 按序号有序部署 / 扩容 / 更新 / 删除 | 并行操作(无顺序) |
身份稳定性 | 重建后名称、存储、网络标识不变 | 重建后名称、IP 全变 |
6 常见问题与注意事项
- StatefulSet 的 Pod 一直 Pending :
- 原因:
volumeClaimTemplates
中请求的 PVC 无法绑定 PV(如 StorageClass 不存在、PV 容量不足)。 - 排查:
kubectl describe pvc data-web-0
(查看具体 PVC 的绑定失败原因)。
- 原因:
- DNS 解析失败 :
- 原因:Headless Service 的
selector
与 StatefulSet 的 Pod 标签不匹配,或 CoreDNS 未正常运行。 - 排查:
kubectl describe service nginx-service
(确认 selector 正确);检查 CoreDNS 状态(kubectl get pods -n kube-system | grep coredns
)。
- 原因:Headless Service 的
- 更新 StatefulSet 时集群不可用 :
- 解决:调整更新策略(
spec.updateStrategy.rollingUpdate.partition
),控制更新范围;或使用OnDelete
策略(手动删除旧 Pod 才更新)。
- 解决:调整更新策略(
7 总结
StatefulSet 是管理有状态应用的 "利器",核心记住 3 点:
- 提供固定身份(名称、DNS),适合需要标识的集群应用;
- 提供专属存储(自动创建 PVC),适合数据独立的实例;
- 提供有序操作,适合对启动 / 更新顺序敏感的集群。
典型适用场景:
- 数据库集群(MySQL 主从、PostgreSQL);
- 分布式协调服务(ZooKeeper、etcd);
- 消息队列(Kafka、RabbitMQ 集群)。