k8s statefulset(K8s -- StatefulSet)

本文目录
- K8s -- StatefulSet
- k8s中statefulset资源类型的深入理解
- k8s中StatefulSet中POD无法在结点失去联系时转移
- k8s StatefulSet && 金丝雀发布
- k8s部署eureka集群
- Kubernetes部署之Eureka迁移
K8s -- StatefulSet
RC、Deployment、DaemonSet都是面向无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的,而StatefulSet是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如MySQL、MongoDB集群等。
StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为 GA 版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有Cluster IP(所以无法负载均衡),解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:
StatefulSet适用于具有以下特点的应用:
接下来看一些示例,演示下上面所说的特性,以加深理解。
通过该配置文件,可看出StatefulSet的三个组成部分:
为什么需要 headless service 无头服务?
在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在statefulset中要求必须是有序 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。而pod IP是变化的,所以是以Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。
为什么需要volumeClaimTemplate?
对于有状态的副本集都会用到持久存储,对于分布式系统来讲,它的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自已的专用存储,但是如果在Deployment中的Pod template里定义的存储卷,是所有副本集共用一个存储卷,数据是相同的,因为是基于模板来的 ,而statefulset中每个Pod都要自已的专有存储卷,所以statefulset的存储卷就不能再用Pod模板来创建了,于是statefulSet使用volumeClaimTemplate,称为卷申请模板,它会为每个Pod生成不同的pvc,并绑定pv, 从而实现各pod有专用存储。这就是为什么要用volumeClaimTemplate的原因。
创建:
看下这三个Pod创建过程:
根据volumeClaimTemplates自动创建的PVC
如果集群中没有StorageClass的动态供应PVC的机制,也可以提前手动创建多个PV、PVC,手动创建的PVC名称必须符合之后创建的StatefulSet命名规则:(volumeClaimTemplates.name)-(pod_name)
Statefulset名称为web 三个Pod副本: web-0,web-1,web-2,volumeClaimTemplates名称为:www,那么自动创建出来的PVC名称为www-web,为每个Pod创建一个PVC。
规律总结:
Statefulset的启停顺序:
Statefulset Pod管理策略:
在v1.7以后,通过允许修改Pod排序策略,同时通过.spec.podManagementPolicy字段确保其身份的唯一性。
StatefulSet使用场景:
在Kubernetes 1.7及更高版本中,通过.spec.updateStrategy字段允许配置或禁用Pod、labels、source request/limits、annotations自动滚动更新功能。
OnDelete :通过.spec.updateStrategy.type 字段设置为OnDelete,StatefulSet控制器不会自动更新StatefulSet中的Pod。用户必须手动删除Pod,以使控制器创建新的Pod。
RollingUpdate :通过.spec.updateStrategy.type 字段设置为RollingUpdate,实现了Pod的自动滚动更新,如果.spec.updateStrategy未指定,则此为默认策略。
StatefulSet控制器将删除并重新创建StatefulSet中的每个Pod。它将以Pod终止(从最大序数到最小序数)的顺序进行,一次更新每个Pod。在更新下一个Pod之前,必须等待这个Pod Running and Ready。
Partitions :通过指定 .spec.updateStrategy.rollingUpdate.partition 来对 RollingUpdate 更新策略进行分区,如果指定了分区,则当 StatefulSet 的 .spec.template 更新时,具有大于或等于分区序数的所有 Pod 将被更新。
具有小于分区的序数的所有 Pod 将不会被更新,即使删除它们也将被重新创建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition 大于其 .spec.replicas,则其 .spec.template 的更新将不会传播到 Pod。在大多数情况下,不需要使用分区。
k8s中statefulset资源类型的深入理解
statefulset是为了解决 有状态服务 的问题,而产生的一种资源类型(deployment和replicaSets是解决无状态服务而设计的)。
这里可能有人说,mysql是有状态服务吧,但我使用的是deploment资源类型,mysql的data数据通过pv的方式存储在第三方文件系统中,也能解决mysql数据存储问题。
是的,如果你的mysql是单节点,使用deployment类型确实可以解决数据存储问题。但是如果你的有状态服务是集群,节点之间有主备或者主从之分,且每个节点分片存储的情况下,deployment则不适用这种场景,因为deployment不会保证pod的有序性,集群通常需要主节点先启动,从节点在加入集群,statefulset则可以保证,其次deployment资源的pod内的pvc是共享存储的,而statefulset下的pod内pvc是不共享存储的,每个pod拥有自己的独立存储空间,正好满足了分片的需求,实现分片的需求的前提是statefulset可以保证pod重新调度后还是能访问到相同的持久化数据。
适用statefulset常用的服务有elasticsearch集群,mogodb集群,redis集群等等。
基于上面的特性,可以发现statefulset由以下几个部分组成:
创建一个headless service,service.yaml文件。
注意:该headless类型service和clusterIp类型的serivice有明显区别,spec下是 clusterIp: None
接着创建一个statefulSet的资源,statefulset.yaml文件。
可以注意到statefulset资源pvc的创建是使用的volumesClaimTemplates,会在俩个pod中分别创建一个资源互相隔离的pvc。
在spec相比deployment多了一个serviceName配置,该值就是对应的headless service。
删除statefulset后,pvc不会自动删除需要我们手动删除
v1.7+支持statefulset的自动更新,通过 spec.updateStrategy 设置更新策略,目前支持俩种策略:
RollingUpdate更新策略还支持Partitions,通过 spec.updateStategy.rollingUpdate.partition 来设置。当partition设置后,只有序号大于或者等于partition的pod会在 spec.templdate 更新的时候滚动更新,而其他pod则保持不变(即便是删除后也是使用以前版本重新创建)。
v1.7+可以通过 .spec.podManagementPolicy 设置pod的管理策略,支持以下俩中方式
使用statefulset资源类型的服务通常有以下几点特点
k8s中StatefulSet中POD无法在结点失去联系时转移
DepAndSta.yaml
将上面的文件保存到master结点上,名字为 DepAndSta.yaml
简单解释一下上面这个文件部署了什么?
上面这个文件,创建了一个 Deployment ,其对应创建了三个nginx的POD
此外,创建了一个StatefulSet,其也对应创建了三个POD
安装下面命令一步一步执行,并观察结果
上面 Node 一栏显示这个Pod位于哪个结点中,我这边配置的只有两个结点node01和node02.
接下来我们去node02结点上关机或者将kubelet进程关闭,我这次采用的是将kubelet进程关闭。
kubelet进程是用来和主节点进行通信的进程,关闭之后,node02的结点就失联了。
稍等一大会 5min之上
我们可以看到途中deployment中在node02结点的都被重新新建了,但是statefulset创建的pod都是terminating的状态,也就是没有被重新启动。
注意点: 假如此时此刻我将node02结点执行 systemctl start kubelet 或者重新开机后, 此时此刻deployment下面的pod处于 Terminating 状态的被删除,statefulset下面的pod在原来位置重新启动。
k8s StatefulSet && 金丝雀发布
Pv被成功绑定。
Pvc被成功创建
说明案例创建成功。
横向扩展:kubectl scale sts myapp --replicas=5
查看扩展结果:kubectl get pvc 或kubectl get pod -o wide
StatefulSet做金丝雀更新
kubectl patch sts myapp -p ’{"spec":{"updateStrategy":{"rollingUpdate":{"partition":4}}}}’
表示从编号大于等于4的pod先更新,小于4的暂时不更新
现在我们更新image得到为ikubernetes/myapp:v2
kubectl set image sts/myapp myapp=ikubernetes/myapp:v2
查看statefulSet的状态:kubectl get sts -o wide
可以看出系统层面已经更改完毕,查看一下pod的层面镜像是否已经更改了。
Kubectl get pod myapp-4 -o yaml
显示镜像已经被更新的信息,
查看其它pod信息未被修改。
如果修改的镜像没有问题,就全部更新,这就是金丝雀发布。
k8s部署eureka集群
对于一般的后端微服务来说,在k8s中同时起多个相同的服务来做负载均衡,只需要简单的修改deployment的replicas,增加pod数量,然后通过对外暴露一个service来代理这些pod。
而对于eureka来说,要实现eureka的高可用,那就不是修改replicas这么方便了。由于部署的多个eureka之间需要将自己注册到彼此,因此要做一些特殊改动。
主要是用到了StatefulSet和headless service这两个k8s对象
StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括
稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
有序收缩,有序删除(即从N-1到0)
StatefulSet中每个Pod的DNS格式为
Headless Service 和普通service的一个显著的区别是,Headless Service的对应的每一个Endpoints,即每一个Pod,都会有对应的DNS域名
例如:我们可以用过这种域名来访问某个具体的pod:
在实际使用中,将service的clusterIP设置成None,就表明这个service是一个Headless Service。
通过 StatefulSet,我们得到了一些列pod,每个pod的name为statefulSetName-{0..N-1},
加入我们创建了一个名称叫eureka的StatefulSet,并且设置replicas =3,那么部署到k8s后,k8s会为我们生成三个名称依次为eureka-0,eureka-1,eureka-2的pod。
通过Headless Service,我们可以通过pod名称来访问某个pod,
例如,我们在namespace=test的命名空间下创建了一个名称为register-server的service,并且关联了之前StatefulSet创建的pod,那么我们可以在集群内任意地方
通过eureka-0.register-server.test.svc.cluster.local这个域名访问到eureka-0这个pod。
有了前面的基础,现在部署eureka集群的方式就逐渐清晰了。
首先明确部署eureka的关键点:需要让每个eureka注册到另外的eureka上。
也就是eureka.client.serviceUrl.defaultZone这个配置,是一组eureka的地址。
通过StatefulSet,我们可以明确知道生成的每个eureka的名称,
通过Headless Service,我们又可以访问到每个eureka,所以eureka.client.serviceUrl.defaultZone的值就是
有个这个配置,那么我们部署StatefulSet,和Headless Service
那么我们能基本能得到一个可用的eureka集群
除了会有以下问题:
红框中的可用副本(available-replicas)会出现在不可用unavailable-replicas中
原因是我们默认是通过ip的方式来注册eureka(eureka.instance.prefer-ip-address配置默认为true),但是eureka的注册地址又是域名的形式,两者不一致。
要解决这个问题,还需做一些额外的配置。
1.在application.yaml中,将eureka.instance.prefer-ip-address设置成false。
2.StatefulSet.yaml中,增加环境变量配置,将pod的名称绑定到环境变量
3.在application.yaml中指定eureka的 hostname,其中MY_POD_NAME取到了第二部中绑定的当前pod名称
eureka:
instance:
hostname: ${MY_POD_NAME}.register-server
如上配置后,便可以得到一个eureka集群。
后面是一些配置文件:
整体文件配置:
service.yaml
StatefulSet.yaml
application.yml
bootstrap.yml
将一般的微服务注册到eureka集群中,可以通过eureka的service来访问eureka,即:将eureka.client.serviceUrl.defaultZone设置成register-server.test.svc.cluster.local,使用了k8s的service负载均衡,将服务注册到任意一个活着的eureka上,然后eureka集群内部会做同步,最终注册到eureka集群内部所有eureka上
Kubernetes部署之Eureka迁移
背景: 由于项目需要,准备将之前的项目搬迁到kubernetes上,项目中使用了一些列组件,其中就包含Eureka注册中心。
Eureka架构一般为:gateway网关+注册中心server+服务编排。
所有服务包括gateway都在注册中心注册,采用Eureka的负载均衡来调用服务。Eureka简单示意图如下:
对于每一个StatefulSets中的每个pod其域名如下:
$(statefulset name)-$(ordinal).$(service name).$(namespace).svc.cluster.local
问题一:为什么要使用StatefulSet?
因为我们部署服务时需要提前知道注册中心的地址,由于Kubernetes物理IP不固定的特性(Pod重启机制),我们没办法知道每一台服务节点的位置,所以需要StatefulSet,创建时是按照{0-N-1}的序号创建的,也就是其域名是确定的。
问题二:为什么不能使用集群IP?
首先集群IP需要提前指定(默认k8s自行分配),但不推荐这样做,一是不利于IP资源的利用(只有有一个固定IP段可使用),二是因为我们尝试过使用ClusterIP,发现不是很稳定,原因后续再定位。
对于具体的生产者(也可能是消费者)服务来说,它们工作的原理是在启动时向EurekaServer端注册信息,有两种方法,一种是域名注册,一种是IP地址注册,因为K8s的Pod可变性,无法使用稳定的域名(另外我们测试过通过域名注册会报Unknownhostexception无法解析主机),因此采用IP地址注册。可以看出它们不需要提前暴露自己的域名和IP,pod变化时,会重新注册IP地址,因此无需部署service。步骤如下:

更多文章:
form表单制作(为什么制作的form表单会在网页显示中多出一行)
2026年10月11日 09:10
teammate(teammate,company,partner)
2026年10月11日 06:10
javascript arraybuffer(javascript可以把base64编码转换成二进制代码吗求示例代码!)
2026年10月11日 04:00
text函数公式(excel中round和text函数的区别是什么)
2026年10月11日 03:50
google chrome打不开(chrome浏览器打不开怎么回事 浏览器打不开的处理方法)
2026年10月11日 02:00
websocket整合springboot(Springboot整合Websocket遇到的坑)
2026年10月11日 01:40
drawerlayout(android 怎样让drawerlayout设置的侧滑菜单的内容充满屏幕)
2026年10月10日 19:20
xor四位数怎么运算(单片机怎样用C语言实现4个数字间的异或)
2026年10月10日 17:50




