4. StatefulSet 与有状态应用:Headless Service、Pod 标识、存储卷模板
在 Kubernetes 中部署数据库等有状态应用,核心挑战在于如何为每个 Pod 提供稳定的网络标识和持久化存储。StatefulSet 正是为解决这一问题而设计的控制器,它通过 Headless Service、有序的 Pod 标识 和 存储卷模板(VolumeClaimTemplate) 三大机制,为有状态工作负载提供了可预测、可恢复的运行环境。
4.1 StatefulSet 的核心设计理念
与 Deployment 不同,StatefulSet 管理的每个 Pod 都拥有唯一的身份标识,并且遵循严格的启动和终止顺序。这种设计使得数据库集群中的主从节点能够被准确识别和访问。
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机后缀(如 app-7d9f8c6b4-abcde) |
有序索引(如 mysql-0, mysql-1) |
| 网络标识 | 无状态,依赖 Service 负载均衡 | 通过 Headless Service 提供稳定的 DNS 记录 |
| 存储 | 共享卷或动态挂载,无持久绑定 | 每个 Pod 绑定独立的 PVC,Pod 重建后自动重挂 |
| 启动/终止顺序 | 并行 | 有序(从 N-1 到 0) |
| 适用场景 | 无状态应用(Web、API) | 数据库、消息队列、分布式存储 |
4.2 Headless Service:稳定的网络标识
Headless Service 是 StatefulSet 网络标识的基石。它不提供 Cluster IP 和负载均衡,而是为每个 Pod 生成独立的 DNS A/AAAA 记录,格式为 <pod-name>.<service-name>.<namespace>.svc.cluster.local。
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None # 关键:设置为 None 即为 Headless Service
selector:
app: mysql
ports:
- port: 3306
name: mysql
当 StatefulSet 创建 Pod mysql-0 和 mysql-1 后,集群 DNS 会自动生成以下记录:
mysql-0.mysql-headless.default.svc.cluster.local→ Pod IPmysql-1.mysql-headless.default.svc.cluster.local→ Pod IP
这种机制使得数据库集群中的每个节点都能通过固定的域名被其他节点发现,无需依赖 IP 地址的变化。
publishNotReadyAddresses 设置为 true。这样即使 Pod 尚未就绪(如正在恢复数据),其他节点也能通过 DNS 解析到该 Pod 的 IP,避免因健康检查延迟导致集群发现失败。
4.3 Pod 标识:有序索引与稳定身份
StatefulSet 为每个 Pod 分配一个从 0 开始的整数索引,该索引嵌入在 Pod 名称和主机名中。这种有序标识在数据库迁移中至关重要:
- 主节点选举: 通常约定索引最小的 Pod(如
mysql-0)作为主节点,其他 Pod 作为从节点。 - 数据恢复: 当 Pod 崩溃重建后,新 Pod 仍使用相同的名称和索引,客户端可以通过 DNS 继续访问。
- 滚动更新: StatefulSet 按索引逆序更新(从
N-1到0),确保从节点先更新,主节点最后更新。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless # 关联 Headless Service
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
- name: MYSQL_REPLICATION_ROLE
valueFrom:
fieldRef:
fieldPath: metadata.name # 通过 Pod 名称判断角色
ports:
- containerPort: 3306
volumeClaimTemplates: # 存储卷模板
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
replicas 数量超过集群实际节点数。如果缩容,StatefulSet 会删除索引最大的 Pod,可能导致该 Pod 上的数据分片丢失。建议先通过备份或数据迁移工具将数据转移后再调整副本数。
4.4 存储卷模板(VolumeClaimTemplate)
VolumeClaimTemplate 是 StatefulSet 独有的字段,它为每个 Pod 自动生成一个独立的 PersistentVolumeClaim(PVC)。当 Pod 被调度到不同节点或重建时,Kubernetes 会确保该 Pod 重新绑定到同一个 PVC,从而保证数据持久性。
| 特性 | 普通 Volume(Deployment) | VolumeClaimTemplate(StatefulSet) |
|---|---|---|
| PVC 创建方式 | 手动创建或共享 | 自动为每个 Pod 创建独立 PVC |
| PVC 命名规则 | 自定义 | <volume-name>-<statefulset-name>-<ordinal> |
| Pod 重建后绑定 | 可能丢失绑定关系 | 自动重挂到同名 PVC |
| 存储隔离性 | 多个 Pod 可能共享同一卷 | 每个 Pod 独占卷 |
| 扩容/缩容 | 需手动管理 PVC | 自动创建/保留 PVC |
例如,对于 mysql StatefulSet 和 data 卷模板,生成的 PVC 名称为:
data-mysql-0data-mysql-1data-mysql-2
即使 mysql-0 Pod 被删除并重建,新的 Pod 仍会绑定到 data-mysql-0 PVC,读取之前写入的数据。
volumeBindingMode: WaitForFirstConsumer。这样 PV 的创建会延迟到 Pod 被调度到具体节点之后,确保 PV 与 Pod 所在节点的可用区一致,避免跨可用区存储访问带来的延迟开销。
4.5 数据库迁移中的 StatefulSet 最佳实践
在将数据库迁移到 Kubernetes 时,以下实践可以帮助你充分利用 StatefulSet 的特性:
- 始终使用 Headless Service: 为 StatefulSet 创建专用的 Headless Service,并确保
serviceName字段正确引用。这是 Pod 获得稳定 DNS 记录的前提。 - 合理规划副本数: 数据库集群的副本数应与数据分片或副本集数量一致。例如,MongoDB 副本集通常为 3 或 5 个节点。
- 设置 Pod 管理策略: 对于需要并行启动的场景(如 Cassandra),可以设置
podManagementPolicy: Parallel,跳过有序启动。 - 使用 Readiness Probe: 配置数据库专用的就绪探针(如检查主从同步状态),确保流量只在 Pod 完全就绪后才路由到该节点。
- 备份 VolumeClaimTemplate: 在迁移前,使用快照或备份工具对 PVC 进行全量备份,防止意外删除导致数据丢失。
mysql-0.mysql-headless 域名连接主节点进行同步。迁移后,主节点故障时只需删除 Pod,StatefulSet 自动重建并绑定原 PVC,数据零丢失,恢复时间从原来的 30 分钟缩短至 2 分钟。