StatefulSet
StatefulSet
目录
[toc]
前言
什么是有状态服务和无状态服务
前面我们学习了 Deployment 和 ReplicaSet 两种资源对象的使用,在实际使用的过程中,Deployment 并不能编排所有类型的应用,对无状态服务编排是非常容易的,但是对于有状态服务就无能为力了。我们需要先明白一个概念:什么是有状态服务,什么是无状态服务。
无状态服务(Stateless Service)
:该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的。比如前面我们讲解的 WordPress 实例,我们是不是可以同时启动多个实例,但是我们访问任意一个实例得到的结果都是一样的吧?因为他唯一需要持久化的数据是存储在MySQL数据库中的,所以我们可以说 WordPress 这个应用是无状态服务,但是 MySQL 数据库就不是了,因为他需要把数据持久化到本地。有状态服务(Stateful Service)
:就和上面的概念是对立的了,该服务运行的实例需要在本地存储持久化数据。比如上面的 MySQL 数据库,你现在运行在节点 A,那么他的数据就存储在节点 A 上面的,如果这个时候你把该服务迁移到节点 B 去的话,那么就没有之前的数据了,因为他需要去对应的数据目录里面恢复数据,而此时没有任何数据。
⚠️ 注意:
statefulSet和Deployment最大的区别就是:deployment里的pod副本是用的相同的存储,而statsfulet中的pod副本有自己的存储,这是他们最大的区别。
现在对有状态
和无状态
有一定的认识了吧。比如我们常见的 WEB 应用,是通过 Session 来保持用户的登录状态的,如果我们将 Session 持久化到节点上,那么该应用就是一个有状态的服务了,因为我现在登录进来你把我的 Session 持久化到节点 A 上了,下次我登录的时候可能会将请求路由到节点 B 上去了,但是节点 B 上根本就没有我当前的 Session 数据,就会被认为是未登录状态了,这样就导致我前后两次请求得到的结果不一致了。所以一般为了横向扩展,我们都会把这类 WEB 应用改成无状态的服务,怎么改?将 Session 数据存入一个公共的地方,比如 Redis 里面,是不是就可以了。对于一些客户端请求 API 的情况,我们就不使用 Session 来保持用户状态,改成用Token也是可以的。
无状态服务利用我们前面的 Deployment 可以很好的进行编排。对应有状态服务,需要考虑的细节就要多很多了,容器化应用程序最困难的任务之一,就是设计有状态分布式组件的部署体系结构。由于无状态组件没有预定义的启动顺序、集群要求、点对点 TCP 连接、唯一的网络标识符、正常的启动和终止要求等,因此可以很容易地进行容器化。诸如**数据库(例如mysql主从),大数据分析系统,分布式 key/value 存储(etcd集群)、消息中间件(zookeeper集群)**需要有复杂的分布式体系结构,都可能会用到上述功能。
StatefulSet特点
为此,Kubernetes 引入了 StatefulSet
这种资源对象来支持这种复杂的需求。StatefulSet
类似于 ReplicaSet
,部署有状态应用,解决Pod独立生命周期,但是它可以处理 Pod 的启动顺序,为保留每个 Pod 的状态设置唯一标识,具有以下几个功能特性:
- 稳定的、唯一的网络标识符 (例如:etcd 配置文件,节点地址发生变化,将无法使用)
- 稳定的、持久化的存储
- 有序的、优雅的部署和缩放(例如:mysql 主从关系,先启动主,再启动从)
- 有序的、优雅的删除和终止
- 有序的、自动滚动更新
说明:
• 稳定的网络ID使用Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份。并且添加serviceName:“nginx”
字段指定StatefulSet控制器要使用这个Headless Service。 DNS解析名称:<statefulsetName-index>.<service-name>.<namespace-name>.svc.cluster.local
• 稳定的存储StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。
StatefulSet与Deployment区别:有身份的身份三要素: • 域名 • 主机名 • 存储(PVC)
注意:有状态应用pod的sts编号1、与pvc对应关系 2、标记启动顺序 3、网络身份、主机名
- 有状态部署中的pod都是有dns记录的
每一个pod都是有一个dns名称的;
dns名称有自己的专属格式:<statefulsetName-index>.<service-name>.<namespace-name>.svc.cluster.local
有状态部署应用是需要去依赖coredns的; 这个域名是可以ping通的,k8s集群任何pod都可以去访问这个域名的; 那个dns名称是固定的,pod重建了,那个名称也不会改变;
普通svc的pod也会有dns记录的吗?-->不会有的(也没必要有)。这个只是stateful应用svc专有的特性。
1、Headless Service
在我们学习 StatefulSet 对象之前,我们还必须了解一个新的概念:Headless Service
。
Service
其实在之前我们和大家提到过,Service 是应用服务的抽象,通过 Labels 为应用提供负载均衡和服务发现,每个 Service 都会自动分配一个 cluster IP 和 DNS 名,在集群内部我们可以通过该地址或者通过 FDQN 的形式来访问服务。
在集群中我们是通过部署 CoreDNS组件来为 Kubernetes 集群提供 DNS 服务的,CoreDNS 同样通过 watch 到APIServer 中的新服务来为每个服务创建一组 DNS 记录。如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。
例如,如果你在 Kubernetes 命名空间 my-ns 中有一个名为 my-service 的 Service 服务,则控制平面和CoreDNS 会其创建一个 my-service.my-ns 的 DNS 记录(全路径为 my-service.my-ns.svc.cluster.local
), my-ns 命名空间中的 Pod 应该能够通过名称 my-service 来找到服务(当然 my-service.my-ns 也可以工作)。而其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns,这些名称将解析为为 Service 服务分配的 cluster IP,然后该 Service 就会将请求代理到其关联的Endpoints 列表中的某个Pod 上去了,所以 Service 这里的作用有点类似于 Nginx 代理。
对于 DNS 这种方式实际上也有两种情况:
第一种就是普通的 Service,我们访问
“mysvc.mynamespace.svc.cluster.local”
的时候是通过集群中的 DNS 服务解析到的 mysvc 这个 Service 的 cluster IP 的;第二种情况就是
Headless Service
,对于这种情况,我们访问“mysvc.mynamespace.svc.cluster.local”
的时候是直接解析到的 mysvc 代理的某一个具体的 Pod 的 IP 地址,中间少了 cluster IP 的转发,这就是二者的最大区别。Headless Service 不需要分配一个 VIP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由,而是可以直接以 DNS 的记录方式解析到后面的 Pod 的 IP 地址。
比如,一个 Deployment 有 3 个 Pod,那么我就可以定义一个 Service,有如下两种方式来访问这个 Service:
- cluster IP 的方式。比如:当我访问 10.109.169.155 这个 Service 的 IP 地址时,10.109.169.155 其实就是一个 VIP,它会把请求转发到该 Service 所代理的 Endpoints 列表中的某一个 Pod 上。具体原理我们会在后面的 Service 章节中和大家深入了解。
- Service 的 DNS 方式。比如我们访问
“mysvc.mynamespace.svc.cluster.local”
这条 DNS 记录,就可以访问到 mynamespace 这个命名空间下面名为 mysvc 的 Service 所代理的某一个 Pod。
注意:这其实是由cordns来进行了一个解析的。
比如我们定义一个如下的 Headless Service
:
# headless-svc.yamlapiVersion:v1kind:Servicemetadata:name:nginxnamespace:defaultlabels:app:nginxspec:ports:- name:httpport:80clusterIP:None#仅仅这里改成None就好selector:app:nginx
实际上 Headless Service
在定义上和普通的 Service 几乎一致,只是他的 clusterIP=None
,所以,这个 Service 被创建后并不会被分配一个 cluster IP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。而且还有一个非常重要的特性,对于 Headless Service
所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
这个 DNS 记录正是 Kubernetes 集群为 Pod 分配的一个唯一标识,只要我们知道 Pod 的名字,以及它对应的 Service 名字,就可以组装出这样一条 DNS 记录访问到 Pod 的 IP 地址,这个能力是非常重要的。
接下来我们就来看下 StatefulSet 资源对象是如何结合 Headless Service 提供服务的。
🚩
常规的 service 服务和无头服务的区别
service:一组 Pod 访问策略,提供 cluster-IP 群集之间通讯,还提供负载均衡和服务发现;
Headless service 无头服务,不需要 cluster-IP,直接绑定具体的 Pod 的 IP,无头服务经常用于 statefulset 的有状态部署;
创建无头服务的 service 资源和 dns 资源,由于有状态服务的 IP 地址是动态的,所以使用无头服务的时候要绑定 dns 服务。
2、StatefulSet案例
💘 实战:StatefulSet控制器部署有状态服务-2022.12.18(成功测试)
- 实验环境
1、win10,vmwrokstation虚机;2、k8s集群:3台centos7.61810虚机,2个master节点,1个node节点k8sversion:v1.20CONTAINER-RUNTIME:containerd:v1.6.10
- 实验软件(无)
在开始之前,我们先准备两个 1G 的存储卷(PV),如果你使用的是 Kind 搭建的集群,则可以忽略该步骤 搭建的集群,则可以忽略该步骤,因为Kind 搭建的集群会自动提供一个 local-path-provisioner组件,该组件会自动生成 PV。
[root@docker ~]#kubectl get scNAMEPROVISIONERRECLAIMPOLICYVOLUMEBINDINGMODEALLOWVOLUMEEXPANSIONAGEstandard(default) rancher.io/local-path Delete WaitForFirstConsumer false 12d[root@docker ~]#kubectl get po -nlocal-path-storageNAMEREADYSTATUSRESTARTSAGElocal-path-provisioner-684f458cdd-x97z81/1Running012d
- 在后面的课程中我们也会和大家详细讲解 PV 和 PVC 的使用方法的,如果不是 Kind 搭建的集群可以先手动创建下面的PV 对象:
#pv.yamlapiVersion:v1kind:PersistentVolumemetadata:name:pv001#pv是全局的,没有命名空间一说;spec:capacity:storage:1GiaccessModes:- ReadWriteOncehostPath:path:/tmp/pv001#注意,这里不是使用nfs,而是直接使用hostPath的;---apiVersion:v1kind:PersistentVolumemetadata:name:pv002spec:capacity:storage:1GiaccessModes:- ReadWriteOncehostPath:path:/tmp/pv002
- 然后直接创建 PV 即可:
[root@master1 ~]#kubectl apply -f pv.yamlpersistentvolume/pv001createdpersistentvolume/pv002created[root@master1 ~]#kubectl get pvNAMECAPACITYACCESSMODESRECLAIMPOLICYSTATUSCLAIMSTORAGECLASSREASONAGEpv0011GiRWORetainAvailable6spv0021GiRWORetainAvailable6s
可以看到成功创建了两个 PV 对象,状态是:Available
。
1.特性
- 然后接下来声明一个如下所示的 StatefulSet 资源清单:
#nginx-sts.yamlapiVersion:v1kind:Servicemetadata:name:nginxnamespace:defaultlabels:app:nginxspec:ports:- name:httpport:80clusterIP:Noneselector:app:nginx---apiVersion:apps/v1kind:StatefulSetmetadata:name:webnamespace:defaultspec:serviceName:"nginx"#指定一个headless servicereplicas:2selector:matchLabels:app:nginxtemplate:#Pod模板metadata:labels:app:nginx#一定要和上面selector保持一致spec:containers:- name:nginximage:nginx:1.7.9ports:- containerPort:80#这个端口只是声明,不会起到访问的作用name:webvolumeMounts:- name:wwwmountPath:/usr/share/nginx/htmlvolumeClaimTemplates:#pvc模板,实际工作中是会使用sc,存储类的- metadata:name:www#要和volumeMounts的名称一致spec:accessModes:["ReadWriteOnce"]resources:requests:storage:1Gi
从上面的资源清单中可以看出和我们前面的 Deployment 基本上也是一致的,也是通过声明的 Pod 模板来创建 Pod 的。另外上面资源清单中和 volumeMounts
进行关联的不是 volumes
而是一个新的属性:volumeClaimTemplates
,该属性会自动创建一个 PVC 对象,其实这里就是一个 PVC 的模板,和 Pod 模板类似,**PVC 被创建后会自动去关联当前系统中和他合适的 PV 进行绑定。**除此之外,还多了一个 serviceName:"nginx"
的字段,serviceName
就是管理当前 StatefulSet
的服务名称,该服务必须在 StatefulSet 之前存在,并且负责该集合的网络标识,Pod 会遵循以下格式获取 DNS/主机名:pod-specific-string.serviceName.default.svc.cluster.local
,其中 pod-specific-string
由 StatefulSet 控制器管理。
StatefulSet
的拓扑结构和其他用于部署的资源对象其实比较类似,比较大的区别在于**StatefulSet**
引入了 PV 和 PVC 对象来持久存储服务产生的状态,这样所有的服务虽然可以被杀掉或者重启,但是其中的数据由于 PV 的原因不会丢失。
- 创建对应的 StatefulSet 对象:
[root@master1 ~]#kubectl apply -f sts.yamlstatefulset.apps/webcreated[root@master1 ~]#kubectl get pvcNAMESTATUSVOLUMECAPACITYACCESSMODESSTORAGECLASSAGEwww-web-0Boundpv0011GiRWO3m15swww-web-1Boundpv0021GiRWO3m13s[root@master1 ~]#kubectl get svcNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEkubernetesClusterIP10.96.0.1<none>443/TCP14d110snginxClusterIPNone<none>80/TCP76s
- 可以看到这里通过 Volume 模板自动生成了两个 PVC 对象,也自动和 PV 进行了绑定。这个时候我们可以快速通过一个
--watch
参数来查看 Pod 的创建过程:
[root@master1 ~]#kubectl get pods -l app=nginx--watchNAMEREADYSTATUSRESTARTSAGEweb-00/1Pending00sweb-00/1Pending00sweb-00/1Pending01sweb-00/1ContainerCreating01sweb-01/1Running03sweb-10/1Pending00sweb-10/1Pending00sweb-10/1Pending01sweb-10/1ContainerCreating01sweb-11/1Running03s[root@master1 ~]#kubectl get poNAMEREADYSTATUSRESTARTSAGEweb-01/1Running041sweb-11/1Running038s
我们仔细观察整个过程出现了两个 Pod:web-0
和 web-1
,而且这两个 Pod 是按照顺序进行创建的,web-0
启动起来后 web-1
才开始创建。 如同上面 StatefulSet 概念中所提到的,StatefulSet 中的 Pod 拥有一个具有稳定的、独一无二的身份标志。这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。Pod 的名称的形式为<statefulset name>-<ordinal index>
。我们这里的对象拥有两个副本,所以它创建了两个 Pod 名称分别为:web-0 和 web-1。
- 我们可以使用
kubectl exec
命令进入到容器中查看它们的 hostname:
[root@master1 ~]#kubectl exec web-0 -- hostnameweb-0[root@master1 ~]#kubectl exec web-1 -- hostnameweb-1
StatefulSet 中 Pod 副本的创建会按照序列号升升序序处理,副本的更新和删除会按照序列号 降序处理。
- 可以看到,这两个 Pod 的 hostname 与 Pod 名字是一致的,都被分配了对应的编号。我们随意查看一个 Pod 的描述信息:
[root@master1 ~]#kubectl describe pod web-0Name:web-0Namespace:defaultPriority:0ServiceAccount:defaultNode:node1/172.29.9.62StartTime:Mon,19Dec202206:45:37+0800Labels:app=nginxcontroller-revision-hash=web-7466694c86statefulset.kubernetes.io/pod-name=web-0Annotations:<none>Status:RunningIP:10.244.1.23IPs:IP:10.244.1.23ControlledBy:StatefulSet/web……
我们可以看到 Controlled By:StatefulSet/web ,证明我们的 Pod 是直接受到 StatefulSet 控制器管理的。
- 我们来访问下pod
[root@master1 ~]#kubectl get po -owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESweb-01/1Running012m10.244.1.23node1<none><none>web-11/1Running012m10.244.2.26node2<none><none>[root@master1 ~]#curl 10.244.1.23<html><head><title>403 Forbidden</title></head><body bgcolor="white"><center><h1>403 Forbidden</h1></center><hr><center>nginx/1.7.9</center></body></html>[root@master1 ~]#curl 10.244.2.26<html><head><title>403 Forbidden</title></head><body bgcolor="white"><center><h1>403 Forbidden</h1></center><hr><center>nginx/1.7.9</center></body></html>
会发现出现403报错,这是为什么呢?
由于我们这里用**volumeClaimTemplates**
声明的模板是挂载点的方式,并不是 volume,所有实际上相当于把 PV 的存储挂载到容器中,所以会覆盖掉容器中的数据,在容器启动完成后我们可以手动在 PV 的存储里面新建 index.html 文件来保证容器的正常访问,当然也可以进入到容器中去创建,这样更加方便:
[root@master1 ~]#fori in01;dokubectlexecweb-$i --sh-c'echo hello $(hostname) >/usr/share/nginx/html/index.html';done[root@master1 ~]#kubectl get po -owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESweb-01/1Running015m10.244.1.23node1<none><none>web-11/1Running015m10.244.2.26node2<none><none>[root@master1 ~]#curl 10.244.1.23helloweb-0[root@master1 ~]#curl 10.244.2.26helloweb-1
- 现在我们创建一个 busybox(该镜像中有一系列的工具)的容器,在容器中用 DNS 的方式来访问一下这个
Headless Service
,由于我们这里只是单纯的为了测试,所以没必要写资源清单文件来声明,用kubectl run
命令启动一个测试的容器即可:
➜~kubectlrun-it--imagebusybox:1.28.3test--restart=Never--rm/bin/shIfyoudon't see a command prompt,try pressing enter./ #
busybox
最新版本的镜像有 BUG,会出现nslookup
提示无法解析的问题,我们这里使用老一点的镜像版本1.28.3
即可。
如果对 kubectl run
命令的使用参数不清楚,我们可以使用 kubectl run --help
命令查看可使用的参数。我们这里使用 kubectl run
命令启动了一个以 busybox 为镜像的 Pod,--rm
参数意味着我们退出 Pod 后就会被删除,和之前的 docker run
命令用法基本一致。
- 现在我们在这个 Pod 容器里面可以使用
nslookup
命令来尝试解析下上面我们创建的Headless Service
:
[root@master1 ~]#kubectl run -it --image busybox:1.28.3 test --restart=Never--rm/bin/shIfyoudon't see a command prompt,try pressing enter./ # nslookup nginxServer:10.96.0.10Address 1:10.96.0.10 kube-dns.kube-system.svc.cluster.localName:nginxAddress 1:10.244.2.26 web-1.nginx.default.svc.cluster.localAddress 2:10.244.1.23 web-0.nginx.default.svc.cluster.local/ # ping nginxPING nginx (10.244.2.26):56 data bytes64 bytes from 10.244.2.26:seq=0 ttl=64 time=0.105 ms64 bytes from 10.244.2.26:seq=1 ttl=64 time=0.064 ms^C--- nginx ping statistics ---2 packets transmitted,2 packets received,0% packet lossround-trip min/avg/max =0.064/0.084/0.105 ms/ # ping nginxPING nginx (10.244.1.23):56 data bytes64 bytes from 10.244.1.23:seq=0 ttl=62 time=0.824 ms64 bytes from 10.244.1.23:seq=1 ttl=62 time=0.535 ms64 bytes from 10.244.1.23:seq=2 ttl=62 time=0.460 ms^C--- nginx ping statistics ---3 packets transmitted,3 packets received,0% packet lossround-trip min/avg/max =0.460/0.606/0.824 ms/ #
我们直接解析 Headless Service
的名称,可以看到得到的是两个 Pod 的解析记录。但实际上如果我们通过nginx
这个 DNS 去访问我们的服务的话,并不会随机或者轮询背后的两个 Pod,而是访问到一个固定的 Pod,所以不能代替普通的 Service。
- 如果分别解析对应的 Pod 呢?
/# nslookup web-0.nginxServer:10.96.0.10Address1:10.96.0.10kube-dns.kube-system.svc.cluster.localName:web-0.nginxAddress1:10.244.1.23web-0.nginx.default.svc.cluster.local/# nslookup web-1.nginxServer:10.96.0.10Address1:10.96.0.10kube-dns.kube-system.svc.cluster.localName:web-1.nginxAddress1:10.244.2.26web-1.nginx.default.svc.cluster.local/##注意这里也是可以简写成:pingweb-0.nginx.default或者web-0.nginx的(如果命名空间是默认的话,可以省略命名空间);
可以看到解析 web-0.nginx
的时候解析到了 web-0
这个 Pod 的 IP,web-1.nginx
解析到了 web-1
这个 Pod 的 IP,而且这个 DNS 地址还是稳定的,因为 Pod 名称就是固定的。
- 比如我们这个时候去删掉
web-0
和web-1
这两个 Pod:
[root@master1 ~]#kubectl delete pod -l app=nginxpod"web-0"deletedpod"web-1"deleted
删除完成后才看 Pod 状态:
[root@master1 ~]#kubectl get po -l app=nginxNAMEREADYSTATUSRESTARTSAGEweb-01/1Running054sweb-11/1Running051s
可以看到 StatefulSet 控制器仍然会安装顺序创建出两个 Pod 副本出来,而且 Pod 的唯一标识依然没变,所以这两个 Pod 的网络标识还是固定的,我们依然可以通过web-0.nginx
去访问到web-0
这个 Pod,虽然 Pod 已经重建了,对应 Pod IP 已经变化了,但是访问这个 Pod 的地址依然没变:
/# nslookup web-0.nginxServer:10.96.0.10Address1:10.96.0.10kube-dns.kube-system.svc.cluster.localName:web-0.nginxAddress1:10.244.1.24web-0.nginx.default.svc.cluster.local/# nslookup web-1.nginxServer:10.96.0.10Address1:10.96.0.10kube-dns.kube-system.svc.cluster.localName:web-1.nginxAddress1:10.244.2.29web-1.nginx.default.svc.cluster.local/#
并且他们依然还是关联的之前的 PVC,数据并不会丢失:
[root@master1 ~]#kubectl get po -l app=nginx-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESweb-01/1Running02m27s10.244.1.24node1<none><none>web-11/1Running02m24s10.244.2.29node2<none><none>[root@master1 ~]#curl 10.244.1.24helloweb-0[root@master1 ~]#curl 10.244.2.29helloweb-1
通过 Headless Service
,StatefulSet 就保证了 Pod 网络标识的唯一稳定性,由于 Pod IP 并不是固定的,所以我们访问有状态应用
实例的时候,就必须使用 DNS 记录的方式来访问了,所以很多同学偶尔有固定的 Pod IP 的需求,或许可以用这种方式来代替。(一般有pod需要固定ip的场景基本都是有状态服务场景;****)
- 最后我们可以通过删除 StatefulSet 对象来删除所有的 Pod,仔细观察也会发现是按照倒序的方式进行删除的:
[root@docker ~]#kubectl get po -l app=nginx-w#这里是用kind集群跑的demo#创建时是顺序创建的:NAMEREADYSTATUSRESTARTSAGEweb-00/1Pending00sweb-00/1Pending06sweb-00/1ContainerCreating06sweb-01/1Running07sweb-10/1Pending00sweb-10/1Pending05sweb-10/1ContainerCreating06sweb-11/1Running035sweb-20/1Pending00sweb-20/1Pending05sweb-20/1ContainerCreating06sweb-21/1Running07sweb-30/1Pending00sweb-30/1Pending09sweb-30/1ContainerCreating09sweb-31/1Running010sweb-40/1Pending00sweb-40/1Pending05sweb-40/1ContainerCreating06sweb-41/1Running07sweb-50/1Pending00sweb-50/1Pending05sweb-50/1ContainerCreating05sweb-51/1Running06sxxx#注意:这第一眼看上去好像是无序删除的,但仔细看下后面的age时间,可以发现是倒虚删除的。但是还是不符合预期现象。……不知道为啥子哦web-41/1Terminating043sweb-01/1Terminating02m14sweb-11/1Terminating02m7sweb-51/1Terminating035sweb-31/1Terminating054sweb-21/1Terminating064sweb-50/1Terminating037sweb-40/1Terminating045sweb-40/1Terminating045sweb-50/1Terminating037sweb-50/1Terminating037sweb-40/1Terminating045sweb-10/1Terminating02m9sweb-10/1Terminating02m9sweb-10/1Terminating02m9sweb-30/1Terminating056sweb-00/1Terminating02m16sweb-30/1Terminating056sweb-30/1Terminating057sweb-00/1Terminating02m17sweb-00/1Terminating02m17sweb-20/1Terminating066sweb-20/1Terminating066sweb-20/1Terminating066s
实验结束。😘
2.管理策略
***对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的,这些系统仅仅要求唯一性和身份标志。***为了解决这个问题,我们只需要在声明 StatefulSet 的时候重新设置 spec.podManagementPolicy
的策略即可。
默认的管理策略是 OrderedReady
,表示让 StatefulSet 控制器遵循上文演示的顺序性保证。除此之外,还可以设置为 Parallel
管理模式,表示让 StatefulSet 控制器并行的终止所有 Pod,在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。
[root@master1 ~]#kubectl explain sts.spec……podManagementPolicy<string>podManagementPolicycontrolshowpodsarecreatedduringinitialscaleup,whenreplacingpodsonnodes,orwhenscalingdown.Thedefaultpolicyis`OrderedReady`,wherepodsarecreatedinincreasingorder(pod-0,thenpod-1,etc) and the controller will wait untileachpodisreadybeforecontinuing.Whenscalingdown,thepodsareremovedintheopposite(完全相反的) order.Thealternative(可供选择的) policyis`Parallel(并行的)`whichwillcreatepodsinparalleltomatchthedesiredscalewithoutwaiting,andonscaledownwilldeleteallpodsatonce.……
💘 实战:StatefulSet控制器部署有状态服务-管理策略-2022.12.20(成功测试)
- 实验环境
1、win10,vmwrokstation虚机;2、k8s集群:3台centos7.61810虚机,2个master节点,1个node节点k8sversion:v1.20CONTAINER-RUNTIME:containerd:v1.6.10
实验软件(无)
这里按照以上方法来测试一下效果
我这里为了测试方便,直接使用kind集群了哦。
- 创建资源清单文件
当sts控制器的管理策略为OrderedReady
时:
#nginx-sts.yamlapiVersion:v1kind:Servicemetadata:name:nginxnamespace:defaultlabels:app:nginxspec:ports:-name:httpport:80clusterIP:Noneselector:app:nginx---apiVersion:apps/v1kind:StatefulSetmetadata:name:webnamespace:defaultspec:podManagementPolicy:OrderedReady#默认就是OrderedReadyserviceName:"nginx"#指定一个headless servicereplicas:6#为了现象更明显,这里设置为6selector:matchLabels:app:nginxtemplate:#Pod模板metadata:labels:app:nginx#一定要和上面selector保持一致spec:containers:-name:nginximage:nginx:1.7.9ports:-containerPort:80#这个端口只是声明,不会起到访问的作用name:webvolumeMounts:-name:wwwmountPath:/usr/share/nginx/htmlvolumeClaimTemplates:#pvc模板,实际工作中是会使用sc,存储类的-metadata:name:www#要和volumeMounts的名称一致spec:accessModes:["ReadWriteOnce"]resources:requests:storage:1Gi
- 创建pod和删除pod,观察pod的创建和删除顺序
[root@docker ~]#kubectl apply -f nginx-sts.yaml [root@docker ~]#kubectl get po -l app=nginx-w#这里是用kind集群跑的demo#创建时是顺序创建的:NAMEREADYSTATUSRESTARTSAGEweb-00/1Pending00sweb-00/1Pending06sweb-00/1ContainerCreating06sweb-01/1Running07sweb-10/1Pending00sweb-10/1Pending05sweb-10/1ContainerCreating06sweb-11/1Running035sweb-20/1Pending00sweb-20/1Pending05sweb-20/1ContainerCreating06sweb-21/1Running07sweb-30/1Pending00sweb-30/1Pending09sweb-30/1ContainerCreating09sweb-31/1Running010sweb-40/1Pending00sweb-40/1Pending05sweb-40/1ContainerCreating06sweb-41/1Running07sweb-50/1Pending00sweb-50/1Pending05sweb-50/1ContainerCreating05sweb-51/1Running06s[root@docker ~]#kubectl delete -f nginx-sts.yaml [root@docker ~]#kubectl get po -l app=nginx-wxxx#注意:这第一眼看上去好像是无序删除的,但仔细看下后面的age时间,可以发现是倒虚删除的。但是还是不符合预期现象。……不知道为啥子哦web-41/1Terminating043sweb-01/1Terminating02m14sweb-11/1Terminating02m7sweb-51/1Terminating035sweb-31/1Terminating054sweb-21/1Terminating064sweb-50/1Terminating037sweb-40/1Terminating045sweb-40/1Terminating045sweb-50/1Terminating037sweb-50/1Terminating037sweb-40/1Terminating045sweb-10/1Terminating02m9sweb-10/1Terminating02m9sweb-10/1Terminating02m9sweb-30/1Terminating056sweb-00/1Terminating02m16sweb-30/1Terminating056sweb-30/1Terminating057sweb-00/1Terminating02m17sweb-00/1Terminating02m17sweb-20/1Terminating066sweb-20/1Terminating066sweb-20/1Terminating066s
- 创建资源清单文件
当sts控制器的管理策略为Parallel
时:
#nginx-sts.yamlapiVersion:v1kind:Servicemetadata:name:nginxnamespace:defaultlabels:app:nginxspec:ports:-name:httpport:80clusterIP:Noneselector:app:nginx---apiVersion:apps/v1kind:StatefulSetmetadata:name:webnamespace:defaultspec:podManagementPolicy:ParallelserviceName:"nginx"#指定一个headless servicereplicas:6#为了现象更明显,这里设置为6selector:matchLabels:app:nginxtemplate:#Pod模板metadata:labels:app:nginx#一定要和上面selector保持一致spec:containers:-name:nginximage:nginx:1.7.9ports:-containerPort:80#这个端口只是声明,不会起到访问的作用name:webvolumeMounts:-name:wwwmountPath:/usr/share/nginx/htmlvolumeClaimTemplates:#pvc模板,实际工作中是会使用sc,存储类的-metadata:name:www#要和volumeMounts的名称一致spec:accessModes:["ReadWriteOnce"]resources:requests:storage:1Gi
- 创建pod和删除pod,观察pod的创建和删除顺序
[root@docker ~]#kubectl delete -f nginx-sts.yaml[root@docker ~]#kubectl get po -w……NAMEREADYSTATUSRESTARTSAGEweb-00/1Pending00sweb-10/1Pending00sweb-20/1Pending00sweb-30/1Pending00sweb-40/1Pending00sweb-20/1Pending00sweb-00/1Pending00sweb-10/1Pending00sweb-30/1Pending00sweb-50/1Pending00sweb-40/1Pending00sweb-50/1Pending00sweb-20/1ContainerCreating00sweb-30/1ContainerCreating00sweb-00/1ContainerCreating00sweb-10/1ContainerCreating00sweb-50/1ContainerCreating01sweb-40/1ContainerCreating02sweb-41/1Running03sweb-01/1Running04sweb-11/1Running04sweb-51/1Running04sweb-21/1Running04sweb-31/1Running05s……[root@docker ~]#kubectl delete -f nginx-sts.yaml[root@docker ~]#kubectl get po -w#这个就符合预期现象了。……web-41/1Terminating058sweb-01/1Terminating058sweb-21/1Terminating058sweb-11/1Terminating058sweb-31/1Terminating058sweb-51/1Terminating058sweb-40/1Terminating060sweb-40/1Terminating060sweb-40/1Terminating060sweb-50/1Terminating060sweb-50/1Terminating060sweb-10/1Terminating060sweb-50/1Terminating060sweb-10/1Terminating060sweb-10/1Terminating060sweb-20/1Terminating060sweb-30/1Terminating060sweb-20/1Terminating060sweb-30/1Terminating060sweb-20/1Terminating060sweb-30/1Terminating060sweb-00/1Terminating060sweb-00/1Terminating060sweb-00/1Terminating060s……
测试结束。😘
3.更新策略
前面课程中我们学习了 Deployment 的升级策略,在 StatefulSet 中同样也支持两种升级策略:onDelete
和 RollingUpdate
,同样可以通过设置 .spec.updateStrategy.type
进行指定。
OnDelete
:该策略表示当更新了StatefulSet
的模板后,只有手动删除旧的 Pod 才会创建新的 Pod。RollingUpdate
:该策略表示当更新 StatefulSet 模板后会自动删除旧的 Pod 并创建新的Pod,如果更新发生了错误,这次“滚动更新”就会停止。不过需要注意 StatefulSet 的 Pod 在部署时是顺序从 0~n 的,而在滚动更新时,这些 Pod 则是按逆序的方式即 n~0 一次删除并创建。
1、滚动更新
RollingUpdate 更新策略会更新一个 StatefulSet 中的所有 Pod,采用与序号索引相反的顺序进行更新。
[root@docker ~]#kubectl explain sts.spec.updateStrategy.rollingUpdateKIND:StatefulSetVERSION:apps/v1RESOURCE:rollingUpdate<Object>DESCRIPTION:RollingUpdateisusedtocommunicateparameterswhenTypeisRollingUpdateStatefulSetStrategyType.RollingUpdateStatefulSetStrategyisusedtocommunicateparameterforRollingUpdateStatefulSetStrategyType.FIELDS:maxUnavailable<string>Themaximumnumberofpodsthatcanbeunavailableduringtheupdate.Valuecanbeanabsolutenumber(ex:5) or a percentage of desired pods (ex:10%). Absolute number is calculated from percentage by rounding up. Thiscannotbe0.Defaultsto1.Thisfieldisalpha-levelandisonlyhonoredbyserversthatenabletheMaxUnavailableStatefulSetfeature.Thefieldappliestoallpodsintherange0toReplicas-1.Thatmeansifthereisanyunavailablepodintherange0toReplicas-1,itwillbecountedtowardsMaxUnavailable.partition<integer>PartitionindicatestheordinalatwhichtheStatefulSetshouldbepartitionedforupdates.Duringarollingupdate,allpodsfromordinalReplicas-1toPartitionareupdated.AllpodsfromordinalPartition-1to0remainuntouched.Thisishelpfulinbeingabletodoacanarybaseddeployment.Thedefaultvalueis0.
从上面文档可以看出 StatefulSet 的滚动更新策略只支持 maxUnavailable 、partition 两个属性,我们先按照属性的默认值进行测试。
💘 实战:StatefulSet控制器部署有状态服务-滚动更新-2022.12.20(成功测试)
- 实验环境
1、win10,vmwrokstation虚机;2、k8s集群:3台centos7.61810虚机,2个master节点,1个node节点k8sversion:v1.20CONTAINER-RUNTIME:containerd:v1.6.10
实验软件(无)
重新创建前面的 StatefulSet 应用:
[root@docker ~]#kubectl apply -f nginx-sts.yaml
- 然后此时我们来监控 StatefulSet 中的 Pod:
[root@docker ~]# kubectl get pod -l app=nginx-wNAMEREADYSTATUSRESTARTSAGEweb-01/1Running047sweb-11/1Running047sweb-21/1Running047sweb-31/1Running047sweb-41/1Running047sweb-51/1Running047sxxx
- 然后我们通过 kubectl patch 更新容器镜像(当然也可以直接修改 yaml 文件):
[root@docker ~]# kubectl patch statefulset web --type='json'-p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"nginx:latest"}]'statefulset.apps/webpatched
- watch监控会输出如下所示的内容:
xxxweb-51/1Terminating074sweb-50/1Terminating078sweb-50/1Terminating081sweb-50/1Terminating081sweb-50/1Pending00sweb-50/1Pending01sweb-50/1ContainerCreating01sweb-51/1Running041sweb-41/1Terminating02m31sweb-40/1Terminating02m34sweb-40/1Terminating02m36sweb-40/1Terminating02m37sweb-40/1Pending00sweb-40/1Pending01sweb-40/1ContainerCreating01sweb-41/1Running039sweb-31/1Terminating03m48sweb-30/1Terminating03m49sweb-30/1Terminating03m49sweb-30/1Terminating03m50sweb-30/1Pending00sweb-30/1Pending01sweb-30/1ContainerCreating01sweb-31/1Running02sweb-21/1Terminating03m52sweb-20/1Terminating03m53sweb-20/1Terminating03m53sweb-20/1Terminating03m53sweb-20/1Pending00sweb-20/1Pending00sweb-20/1ContainerCreating00sweb-21/1Running02sweb-11/1Terminating03m55sweb-10/1Terminating03m56sweb-10/1Terminating03m56sweb-10/1Terminating03m56sweb-10/1Pending00sweb-10/1Pending00sweb-10/1ContainerCreating01sweb-11/1Running014sweb-01/1Terminating04m11sweb-00/1Terminating04m26sweb-00/1Terminating04m26sweb-00/1Terminating04m26sweb-00/1Pending00sweb-00/1Pending01sweb-00/1ContainerCreating01sweb-01/1Running03s
StatefulSet 里的 Pod 采用和序号相反的顺序更新,在更新下一个 Pod 前,StatefulSet 控制器终止 Pod 并等待它们变成 Running 和 Ready。
- 同样我们还可以使用
kubectl rollout status sts/<名称>
来查看 StatefulSet 的滚动更新状态。
[root@docker ~]#kubectl rollout status sts webWaitingfor1podstobeready...Waitingforpartitionedrollouttofinish:2outof6newpodshavebeenupdated...Waitingfor1podstobeready...Waitingfor1podstobeready...Waitingforpartitionedrollouttofinish:3outof6newpodshavebeenupdated...Waitingfor1podstobeready...Waitingfor1podstobeready...Waitingforpartitionedrollouttofinish:4outof6newpodshavebeenupdated...Waitingfor1podstobeready...Waitingfor1podstobeready...Waitingforpartitionedrollouttofinish:5outof6newpodshavebeenupdated...Waitingfor1podstobeready...partitionedrolloutcomplete:6newpodshavebeenupdated...
测试结束。😘
2、分段更新
另外SatefulSet
的滚动升级还支持 Partitions
的特性,可以通过.spec.updateStrategy.rollingUpdate.partition
进行设置,在设置 partition 后,SatefulSet 的 Pod 中序号大于或等于 partition 的 Pod 会在 StatefulSet 的模板更新后进行滚动升级,而其余的 Pod 保持不变,这个功能是不是可以实现灰度发布?大家可以去手动验证下。
比如某个业务有10个pod:web0-web9。如果这里的partitial设置为3的话,滚动更新的话,就表示本次更新只升级web3-web9这7个pod,而web0-web2是不会被更新的,即:2个版本可以同事存在,也就实现了灰度发布。
⚠️ 注意:如果partition=0
的话,那么代表更新多有pod。
⚠️ 注意:如果想更新其中一个pod的话,可以使用kubectl patch 命令
来做更新。
💘 实战:StatefulSet控制器部署有状态服务-分段更新-2022.12.20(成功测试)
- 实验环境
1、win10,vmwrokstation虚机;2、k8s集群:3台centos7.61810虚机,2个master节点,1个node节点k8sversion:v1.20CONTAINER-RUNTIME:containerd:v1.6.10
- 实验软件(无)
e
- 现在我们来重新创建如下所示的 StatefulSet,设置 4 个副本,并配置滚动更新的 partition 为 2:
#nginx-sts-parts.yamlapiVersion:v1kind:Servicemetadata:name:nginxnamespace:defaultlabels:app:nginxspec:ports:- name:httpport:80clusterIP:Noneselector:app:nginx---apiVersion:apps/v1kind:StatefulSetmetadata:name:webnamespace:defaultspec:podManagementPolicy:OrderedReady#默认就是OrderedReadyserviceName:"nginx"#指定一个headless servicereplicas:4#为了现象更明显,这里设置为6updateStrategy:type:RollingUpdaterollingUpdate:partition:2selector:matchLabels:app:nginxtemplate:#Pod模板metadata:labels:app:nginx#一定要和上面selector保持一致spec:containers:- name:nginximage:nginx:1.7.9ports:- containerPort:80#这个端口只是声明,不会起到访问的作用name:webvolumeMounts:- name:wwwmountPath:/usr/share/nginx/htmlvolumeClaimTemplates:#pvc模板,实际工作中是会使用sc,存储类的- metadata:name:www#要和volumeMounts的名称一致spec:accessModes:["ReadWriteOnce"]resources:requests:storage:1Gi
- 直接创建上面的资源清单:
#记得删除之前的资源[root@docker ~]#kubectl delete -f nginx-sts.yaml#部署[root@docker ~]#kubectl apply -f nginx-sts-parts.yamlservice/nginxcreatedstatefulset.apps/webcreated
- 查看当前pod
[root@docker ~]# kubectl get pod -l app=nginxNAMEREADYSTATUSRESTARTSAGEweb-01/1Running033sweb-11/1Running029sweb-21/1Running028sweb-31/1Running026s
- 这里利用
--watch参数
来监控下pod输出
[root@docker ~]# kubectl get pod -l app=nginx-w
- 现在我们来更新应用镜像版本触发一次滚动更新:
[root@docker ~]# kubectl patch statefulset web --type='json'-p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"nginx:latest"}]'
- 观察pod更新过程
[root@docker ~]# kubectl get pod -l app=nginx-wNAMEREADYSTATUSRESTARTSAGEweb-01/1Running02m54sweb-11/1Running02m50sweb-21/1Running02m49sweb-31/1Running02m47sxxxweb-31/1Terminating03m10sweb-30/1Terminating03m12sweb-30/1Terminating03m12sweb-30/1Terminating03m12sweb-30/1Pending00sweb-30/1Pending00sweb-30/1ContainerCreating01sweb-31/1Running03sweb-21/1Terminating03m18sweb-20/1Terminating03m20sweb-20/1Terminating03m20sweb-20/1Terminating03m20sweb-20/1Pending00sweb-20/1Pending00sweb-20/1ContainerCreating01sweb-21/1Running02s#再次查看pod情况及其镜像版本[root@docker ~]#kubectl get poNAMEREADYSTATUSRESTARTSAGEweb-01/1Running04h52mweb-11/1Running04h52mweb-21/1Running04h49mweb-31/1Running04h49m[root@docker ~]#kubectl get po web-1 -oyaml|grepimage-image:nginx:1.7.9……[root@docker ~]#kubectl get po web-2 -oyaml|grepimage-image:nginx:latest……
则正常 web-3 和 web-2 两个 Pod 会被更新成新的版本。当指定了 partition 时,如果更新了 StatefulSet 的.spec.template ,则所有序号大于或等于 partition 的 Pod 都将被更新。
- 如果一个序号小于分区的 Pod 被删除或者终止,它将被按照原来的配置恢复。
[root@docker ~]#kubectl delete po web-1pod"web-1"deleted[root@docker ~]#kubectl get poNAMEREADYSTATUSRESTARTSAGEweb-01/1Running04h54mweb-11/1Running05sweb-21/1Running04h51mweb-31/1Running04h51m[root@docker ~]#kubectl get po web-1 -oyaml|grepimage-image:nginx:1.7.9……
现在就出现了两个版本同时提供服务的情况了,这是不是就是我们常说的金丝雀发布?
在实际的项目中,其实我们还是很少会去直接通过 StatefulSet 来部署我们的有状态服务的,除非你自己能够完全能够 hold 住,对于一些特定的服务,我们可能会使用更加高级的 Operator 来部署,比如 etcd-operator、prometheus-operator等等,这些应用都能够很好的来管理有状态的服务,而不是单纯的使用一个 StatefulSet 来部署一个 Pod 就行,因为对于有状态的应用最重要的还是数据恢复、故障转移等等**。**
测试结束。😘
关于我
我的博客主旨:
- 排版美观,语言精炼;
- 文档即手册,步骤明细,拒绝埋坑,提供源码;
- 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!
🍀 微信二维码
x2675263825 (舍得), qq:2675263825。
🍀 微信公众号
《云原生架构师实战》
🍀 语雀
上次更新时间: