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) 数据库、消息队列、分布式存储
迁移提示: 将传统数据库迁移至 Kubernetes 时,应优先使用 StatefulSet 而非 Deployment。Deployment 无法保证 Pod 重建后仍使用相同的存储卷和网络标识,会导致数据库集群脑裂或数据丢失。

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 IP
  • mysql-1.mysql-headless.default.svc.cluster.local → Pod IP

这种机制使得数据库集群中的每个节点都能通过固定的域名被其他节点发现,无需依赖 IP 地址的变化。

性能调优要点: 在数据库迁移场景中,建议将 Headless Service 的 publishNotReadyAddresses 设置为 true。这样即使 Pod 尚未就绪(如正在恢复数据),其他节点也能通过 DNS 解析到该 Pod 的 IP,避免因健康检查延迟导致集群发现失败。
Headless Service DNS 解析流程 客户端应用 集群 DNS (CoreDNS) 查询 mysql-0.mysql-headless 返回 Pod IP: 10.1.0.5 mysql-0 10.1.0.5 mysql-1 10.1.0.6 mysql-2 10.1.0.7 直连(无负载均衡) 关键特性 无 Cluster IP 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
迁移风险提示: 在数据库迁移过程中,切勿手动修改 StatefulSet 的 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-0
  • data-mysql-1
  • data-mysql-2

即使 mysql-0 Pod 被删除并重建,新的 Pod 仍会绑定到 data-mysql-0 PVC,读取之前写入的数据。

性能调优实践: 对于数据库工作负载,建议在 StorageClass 中启用 volumeBindingMode: WaitForFirstConsumer。这样 PV 的创建会延迟到 Pod 被调度到具体节点之后,确保 PV 与 Pod 所在节点的可用区一致,避免跨可用区存储访问带来的延迟开销。
StatefulSet 存储卷绑定架构 StatefulSet 控制器 Pod: mysql-0 主机名: mysql-0 IP: 10.1.0.5 挂载: /var/lib/mysql Pod: mysql-1 主机名: mysql-1 IP: 10.1.0.6 挂载: /var/lib/mysql Pod: mysql-2 主机名: mysql-2 IP: 10.1.0.7 挂载: /var/lib/mysql PVC: data-mysql-0 PVC: data-mysql-1 PVC: data-mysql-2 PV (10Gi) PV (10Gi) PV (10Gi) Pod 重建后 自动绑定同名 PVC

4.5 数据库迁移中的 StatefulSet 最佳实践

在将数据库迁移到 Kubernetes 时,以下实践可以帮助你充分利用 StatefulSet 的特性:

  1. 始终使用 Headless Service: 为 StatefulSet 创建专用的 Headless Service,并确保 serviceName 字段正确引用。这是 Pod 获得稳定 DNS 记录的前提。
  2. 合理规划副本数: 数据库集群的副本数应与数据分片或副本集数量一致。例如,MongoDB 副本集通常为 3 或 5 个节点。
  3. 设置 Pod 管理策略: 对于需要并行启动的场景(如 Cassandra),可以设置 podManagementPolicy: Parallel,跳过有序启动。
  4. 使用 Readiness Probe: 配置数据库专用的就绪探针(如检查主从同步状态),确保流量只在 Pod 完全就绪后才路由到该节点。
  5. 备份 VolumeClaimTemplate: 在迁移前,使用快照或备份工具对 PVC 进行全量备份,防止意外删除导致数据丢失。
迁移案例: 某金融科技公司将 MySQL 主从集群迁移至 Kubernetes,使用 StatefulSet 管理 3 个节点。通过 Headless Service,从节点通过 mysql-0.mysql-headless 域名连接主节点进行同步。迁移后,主节点故障时只需删除 Pod,StatefulSet 自动重建并绑定原 PVC,数据零丢失,恢复时间从原来的 30 分钟缩短至 2 分钟。