StatefulSet
StatefulSet 如何具体解决有状态应用的挑战
StatefulSet 的四大核心机制
StatefulSet 通过一系列精心设计的机制,为有状态应用提供了稳定性和可预测性。
1. 稳定的网络标识
解决的问题:有状态应用(如数据库节点)需要稳定的主机名来相互发现和通信,不能使用随机名称。
StatefulSet 的实现:
- 固定的 Pod 名称:Pod 名称遵循固定模式:
<statefulset-name>-<ordinal-index>。- 例如:
redis-cluster-0,redis-cluster-1,redis-cluster-2
- 例如:
- 稳定的 DNS 记录:每个 Pod 都会自动获得一个唯一的、稳定的 DNS 记录:
- 格式:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local - 例子:
redis-cluster-0.redis-service.default.svc.cluster.local
- 格式:
应对场景:
- 在 Redis 集群中,
redis-cluster-0可以告诉redis-cluster-1:“我的地址是redis-cluster-0.redis-service",这个地址在 Pod 的一生中都不会改变,即使它被重新调度到其他节点。
2. 有序的部署与管理
解决的问题:像 Zookeeper、Etcd 这样的集群化应用,节点需要按顺序启动和加入集群,主从数据库也需要先启动主节点。
StatefulSet 的实现:
- 有序部署:当创建 StatefulSet 时,Pod 严格按照索引顺序(0, 1, 2…)依次创建。必须等
Pod-0完全就绪(Ready)后,才会创建Pod-1。 - 有序扩缩容:
- 扩容:按顺序创建新 Pod(如从 3 个扩展到 5 个,会先创建
pod-3,再pod-4)。 - 缩容:按逆序终止 Pod(从
pod-4开始,然后是pod-3)。
- 扩容:按顺序创建新 Pod(如从 3 个扩展到 5 个,会先创建
- 有序滚动更新:同样遵循逆序策略,确保在更新过程中大部分节点保持可用。
应对场景:
- 部署 MySQL 主从集群时,StatefulSet 会确保
mysql-0(主节点)先启动并初始化完成,然后才启动mysql-1(从节点),从节点在启动时就能正确连接到主节点进行数据同步。
3. 稳定的持久化存储
这是 StatefulSet 最核心的特性!
解决的问题:有状态应用的数据必须持久化,并且当 Pod 发生故障或被调度到新节点时,必须能够重新挂载到它自己的那部分数据。
StatefulSet 的实现:
- Volume Claim Template:在 StatefulSet 的 YAML 中,你可以定义一个
volumeClaimTemplate(存储卷申请模板)。 - 专属的 PVC:StatefulSet 会为每个 Pod 实例根据这个模板创建一个独立的、专用的 PersistentVolumeClaim (PVC)。
mysql-0->pvc-name-mysql-0mysql-1->pvc-name-mysql-1mysql-2->pvc-name-mysql-2
工作流程:
- 当你创建名为
mysql、副本数为 3 的 StatefulSet 时,K8s 会:- 创建 Pod
mysql-0,并同时创建 PVCdata-mysql-0,然后将它们绑定。 - 等
mysql-0就绪后,创建 Podmysql-1和 PVCdata-mysql-1,然后绑定。 - 以此类推。
- 创建 Pod
- 如果节点故障导致
mysql-1被删除,K8s 调度器会在其他健康节点上重新创建一个同名的 Podmysql-1。 - 这个新 Pod
mysql-1会自动挂载到之前为它创建的、存有它专属数据的 PVCdata-mysql-1上。 - 这样,Pod 虽然"漂移"了,但数据依然跟随,应用可以无缝恢复。
应对场景:
- 对于数据库,每个 Pod 都有自己独立的数据目录。
mysql-0的数据永远不会和mysql-1的数据混淆。这为数据分片(Sharding)和主从复制提供了基础。
4. 稳定的启动顺序与唯一身份
解决的问题:应用启动脚本或配置可能需要知道当前实例的索引号(如,判断自己是否是第一个节点,从而决定是否要初始化集群)。
StatefulSet 的实现:
- Pod 的序号(
0, 1, 2...)就是其稳定的、唯一的身份标识。 - 这个序号可以通过环境变量注入到 Pod 的容器中,供应用程序使用。
总结
StatefulSet 通过「稳定的网络标识 + 有序的部署管理 + 稳定的专属存储」这三驾马车,将有状态应用在动态的、易变的容器环境中"锚定”,使其获得了类似物理机或虚拟机的稳定运行特性。
它并没有让有状态应用本身变得简单,而是为这些应用的复杂需求(如集群化、数据持久化)提供了一个可以在 K8s 上稳定运行的框架和平台。对于运维人员来说,这意味着可以用声明式的方式像管理无状态应用一样,去管理数据库、消息队列等复杂的有状态服务。