2. 有状态业务基础

在 OpenShift 中,无状态应用通常用 Deployment 管理,Pod 名称随机、副本可互换、存储可有可无。但有状态业务完全不同:每个副本往往有固定身份、独立数据目录、启动顺序要求,甚至客户端需要按名称精确访问某一个实例。Kubernetes 为此提供了 StatefulSet,并配合 Headless Service 与 PersistentVolumeClaim 形成一套稳定的身份与存储体系。理解这三者的协作方式,是后续在 OpenShift 上部署数据库、消息队列、分布式存储等业务的前提。

StatefulSet 解决的核心问题

StatefulSet 与 Deployment 最大的区别在于“稳定标识”。它保证三件事:第一,Pod 名称稳定且有序,形如 mysql-0、mysql-1、mysql-2,序号从 0 开始;第二,每个 Pod 拥有稳定的网络标识,即 <pod-name>.<service-name> 形式的 DNS 记录;第三,每个 Pod 绑定独立的存储卷,即使 Pod 被重建、被调度到其他节点,仍然挂载原来那份数据。

StatefulSet 默认采用顺序管理策略。创建时按 0、1、2 的顺序逐个启动,前一个 Pod 进入 Ready 状态后才会创建下一个;删除或缩容时则逆序进行。这一机制对需要主从选举、集群成员逐个加入的业务非常关键。如果业务本身不要求顺序,也可以通过 podManagementPolicy: Parallel 改为并行启动,但身份与存储的稳定性不变。

Headless Service 提供稳定网络标识

普通 Service 会分配一个 ClusterIP,作为一组 Pod 的负载均衡入口。而 Headless Service 把 clusterIP 显式设置为 None,不分配虚拟 IP,DNS 查询时直接返回后端所有 Pod 的地址。它正是 StatefulSet 稳定网络标识的载体。

假设 Headless Service 名为 mysql,命名空间为 db,那么每个 Pod 会获得如下 DNS 名称:

mysql-0.mysql.db.svc.cluster.local
mysql-1.mysql.db.svc.cluster.local
mysql-2.mysql.db.svc.cluster.local

客户端连接 mysql-0.mysql 时,DNS 直接解析到 0 号 Pod 的 IP,而不是经过负载均衡。这样主从复制、分片路由、集群成员发现都能精确指向某个实例。需要注意,Pod 重建后 IP 会变化,但 DNS 名称保持不变,因此业务配置里应始终使用域名而非 IP。

volumeClaimTemplates 与持久卷声明

StatefulSet 通过 volumeClaimTemplates 为每个 Pod 自动创建独立的 PVC。模板中定义存储类、容量和访问模式,控制器会按 Pod 序号生成 PVC,命名规则为 <模板名>-<StatefulSet名>-<序号>,例如 data-mysql-0、data-mysql-1。每个 PVC 独立绑定一个 PV,Pod 通过 volumeMounts 挂载到容器内的数据目录。

这里有一个容易踩坑的点:删除 StatefulSet 时,默认不会删除这些 PVC,这是为了保护数据。缩容时同样保留已创建的 PVC,以便后续扩容时原 Pod 能重新挂回原数据。如果确实要清理,需要手动删除 PVC 和对应的 PV。在 OpenShift 中,还可以通过 StorageClass 的 reclaimPolicy 控制 PV 回收行为,Delete 会在 PVC 删除后回收底层存储,Retain 则保留数据等待人工处理。

有状态 Pod 的调度机制

StatefulSet 的 Pod 调度并非简单地“随便找个节点”。它受到多重约束共同作用。首先是 PVC 与 PV 的绑定关系:如果 PV 是本地卷或带节点亲和性的存储,Pod 只能被调度到该 PV 所在节点。其次是 nodeSelector、affinity、taints/tolerations 等常规调度规则。再次是拓扑分布约束,例如通过 topologySpreadConstraints 让多个副本分散到不同可用区,提升容灾能力。

