k8s中的StatefulSet 控制器
1. StatefulSet 简介
1.1. 它是什么?
StatefulSet 是 Kubernetes 提供的一种工作负载 API 对象(控制器),专门用于管理和部署有状态应用。与管理无状态应用的 Deployment不同,StatefulSet 为其管理的每个 Pod 提供了稳定且唯一的身份标识和持久化存储。
1.2. 解决了什么问题?
在容器化的世界中,管理有状态应用(如数据库、消息队列)是一项公认的挑战。这类应用通常对运行环境有特殊要求,而这些要求是标准无状态控制器难以满足的:
- 稳定的网络标识 (Stable Network Identity):集群中的应用实例需要通过固定的、可预测的地址相互发现和通信。例如,数据库主节点需要一个稳定的地址,以便从节点能够连接它。
- 持久化的数据存储 (Persistent Storage):应用实例的数据必须在其生命周期之外得以保留。即使 Pod 因故障、重启或迁移而重新调度,其关联的数据也必须能够被重新访问。
- 有序的部署和启停 (Ordered Operations):许多分布式系统(如 ZooKeeper、etcd)在启动、扩容或关闭时需要遵循严格的顺序。例如,必须先启动主节点,然后才能启动从节点。
传统的 Deployment和 ReplicaSet 专为无状态服务设计,它们认为所有 Pod 都是可任意替换的(fungible)。Pod 的名称、主机名和存储都是动态且非持久的,这完全无法满足有状态应用的需求。StatefulSet 的诞生正是为了填补这一关键空白。
1.3. 为什么需要它?
为了高效地编排不同类型的应用,Kubernetes 必须能够区分并分别管理无状态服务和有状态服务。它们的核心差异决定了需要不同的控制器。
| 服务类型 | 特征 | 示例 | 适用控制器 | |
|---|---|---|---|---|
| 无状态服务 (Stateless) | - 不在本地持久化关键数据。 - 所有实例对同一请求的响应完全一致。 - 实例可被随意创建、销毁和替换。 | Web 服务器 (Nginx, Apache)、API 网关、前端应用 (React, Vue) | | |
| 有状态服务 (Stateful) | - 需要在本地持久化数据。 - 每个实例具有唯一且稳定的身份,不可随意替换。 - 实例之间可能存在主从、集群等复杂的拓扑关系。 | 数据库 (MySQL, PostgreSQL, MongoDB)、消息队列 (Kafka, RabbitMQ)、分布式存储 (ZooKeeper, etcd) | |
举例说明:一个典型的 Web 应用,如果将其用户会话(Session)数据存储在外部的 Redis 中,那么该 Web 应用本身就是无状态的,可以使用 Deployment 轻松地进行水平扩展。但 Redis 本身作为数据存储服务,就是有状态的,需要使用 StatefulSet 来确保数据的一致性和服务的稳定性。
StatefulSet 的出现,使得 Kubernetes 能够以一种声明式、自动化的方式来编排复杂的有状态分布式系统,这是 Kubernetes 能够管理几乎所有类型工作负载的基石。
2. 核心特性
StatefulSet 通过一系列精心设计的机制,确保有状态应用能够稳定、可靠地运行。
- 稳定的、唯一的网络标识
- Pod 名称:Pod 名称遵循固定的格式:
<StatefulSet名称>-<序号>,其中序号是一个从 0 开始的整数(例如web-0,web-1)。即使 Pod 重启或被重新调度,其名称和序号也保持不变。
- 主机名 (Hostname):默认情况下,Pod 的主机名 (
hostname) 会被设置为其 Pod 名称。
- DNS 记录:通过关联的 Headless Service,每个 Pod 都会获得一个唯一的、稳定的内部 DNS FQDN(完全限定域名),格式为:
<Pod名称>.<Headless Service名称>.<命名空间>.svc.cluster.local(例如web-0.nginx.default.svc.cluster.local)。应用实例间可以通过这个稳定的 DNS 地址进行发现和通信,无需关心 Pod IP 的变化。
- 稳定的、持久的存储
- StatefulSet 使用
volumeClaimTemplates字段,为每个 Pod 自动创建一个唯一的 PersistentVolumeClaim (PVC)。
- 这个 PVC 的名称也遵循固定格式:
<volumeClaimTemplate名称>-<StatefulSet名称>-<序号>(例如www-web-0)。
- 当 Pod 发生迁移时,Kubernetes 会确保其对应的 PVC 被重新挂载到新的节点上,从而保证数据能够随 Pod "迁移",实现数据的持久化。
- 有序的、逐一的部署和扩缩容 (Ordinal Index)
- 部署与扩容:Pod 按照其序号升序(
0, 1, 2, ..., N-1)逐一创建。控制器必须等待前一个 Pod (pod-i) 进入Running和Ready状态后,才会开始创建下一个 Pod (pod-i+1)。
- 缩容与删除:Pod 按照其序号降序(
N-1, N-2, ..., 0)逐一销毁。这种优雅的终止顺序对于需要先安全转移数据或领导者角色的集群应用至关重要。
- 有序的、自动的滚动更新
- 当更新 StatefulSet 的 Pod 模板(例如,更换容器镜像)时,Pod 会按照其序号降序(从
N-1到0)进行逐一更新。
- 这种机制确保了在更新过程中集群的可用性。例如,在一个主从架构中,可以先更新所有从节点,最后再更新主节点,从而最大限度地减少服务中断。
3. 工作原理
StatefulSet 的核心工作机制围绕着**顺序性(Ordinality)和唯一性(Uniqueness)**展开,并依赖其他 Kubernetes 关键组件协同工作。
3.1. 部署与扩容流程
当一个 replicas: 2 的 StatefulSet 被创建时,其流程如下:
- 创建
web-0:StatefulSet 控制器首先创建 Podweb-0。 - 创建 PVC
www-web-0:与此同时,控制器根据volumeClaimTemplates为web-0创建一个名为www-web-0的 PVC,并由系统为其绑定一个合适的 PV。 - 等待
web-0就绪:控制器持续监控web-0的状态,直到其进入Running和Ready状态。 - 创建
web-1:在确认web-0就绪后,控制器开始创建 Podweb-1及其对应的 PVCwww-web-1。 - 等待
web-1就绪:等待web-1进入Running和Ready状态。 - 完成:所有副本都已创建并就绪,部署完成。
以下命令展示了 Pod 的有序创建过程:
$ kubectl get pods -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 6sShellCopy
3.2. 删除与缩容流程
当 replicas从 N缩减到 M时:
- 终止
web-(N-1):控制器首先向序号最大的 Podweb-(N-1)发送终止信号。
- 等待终止完成:控制器等待
web-(N-1)的所有资源(包括 Pod 本身)被完全清理。
- 继续终止:然后继续删除
web-(N-2),依次类推,直到 Pod 数量达到目标值M。
重要提示:当 StatefulSet 被删除时,其关联的 PVC 默认不会被删除。这是一种安全机制,旨在防止意外删除导致关键数据丢失。您需要手动清理这些 PVC。
3.3. 更新流程 ( RollingUpdate 策略)
当 Pod 模板被修改时,更新流程按降序进行:
- 更新
web-(N-1):控制器首先删除并重建序号最大的 Podweb-(N-1)。 - 等待就绪:等待新版本的
web-(N-1)进入Running和Ready状态。 - 继续更新:接着对
web-(N-2)执行同样的操作,依次进行,直至所有需要更新的 Pod 都已完成。 - 分区更新 (
partition):通过设置spec.updateStrategy.rollingUpdate.partition字段,可以实现**灰度发布(Canary Release)**或分阶段更新。只有序号大于或等于partition值的 Pod 会被自动更新,而序号小于该值的 Pod 将保持不变。这为测试新版本提供了强大的控制能力。
3.4. 管理策略 ( podManagementPolicy )
StatefulSet 提供了两种 Pod 管理策略,通过 spec.podManagementPolicy字段设置:
-
OrderedReady(默认值):严格遵循上述的顺序性保证。这是大多数有状态应用所期望的行为。
-
Parallel:允许控制器并行地启动或终止所有 Pod,无需等待前一个 Pod 就绪。此策略适用于那些只需要稳定身份和存储,但不需要有序操作的分布式系统。
4. 与 Deployment 的对比
下表清晰地总结了 StatefulSet 和 Deployment 之间的核心区别:
| 特性 | StatefulSet | Deployment | |
|---|---|---|---|
| 目标应用 | 有状态应用 (例如数据库、Kafka) | 无状态应用 (例如 Web 服务器、API) | |
| Pod 身份 | 稳定、唯一 (如 , ) | 动态、可替换 (如 ) | |
| 网络标识 | 稳定 (通过 Headless Service 的固定 DNS) | 不稳定 (Pod IP 动态变化,通过 ClusterIP Service 访问) | |
| 存储 | 稳定、持久 (每个 Pod 绑定唯一的 PVC) | 共享或临时 (可共享同一个 PVC,或使用临时存储) | |
| 部署/扩容 | 有序 (按序号 逐一创建) | 并发 (同时创建所有 Pod) | |
| 缩容/删除 | 有序 (按序号 逐一删除) | 并发 (同时删除所有 Pod) | |
| 更新策略 | 有序 (默认按序号 逐一滚动更新) | 灵活 (支持 和 ) |
5. 组件与依赖
StatefulSet 的强大功能并非孤立实现,而是紧密依赖于 Kubernetes 生态中的其他几个核心组件。
- Headless Service
- 定义:一种特殊的
Service,其spec.clusterIP被显式设置为None。
- 作用:它不提供单一的、用于负载均衡的 ClusterIP。相反,Kubernetes DNS 系统会为该 Service 所选择的每一个 Pod 都创建一个独立的 DNS A 记录。这是实现 StatefulSet Pod 稳定网络标识的绝对关键。StatefulSet 的
spec.serviceName字段必须指向一个预先创建好的 Headless Service。
- PersistentVolume (PV) & PersistentVolumeClaim (PVC)
- 作用:为有状态应用提供抽象的、可持久化的存储资源。PV 是集群中的一块存储,而 PVC 是用户对存储的请求。
-
volumeClaimTemplates:这是 StatefulSet Spec 中的一个核心字段。它是一个 PVC 的模板,StatefulSet 控制器会使用这个模板为每个 Pod(从 0到N-1)动态地创建一个专属的 PVC。这确保了每个 Pod 都有自己独立的、持久化的存储卷。
- StorageClass (可选)
- 作用:与
volumeClaimTemplates结合使用时,可以实现存储的动态供应 (Dynamic Provisioning)。当 StatefulSet 创建一个 PVC 时,如果该 PVC 指定了一个StorageClass,底层的云提供商或存储系统会自动创建一个匹配的 PV
6. 典型应用场景
StatefulSet 非常适合部署那些需要稳定身份、持久化存储和有序操作的分布式系统。常见的应用场景包括:
- 数据库集群:如 MySQL/PostgreSQL 主从复制集群、MongoDB 副本集、Cassandra 集群。
- 消息队列集群:如 Kafka、RabbitMQ、ActiveMQ。
- 分布式协调服务:如 ZooKeeper、etcd、Consul。
- 分布式文件系统:如 GlusterFS、Ceph。
- 搜索引擎集群:如 Elasticsearch。
战略考量:StatefulSet vs. Operator虽然可以直接使用 StatefulSet 部署上述复杂应用,但在生产环境中,更推荐使用专门为此类应用定制的 Operator(例如prometheus-operator,etcd-operator,mysql-operator)。 Operator 是一种更高级的模式,它在 StatefulSet 的基础上封装了特定领域的运维知识(如自动备份、故障转移、版本升级、配置调优等),能够以全自动化的方式管理复杂有状态应用的整个生命周期,极大地降低了运维复杂性和人为错误的风险。
7. YAML 示例与解析
以下是一个部署包含 2 个副本的 Nginx 有状态应用的完整示例,它演示了各个组件如何协同工作。
7.1. 依赖组件:Headless Service
首先,我们必须创建一个 Headless Service。这个 Service 的 metadata.name 必须与 StatefulSet 中引用的 serviceName 完全匹配。
# headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx # 这个名称必须与 StatefulSet 中的 serviceName 匹配
labels:
app: nginx
spec:
ports:
- name: http
port: 80
clusterIP: None # 关键:设置为 None,声明这是一个 Headless Service
selector:
app: nginx # 选择器,用于匹配 StatefulSet 创建的 Pod
7.2. StatefulSet 定义
接下来,我们定义 StatefulSet 本身。
# nginx-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
# 1. 关联 Headless Service,这是网络标识的基础
serviceName: "nginx"
# 2. 期望的副本数量
replicas: 2
# 3. Pod 选择器,必须与 Pod 模板中的标签匹配
selector:
matchLabels:
app: nginx # .spec.selector.matchLabels 必须匹配 .spec.template.metadata.labels
# 4. Pod 模板,定义了如何创建每个 Pod
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19.2 # 使用一个具体的镜像版本
ports:
- name: web
containerPort: 80
volumeMounts:
- name: www # 挂载点的名称,必须与下面的 volumeClaimTemplates 名称匹配
mountPath: /usr/share/nginx/html
# 5. 存储卷声明模板,用于为每个 Pod 自动创建独立的 PVC
volumeClaimTemplates:
- metadata:
name: www # PVC 模板的名称
spec:
accessModes: ['ReadWriteOnce'] # 访问模式:单节点读写
# storageClassName: "your-storage-class" # 推荐在生产中显式指定
resources:
requests:
storage: 1Gi # 每个 Pod 请求 1Gi 的存储空间
7.3. 关键字段解析
-
spec.serviceName: (必需)
- 作用:指定与此 StatefulSet 关联的 Headless Service 的名称。
- 说明:这是实现 Pod 稳定网络标识的基石。StatefulSet 使用此服务来生成每个 Pod 的唯一 DNS 记录(例如
web-0.nginx,web-1.nginx)。该 Service 必须在 StatefulSet 创建之前存在。
-
spec.volumeClaimTemplates: (可选)
- 作用:一个 PVC 模板的列表。StatefulSet 控制器会为每个 Pod 实例(从 0 到 N-1)使用此模板生成一个对应的 PVC。
-
metadata.name: 定义了模板的名称(示例中为www)。这个名称有两个用途:
- 作为最终生成的 PVC 名称的前缀,构成的 PVC 名称为
<template-name>-<statefulset-name>-<pod-index>,例如www-web-0。 - 在 Pod 模板的
spec.containers.volumeMounts中通过此名称引用,以将卷挂载到容器的指定路径。
-
spec: 定义了所创建 PVC 的具体规格,包括访问模式 (accessModes) 和请求的存储大小 (resources.requests.storage)。你也可以在这里指定storageClassName来使用动态存储供应。
