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 示例,用于说明关键字段之间的对应关系。示例参数为教学假设,实际部署需按业务调整。
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 是否写对。
掌握以上机制后,后续在 OpenShift 上部署具体有状态中间件时,就能判断哪些字段决定身份、哪些决定存储、哪些决定调度位置,从而在故障排查和容量规划中快速定位问题。