当 Pod 被删除重建时,调度器会先看它绑定的 PVC 是否已经绑定到某个 PV,再结合节点条件选择目标节点。如果原节点不可用且 PV 是节点绑定的本地卷,Pod 可能一直处于 Pending,这正是有状态业务排障中的典型场景。因此生产环境通常选择支持跨节点挂载的网络存储,或为本地卷配合合理的副本与故障转移策略。

StatefulSet 稳定身份与存储协作关系 Headless Service mysql (clusterIP: None) DNS 直接返回 Pod 地址 提供稳定网络标识 StatefulSet mysql (replicas: 3) 顺序创建 / 逆序删除 稳定 Pod 名称与序号 volumeClaimTemplates storageClass: fast-ssd 每 Pod 独立 PVC 数据与 Pod 生命周期解耦 Pod 与 PVC 绑定关系 mysql-0 DNS: mysql-0.mysql.db PVC: data-mysql-0 挂载: /var/lib/mysql 角色示例:主节点 mysql-1 DNS: mysql-1.mysql.db PVC: data-mysql-1 挂载: /var/lib/mysql 角色示例:从节点 mysql-2 DNS: mysql-2.mysql.db PVC: data-mysql-2 挂载: /var/lib/mysql 角色示例:从节点 调度约束叠加 ① PVC/PV 绑定:本地卷限制节点,网络卷可跨节点 ② 节点亲和与污点容忍:nodeSelector / affinity / tolerations ③ 拓扑分布:topologySpreadConstraints 分散可用区,提升容灾

一个可参考的配置骨架

下面给出一个精简的 StatefulSet 与 Headless Service 示例,用于说明关键字段之间的对应关系。示例参数为教学假设,实际部署需按业务调整。

apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: db
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
    - port: 3306
      name: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: db
spec:
  serviceName: mysql
  replicas: 3
  podManagementPolicy: OrderedReady
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 20Gi

其中 serviceName: mysql 必须与 Headless Service 名称一致,否则 Pod 无法获得稳定的 DNS 记录。volumeClaimTemplates 中的 name: data 与容器 volumeMounts 的 name: data 对应,最终生成 data-mysql-0 等 PVC。

结果观察与判断依据

部署后可以用以下命令确认状态。示例输出为合理假设,用于说明判断方法。

oc get pods -n db
NAME      READY   STATUS    AGE
mysql-0   1/1     Running   3m
mysql-1   1/1     Running   2m
mysql-2   1/1     Running   1m

oc get pvc -n db
NAME           STATUS   VOLUME     CAPACITY   STORAGECLASS
data-mysql-0   Bound    pv-001     20Gi       fast-ssd
data-mysql-1   Bound    pv-002     20Gi       fast-ssd
data-mysql-2   Bound    pv-003     20Gi       fast-ssd

判断要点:Pod 名称是否按序号稳定出现;PVC 是否全部 Bound;DNS 是否可解析,例如在集群内执行 nslookup mysql-0.mysql.db.svc.cluster.local 应返回对应 Pod IP。若 Pod 长期 Pending,优先检查 PVC 是否绑定、存储类是否可用、节点亲和是否冲突;若 DNS 解析失败,检查 Headless Service 的 selector 是否与 Pod 标签匹配、serviceName 是否写对。

StatefulSet 的稳定身份来自“名称 + DNS + PVC”三者绑定。删除 Pod 不会删除 PVC,重建后仍挂回原数据;删除 StatefulSet 默认也不删除 PVC,这是数据安全设计,不是异常。
不要把有状态 Pod 的 IP 写进业务配置。Pod 重建后 IP 会变化,只有 DNS 名称和 PVC 是稳定的。跨节点调度时,务必确认存储卷支持多节点挂载,否则会出现 Pod 无法启动或数据不可访问。

掌握以上机制后,后续在 OpenShift 上部署具体有状态中间件时,就能判断哪些字段决定身份、哪些决定存储、哪些决定调度位置,从而在故障排查和容量规划中快速定位问题。