Kubernetes 中的 StatefulSet

文章目录
- [Kubernetes 中的 StatefulSet](#Kubernetes 中的 StatefulSet)
-
- [1. 为什么需要 StatefulSet?](#1. 为什么需要 StatefulSet?)
- [2. StatefulSet 的核心特性](#2. StatefulSet 的核心特性)
-
- [2.1 稳定的网络标识](#2.1 稳定的网络标识)
- [2.2 稳定的持久化存储](#2.2 稳定的持久化存储)
- [2.3 有序的部署和伸缩](#2.3 有序的部署和伸缩)
- [2.4 扩缩容行为](#2.4 扩缩容行为)
- [3. StatefulSet 的组成部分](#3. StatefulSet 的组成部分)
- [4. StatefulSet 的更新策略](#4. StatefulSet 的更新策略)
- [5. 删除和级联删除](#5. 删除和级联删除)
- [6. StatefulSet 的局限性](#6. StatefulSet 的局限性)
- [7. 典型使用场景](#7. 典型使用场景)
- [8. 与 Deployment 的对比](#8. 与 Deployment 的对比)
- [9. 最佳实践](#9. 最佳实践)
- [10. 简单示例:部署一个有状态 Nginx](#10. 简单示例:部署一个有状态 Nginx)
- 总结
StatefulSet 是 Kubernetes 中用于管理有状态应用的工作负载 API 对象。与 Deployment 专注于无状态应用不同,StatefulSet 为每个 Pod 提供持久标识符和稳定的存储,从而确保 Pod 在重新调度后仍能保留其状态和身份。
1. 为什么需要 StatefulSet?
在容器化环境中,无状态应用(如 Web 前端)可以随意扩缩容,每个 Pod 对等且可替换。但有状态应用(如数据库、消息队列、分布式协调服务)需要满足以下要求:
- 稳定的网络标识:每个实例需要固定的主机名或 DNS 名称,即使重启或迁移也不会改变。
- 持久化存储:每个实例绑定独立的持久卷,重启后数据不丢失。
- 有序的部署、伸缩和删除:实例之间通常有启动顺序要求(如主从关系),需要按顺序执行操作。
- 稳定的成员关系:集群中的成员身份需要可预测,便于发现和连接。
Deployment 无法满足这些要求,因为它的 Pod 名称随机、共享存储卷、无顺序控制。StatefulSet 正是为解决这些问题而设计的。
2. StatefulSet 的核心特性
2.1 稳定的网络标识
- 每个 Pod 拥有唯一的、稳定的网络标识,格式为:
$(statefulset名称)-$(序号)。 - 例如,StatefulSet 名为
web,副本数为 3,则 Pod 名称依次为web-0、web-1、web-2。 - 配合 Headless Service (ClusterIP 为 None 的 Service),可以为每个 Pod 提供稳定的 DNS 记录:
pod-name.service-name.namespace.svc.cluster.local。 - 即使 Pod 被重新调度到其他节点,其名称和主机名保持不变,保证了网络身份的稳定性。
2.2 稳定的持久化存储
- 每个 Pod 可以关联一个或多个 PersistentVolumeClaim(PVC),PVC 的名称也包含 Pod 的序号:
pvc-name-statefulset名称-序号。 - 当 Pod 被重新调度时,Kubernetes 会自动将原有的 PVC 重新挂载到新的 Pod 上,确保数据持久化。
- 这要求 StorageClass 支持动态制备或预先创建好 PV,PVC 与 Pod 一一绑定。
2.3 有序的部署和伸缩
- 顺序创建:StatefulSet 按照序号从 0 到 N-1 的顺序逐个创建 Pod,只有前一个 Pod 处于 Running 和 Ready 状态后,才会创建下一个 Pod。
- 顺序删除:当缩容或删除 StatefulSet 时,会按序号从 N-1 到 0 的顺序逐个删除 Pod。
- 顺序更新 :更新时默认采用
RollingUpdate策略,也会按序号倒序逐个更新 Pod(从最后一个开始),以保证可用性。 - 支持并行操作 :可以通过
podManagementPolicy设置为Parallel来改变默认的有序行为,但通常会牺牲一定的顺序保证。
2.4 扩缩容行为
- 扩容时,新增 Pod 的序号继续递增(例如从 3 增加到 4,新增 Pod 名称为
web-3)。 - 缩容时,只会删除序号最大的 Pod,且会等待其完全终止后再删除下一个。
- 如果某个 Pod 故障,StatefulSet 会重新创建一个同名 Pod,并尝试挂载原来的 PVC。
3. StatefulSet 的组成部分
一个典型的 StatefulSet 清单包含以下关键字段:
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx" # 必须指定一个 Headless Service
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
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
-
serviceName:必须指定一个 Headless Service 的名称,用于为 Pod 提供稳定的网络标识。该 Service 通常定义如下:yamlapiVersion: v1 kind: Service metadata: name: nginx spec: clusterIP: None selector: app: nginx ports: - port: 80 name: web -
volumeClaimTemplates:定义 PVC 模板,StatefulSet 会为每个 Pod 创建一个独立的 PVC。PVC 的命名规则为:<volumeClaimTemplate.name>-<statefulset.name>-<ordinal>。
4. StatefulSet 的更新策略
StatefulSet 支持两种更新策略:
RollingUpdate(默认):按序号从大到小的顺序逐个更新 Pod。可以设置partition参数来控制更新的范围(例如只更新序号大于等于 partition 的 Pod),实现金丝雀发布或灰度发布。OnDelete:手动删除 Pod 后才会触发重新创建(使用新模板),适用于需要精细控制更新时机的场景。
更新时,还可以配置 spec.updateStrategy.rollingUpdate.maxUnavailable(可选,但 StatefulSet 默认保证至少有一个 Pod 可用)。
5. 删除和级联删除
- 删除 StatefulSet 时,默认不会删除其管理的 Pod 和 PVC(级联删除策略为
orphan)。这意味着即使 StatefulSet 被删除,Pod 和 PVC 仍然存在,需要手动清理。 - 如果需要同时删除 Pod,可以使用
kubectl delete statefulset web --cascade=orphan改为background或foreground级联删除策略(Kubernetes 1.7+ 支持)。但 PVC 始终需要手动删除。
6. StatefulSet 的局限性
- 升级复杂性:有状态应用的升级通常比无状态复杂,可能需要考虑数据格式兼容性、主从切换等。
- 存储管理:PVC 的生命周期需要额外关注,缩容或删除 StatefulSet 不会自动删除 PVC,可能导致存储资源泄露。
- 扩缩容速度:由于顺序创建/删除,扩缩容速度可能较慢,尤其是副本数较多时。
- 不保证 Pod 的唯一性:虽然名称唯一,但 StatefulSet 不会阻止两个同名 Pod 同时运行(例如在节点故障恢复时可能短暂出现),需要应用自身处理这种冲突(如使用分布式锁)。
7. 典型使用场景
- 分布式数据库:如 Cassandra、MongoDB 副本集、Elasticsearch,需要每个节点有稳定的身份和数据存储。
- 消息队列:如 Kafka、RabbitMQ,每个 broker 需要持久化存储和固定的网络标识。
- 有状态中间件:如 ZooKeeper、etcd,依赖稳定的成员关系进行集群协调。
- 需要独立存储的 Legacy 应用:迁移到 Kubernetes 时,每个实例需要独立的存储卷。
8. 与 Deployment 的对比
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机后缀,如 pod-xxx |
固定有序名称,如 pod-0 |
| 存储 | 共享存储卷(或使用单个 PVC) | 每个 Pod 独立的 PVC |
| 网络标识 | 无保证,Pod 重建后 IP 变化 | 稳定的 DNS 名称(通过 Headless Service) |
| 启动/停止顺序 | 无序,可并行 | 有序(默认) |
| 扩缩容 | 快速,可并行 | 顺序执行 |
| 更新策略 | 滚动更新,支持暂停、最大不可用等 | 滚动更新(默认倒序)或 OnDelete |
| 适用场景 | 无状态应用 | 有状态应用 |
9. 最佳实践
- 始终为 StatefulSet 创建 Headless Service,以便其他服务通过 DNS 发现 Pod。
- 谨慎使用
Parallel策略,只有在应用本身不依赖启动顺序时才使用。 - 监控 PVC 的使用情况,避免存储空间耗尽。
- 对于需要备份的数据,结合 VolumeSnapshot 或备份工具定期备份。
- 更新镜像或配置时 ,考虑数据兼容性,必要时使用
partition进行分批更新。
10. 简单示例:部署一个有状态 Nginx
假设我们要部署一个 Nginx 集群,每个实例有自己的独立存储(存储不同内容)和稳定域名。
-
创建 Headless Service:
bashkubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: nginx spec: clusterIP: None selector: app: nginx ports: - port: 80 EOF -
创建 StatefulSet:
bashkubectl apply -f - <<EOF apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: nginx replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80 volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi EOF -
验证:
bashkubectl get pods -l app=nginx kubectl get pvc可以看到 Pod 名称为
web-0、web-1、web-2,每个 Pod 对应一个 PVCwww-web-0、www-web-1、www-web-2。 -
测试网络标识:
在集群内另一个 Pod 中,可以解析
web-0.nginx.default.svc.cluster.local访问到特定 Pod。
总结
StatefulSet 是 Kubernetes 对有状态应用的标准管理方式,通过稳定的网络标识、独立的持久化存储和有序的操作,弥补了 Deployment 在管理有状态应用时的不足。虽然它引入了一些复杂性,但对于运行数据库、消息队列等核心有状态服务来说,是不可或缺的组件。理解 StatefulSet 的工作原理和最佳实践,能够帮助我们在 Kubernetes 中更可靠地运行生产级有状态应用。