StatefulSet 详解
StatefulSet 是 Kubernetes 中用于管理有状态应用的工作负载控制器。与 Deployment 不同,StatefulSet 为每个 Pod 提供持久的标识符和稳定的网络身份,非常适合运行需要持久存储或稳定网络标识的应用。
为什么需要 StatefulSet
Deployment 适用于无状态应用,但对于有状态应用存在以下问题:
- Pod 标识不固定:Pod 重建后名称和 IP 会变化
- 存储不持久:Pod 删除后数据丢失
- 启动顺序不可控:无法保证 Pod 的启动和停止顺序
- 网络标识不稳定:无法通过稳定的 DNS 名称访问特定 Pod
StatefulSet 解决了这些问题,适用于以下场景:
- 数据库集群:MySQL、PostgreSQL、MongoDB
- 消息队列:Kafka、RabbitMQ
- 分布式存储:Cassandra、Ceph、Elasticsearch
- 缓存集群:Redis Cluster
StatefulSet 与 Deployment 的区别
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机后缀 | 固定序号(statefulset-0, statefulset-1) |
| 网络标识 | 随机 IP | 稳定的 DNS 名称 |
| 存储 | 共享或无 | 每个 Pod 独立的持久存储 |
| 启动顺序 | 并行 | 按序号顺序启动 |
| 扩缩容 | 随机选择 | 按序号顺序操作 |
| 更新策略 | RollingUpdate/Recreate | RollingUpdate/OnDelete |
StatefulSet 的核心特性
1. 稳定的网络标识
每个 Pod 拥有固定的名称和网络标识:
<pod-name>-<ordinal>
web-0, web-1, web-2
通过 Headless Service,每个 Pod 都有稳定的 DNS 名称:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
web-0.nginx.default.svc.cluster.local
2. 稳定的持久存储
每个 Pod 可以绑定独立的 PersistentVolume,Pod 重建后仍然关联相同的存储:
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
3. 有序的部署和扩展
- Pod 按序号顺序创建(0, 1, 2...)
- 缩容时按逆序删除(..., 2, 1, 0)
- 更新时按逆序更新(..., 2, 1, 0)
创建 StatefulSet
基础示例:Nginx
# Headless Service - 必需组件
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # 关键:设置为 None
selector:
app: nginx
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx-headless # 关联 Headless Service
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # 为每个 Pod 创建 PVC
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 1Gi
# 创建 StatefulSet
kubectl apply -f statefulset.yaml
# 查看 StatefulSet
kubectl get statefulset
# 查看 Pod(注意有序命名)
kubectl get pods -l app=nginx
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 1m
# web-1 1/1 Running 0 50s
# web-2 1/1 Running 0 40s
# 查看 PVC(每个 Pod 一个)
kubectl get pvc
# www-web-0 Bound pvc-xxx 1Gi standard
# www-web-1 Bound pvc-xxx 1Gi standard
# www-web-2 Bound pvc-xxx 1Gi standard
Headless Service 的作用
Headless Service(clusterIP: None)是 StatefulSet 的关键组件:
- DNS 解析:为每个 Pod 创建 A 记录
- 服务发现:客户端可以直接访问特定 Pod
- 网络标识:提供稳定的网络身份
# 测试 DNS 解析
kubectl run -it --rm debug --image=busybox:1.36 -- nslookup web-0.nginx-headless.default.svc.cluster.local
# 输出示例
# Name: web-0.nginx-headless.default.svc.cluster.local
# Address 1: 10.244.1.5 web-0.nginx-headless.default.svc.cluster.local
Pod 管理策略
StatefulSet 支持两种 Pod 管理策略:
OrderedReady(默认)
严格按照序号顺序管理 Pod:
spec:
podManagementPolicy: OrderedReady # 默认值
- 扩容:必须等待前一个 Pod Ready 后才创建下一个
- 缩容:必须等待后一个 Pod 删除后才删除前一个
- 更新:必须等待后一个 Pod 更新完成才更新前一个
Parallel
并行管理 Pod,不保证顺序:
spec:
podManagementPolicy: Parallel
- 扩容:同时创建所有 Pod
- 缩容:同时删除所有 Pod
- 更新:同时更新所有 Pod
适用于不需要严格顺序的场景,如分布式缓存。
更新策略
RollingUpdate(默认)
滚动更新,按逆序逐个更新 Pod:
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0 # 分区更新
Partition 更新
只更新序号大于等于 partition 的 Pod:
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新 web-2 及之后的 Pod
用于金丝雀发布,先更新部分 Pod 观察效果。
OnDelete
手动更新,只有 Pod 被删除时才更新:
spec:
updateStrategy:
type: OnDelete
适用于需要精确控制更新时机的场景。
完整示例:MySQL 主从复制
# ConfigMap - MySQL 配置
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
master.cnf: |
[mysqld]
log-bin=mysql-bin
server-id=1
slave.cnf: |
[mysqld]
server-id=2
read-only=1
---
# Headless Service
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
clusterIP: None
selector:
app: mysql
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
initContainers:
# 初始化容器:设置 server-id
- name: init-mysql
image: mysql:8.0
command:
- bash
- "-c"
- |
set -ex
# 从 Pod 序号生成 server-id
[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# 第一个 Pod 作为 master
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
else
cp /mnt/config-map/slave.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
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
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 500m
memory: 1Gi
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gi
扩缩容操作
扩容
# 扩容到 5 个副本
kubectl scale statefulset web --replicas=5
# 观察创建顺序
kubectl get pods -l app=nginx -w
# web-3 创建 -> Ready -> web-4 创建
缩容
# 缩容到 2 个副本
kubectl scale statefulset web --replicas=2
# 观察删除顺序
kubectl get pods -l app=nginx -w
# web-4 删除 -> web-3 删除
手动删除 Pod
# StatefulSet 会自动重建 Pod,并保留存储
kubectl delete pod web-1
# 等待 Pod 重建
kubectl get pod web-1 -w
级联删除策略
删除 StatefulSet 时,可以指定级联策略:
# 同时删除 StatefulSet 和 Pod(默认)
kubectl delete statefulset web
# 仅删除 StatefulSet,保留 Pod
kubectl delete statefulset web --cascade=orphan
# 删除 StatefulSet 时同时删除 PVC
# 需要设置 persistentVolumeClaimRetentionPolicy
persistentVolumeClaimRetentionPolicy(Kubernetes 1.27+)
默认情况下,StatefulSet 的 PVC 在 Pod 缩容或 StatefulSet 删除时不会被自动删除,这可能导致存储资源持续占用。从 Kubernetes 1.27 开始,可以通过 persistentVolumeClaimRetentionPolicy 精确控制 PVC 的生命周期。
配置选项:
| 策略 | 说明 |
|---|---|
Retain(默认) | 保留 PVC,数据持久保存 |
Delete | 自动删除 PVC 和底层 PV |
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete # 删除 StatefulSet 时的行为
whenScaled: Delete # 缩容时的行为
两种场景的区别:
whenDeleted:当整个 StatefulSet 被删除时触发whenScaled:当副本数减少时触发(只影响被删除的 Pod 对应的 PVC)
实际应用示例:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
spec:
serviceName: database-headless
replicas: 3
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete # 删除 StatefulSet 时清理所有数据
whenScaled: Retain # 缩容时保留数据,便于恢复
# ... 其他配置
使用场景分析:
| 场景 | whenDeleted | whenScaled | 说明 |
|---|---|---|---|
| 开发/测试环境 | Delete | Delete | 完全清理,节省资源 |
| 生产数据库 | Retain | Retain | 保护数据,支持恢复 |
| 缓存集群 | Delete | Delete | 数据可重建,节省空间 |
| 消息队列 | Retain | Delete | 保留历史数据,缩容释放空间 |
注意事项:
- 删除顺序:PVC 删除遵循 Pod 的逆序(从高序号到低序号)
- 存储类支持:需要存储类支持动态删除(
ReclaimPolicy: Delete) - 数据备份:启用 Delete 策略前,确保有完善的备份机制
- 不可逆操作:删除 PVC 后数据无法恢复,谨慎配置
验证保留策略是否生效:
# 查看 StatefulSet 配置
kubectl get statefulset database -o jsonpath='{.spec.persistentVolumeClaimRetentionPolicy}'
# 缩容后检查 PVC
kubectl scale statefulset database --replicas=2
kubectl get pvc -l app=database
# 删除 StatefulSet 后检查 PVC
kubectl delete statefulset database
kubectl get pvc -l app=database
状态调试
查看 StatefulSet 状态
# 查看状态
kubectl get statefulset web -o yaml
# 查看事件
kubectl describe statefulset web
# 查看当前副本状态
kubectl get statefulset web -o jsonpath='{.status}'
查看特定 Pod
# 使用固定名称访问 Pod
kubectl exec web-0 -- hostname
# 通过 DNS 访问
kubectl run -it --rm debug --image=busybox:1.36 -- wget -qO- web-0.nginx-headless:80
查看持久存储
# 查看 PVC
kubectl get pvc -l app=nginx
# 查看 PV
kubectl get pv | grep www-web
# 检查存储内容
kubectl exec web-0 -- ls /usr/share/nginx/html
最佳实践
1. 使用 Headless Service
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
clusterIP: None # 必须设置
selector:
app: my-app
ports:
- port: 8080
2. 设置合理的资源限制
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
3. 配置健康检查
livenessProbe:
exec:
command: ["pg_isready"]
initialDelaySeconds: 30
readinessProbe:
exec:
command: ["pg_isready"]
initialDelaySeconds: 5
4. 使用 init 容器初始化
initContainers:
- name: init-data
image: busybox:1.36
command: ["sh", "-c", "chown -R 1000:1000 /data"]
volumeMounts:
- name: data
mountPath: /data
5. 配置 Pod 反亲和性
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: mysql
topologyKey: kubernetes.io/hostname
常见问题排查
Pod 一直处于 Pending
# 检查 PVC 是否绑定
kubectl get pvc
# 检查存储类是否存在
kubectl get storageclass
# 检查节点资源
kubectl describe node <node-name>
Pod 启动顺序问题
# 检查前一个 Pod 是否 Ready
kubectl get pods -l app=nginx
# 检查 init 容器状态
kubectl describe pod web-2
存储数据丢失
# 检查 PV 回收策略
kubectl get pv -o jsonpath='{.items[*].spec.persistentVolumeReclaimPolicy}'
# 确保使用 Retain 策略或动态存储
StatefulSet 与其他控制器的对比
┌─────────────────────────────────────────────────────────────────┐
│ 工作负载控制器选择指南 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用类型 推荐控制器 特点 │
│ ───────────────────────────────────────────────────────────── │
│ 无状态 Web 服务 Deployment 快速扩缩容、滚动更新 │
│ 有状态数据库 StatefulSet 稳定标识、持久存储 │
│ 系统守护进程 DaemonSet 每个节点一个 Pod │
│ 批处理任务 Job 一次性执行 │
│ 定时任务 CronJob 周期性执行 │
│ │
└─────────────────────────────────────────────────────────────────┘
小结
本章我们学习了:
- StatefulSet 概念:管理有状态应用的控制器
- 核心特性:稳定网络标识、持久存储、有序管理
- Headless Service:提供稳定的 DNS 解析
- Pod 管理策略:OrderedReady 和 Parallel
- 更新策略:RollingUpdate、Partition、OnDelete
- 最佳实践:资源限制、健康检查、反亲和性
练习
- 创建一个 3 副本的 StatefulSet,观察 Pod 创建顺序
- 删除中间的 Pod,观察重建后的名称和存储是否保持
- 使用 Partition 策略进行金丝雀发布
- 配置 Pod 反亲和性,确保副本分布在不同节点