StatefulSet

StatefulSet(有状态集,缩写为 sts)常用于部署有状态的且需要有序启动的应用程序,比如在进行Spring Cloud 项目容器化时,Eureka 的部署是比较适合用 StatefulSet 部署的,可以给每个 Eureka 实例创建一个唯一且固定的标识符,并且每个 Eureka 实例无须配置多余的 Service,其余 Spring Boot 应用可以直接通过 Eureka 的 Headless Service 进行注册。

简介

在 Kubernetes 系统中,Pod 的管理对象 RC、Deployment、DaemonSet 和 Job 都面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如 MySQL 集群、MongoDB 集群、Akka 集群、ZooKeeper 集群等,这些应用集群有 4 个共同点:

  • 每个节点都有固定的身份 ID,通过这个 ID,集群中的成员可以相互发现并通信。

  • 集群的规模是比较固定的,集群规模不能随意变动。

  • 集群中的每个节点都是有状态的,通常会持久化数据到永久存储中。

  • 如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。

如果通过 RC 或 Deployment 控制 Pod 副本数量来实现上述有状态的集群,就会发现第 1 点是无法满足的,因为 Pod 的名称是随机产生的,Pod 的 IP 地址也是在运行期才确定且可能有变动的,事先无法为每个 Pod 都确定唯一不变的 ID

另外,为了能够在其他节点上恢复某个失败的节点,这种集群中的 Pod 需要挂接某种共享存储

为了解决这个问题,Kubernetes 从 1.4 版本开始引入了 PetSet 这个新的资源对象,并且在 1.5 版本时更名为 StatefulSet,StatefulSet 从本质上来说,可以看作 Deployment/RC 的一个特殊变种,它有如下特性:

  • StatefulSet 里的每个 Pod 都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设StatefulSet 的名称为 kafka,那么第 1 个 Pod 叫 kafka-0,第 2 个叫 kafka-1,以此类推。

  • StatefulSet 控制的 Pod 副本的启停顺序是受控的,操作第 n 个 Pod 时,前 n-1 个 Pod 已经是运行且准备好的状态。

  • StatefulSet 里的 Pod 采用稳定的持久化存储卷,通过 PV 或 PVC 来实现,删除 Pod 时默认不会删除与 StatefulSet 相关的存储卷(为了保证数据的安全)

注意

删除一个 StatefulSet 时,不保证对 Pod 的终止!

要在 StatefulSet 中实现 Pod 的有序和正常终止,可以在删除之前将 StatefulSet 的副本缩减为 0。

Headless Service

StatefulSet 除了要与 PV 卷捆绑使用以存储 Pod 的状态数据,还要与 Headless Service 配合使用,即在每个 StatefulSet 定义中都要声明它属于哪个 Headless Service

Headless Service 与普通 Service 的关键区别在于,它没有 Cluster IP,如果解析 Headless Service 的 DNS 域名,则返回的是该 Service 对应的全部 Pod 的 Endpoint 列表。Headless Service 使用 Endpoint 进行互相通信。

StatefulSet 在 Headless Service 的基础上又为 StatefulSet 控制的每个 Pod 实例都创建了一个 DNS 域名,这个域名的格式为:${podName}.${serviceName}.${namespace}.svc.cluster.local

其中:

  • podName=${statefulSetName}-number

    • statefulSetName 是 StatefulSet 的名称

    • number 是一个整数,表示 Pod 的序号,以 0 作为起始值

  • serviceName 是 Headless Service 的名字,创建 StatefulSet 时必须指定 Headless Service 名称

  • namespace 是服务所在的命名空间。

    如果访问的 Pod 位于同一命名空间下,可以省略不写

  • cluster.local 为 Cluster Domain(集群域)

假如公司某个项目需要在 Kubernetes 中部署一个主从模式的 Redis,此时使用 StatefulSet 部署就极为合适,因为 StatefulSet 启动时,只有当前一个容器完全启动时,后一个容器才会被调度,并且每个容器的标识符是固定的,那么就可以通过标识符来断定当前 Pod 的角色

比如用一个名为 redis-cluster 的 StatefulSet 部署主从架构的 Redis:

  • 第一个容器启动时,它的标识符为 redis-cluster-0,并且 Pod 内主机名也为 redis-cluster-0。

  • 将主机名为 redis-cluster-0 的容器作为 Redis 的主节点,其余为从节点,那么从节点连接 主节点时就可以使用不会更改的 Master 的 Headless Service。

此时 Redis 从节点(Slave)配置文件如下:

 port 6379
 slaveof redis-cluster-0.redis-ms.public-service.svc.cluster.local 6379
 tcp-backlog 511
 timeout 0
 tcp-keepalive 0
 ...

其中 redis-cluster-0.redis-ms.public-service.svc.cluster.local 是 Redis Master 的 Headless Service。

在同一命名空间下只需要写 redis-cluster-0.redis-ms 即可后面的 public-service.svc.cluster.local 可以省略

最后更新于