关注微信公众号:CodingTechWork,一起学习进步。
我们都知道在k8s集群中,Deployment是用来部署无状态的服务,那有状态的服务是用什么资源对象来部署呢?无状态和有状态服务部署的区别是什么?有状态的pod肯定需要独立的存储卷,这样才能保证故障后寻找数据就地恢复原状态,那如何实现多个pod拥有自己独立存储卷?下面我们来看看如何演进方案。
手动创建多个pod,每个pod使用一个独立的持久卷声明,但是需要我们手动管理这些pod,当发生故障后,需要重新手动创建这些pod,从而保证有状态恢复。
手动创建pod,肯定不便于维护,我们在每个pod的上一层来操作,创建多个ReplicaSet,每个ReplicaSet的副本数设置为1,这样pod和ReplicaSet是一一对应的,每个ReplicaSet的pod模板都关联一个独立的持久卷声明。这种可以达到某个节点故障或pod误删时自动重新调度创建pod的效果,但是对于伸缩副本时,又需要手动创建或删除ReplicaSet,还是达不到一次性创建、更新、删除后,后期自动调度的效果。
创建多个ReplicaSet对应多个pod,还是会有伸缩问题,且不好维护,如果只创建一个ReplicaSet,让所有pod共享同一持久卷,但每个pod是使用同一持久卷的不同目录。
所有pod使用同一数据卷中不同目录要求实例之间相互协作,由于不能在一个pod模板中给所有pod做不同目录的指定,需要让pod自己识别选择一个其他实例没有使用的目录,这种共享存储将会给集群带来性能问题。
若每个pod拥有自己的网络标识,就算失败了,再恢复时,还是原有的稳定的网络标识。这就需要使用StatefulSet资源来部署这些服务,这些服务中每个实例都是不可替代的,都有稳定的名字和网络标识。
StatefulSet是k8s从1.4版本引入的PetSet
资源对象发展到1.5版本更名而来,在k8s集群中用于部署有状态服务
。为何之前叫PetSet?是因为拿宠物和牛作类比,把应用看做是宠物,给每个实例都起一个名字,在宠物店里,若一个宠物死掉,我们买不到一只一模一样的,用户肯定会察觉到差异,若要代替这只宠物,我们必须找到一只属性及行为和之前完全一致的宠物,同样的,对于应用而言,我们需要找到状态和标识和之前一致的实例来代替之前故障实例。
Headless Service
(没有Cluster IP的Service)进行配合,一般在StatefulSet中指定spec.serviceName
的名称与Service资源中的metadata.name
保持一致。 StatefulSet部署有状态应用时,创建出的每个pod都有命名规则,pod名是由StatefulSet名+有序数字组成,每个pod都有一个从0开始的顺序索引,这个顺序索引体现在pod名称、主机名以及pod对应的固定存储
上(pvc名称同样会有顺序索引)。如:3节点的zk的StatefulSet集群对应的StatefulSet名称为test-zk-ss,则对应的3个pod名称为test-zk-ss-0,test-zk-ss-1,test-zk-ss-2。
如果k8s集群中没有StorageClass的动态存储卷,我们也可以提前手动创建多个PV、PVC,手动创建的PVC名称必须符合之后创建的StatefulSet命名规则:$(volumeClaimTemplates.name)-$(pod_name)
,如Statefulset控制的3个pod对应名称为test-zk-ss-0,test-zk-ss-1,test-zk-ss-2,volumeClaimTemplates.name=test-pvc,则自动创建出来的pvc名称分别为:test-pvc-test-zk-ss-0、test-pvc-test-zk-ss-1、test-pvc-test-zk-ss-2。
Headless Service是没有Cluster IP的Service(与普通Service的区别),在Headless Service中可以看到spec.ClusterIP=None
。
若解析Headless Service的DNS域名,返回的是该Service对应的全部pod的Endpoint列表,StatefulSet在Headless Service基础上为StatefulSet控制的每个pod实例创建DNS域名,格式为$(pod_name).$(headless_service_name)
,全限定域名为:FQDN:$(pod_name).$(headless_service_name).$(namespace_name).svc.cluster.local
如:3节点的zk的StatefulSet集群对应的StatefulSet名称为test-zk-ss,Headless Service名称为test-zk-svc,则StatefulSet控制的3个pod对应DNS分别为:test-zk-ss-0.test-zk-svc,test-zk-ss-1.test-zk-svc,test-zk-ss-2.test-zk-svc。若命名空间名称为test-ns,则3个pod对应的FQDN分别为test-zk-ss-0.test-zk-svc.test-ns.svc.cluster.local,test-zk-ss-1.test-zk-svc.test-ns.svc.cluster.local,test-zk-ss-2.test-zk-svc.test-ns.svc.cluster.local。
StatefulSet控制的pod启停过程,类似于扩缩容过程。
假设有N个副本数。
启动时,先启动pod序号为0的,然后依次递增至N-1,操作第N个pod时,前N-1个pod已经是运行并准备好的状态(Running状态)。
停止时,先停止pod序号为最大的N-1,然后依次递减至0,操作第N-1个pod时,第N个pod已经是停止状态。
我们先看一下ReplicaSet管理的一个pod如果消失时,如何重启一个新的pod来替换旧的。
当一个StatefulSet管理的一个pod因为发生故障或被人为删除而消失后,StatefulSet可以保证再去重启一个新的pod实例去替换它,这个新pod实例与之前的pod保持完全一致的行为(pod名、主机名)。
我们可以从ReplicaSet和StatefulSet重启pod替换消失pod的过程中看出两种过程中的标识是很明显的差别,也是无状态和有状态的差异。
假设StatefulSet名称为A,从副本数1扩容到3。扩容一个StatefulSet时会使用下一个还没有使用到的顺序索引值进行新pod实例的创建,依次递增1。
假设StatefulSet名称为A,从副本数3缩容到1。缩容一个StatefulSet时,StatefulSet是明确知道先删除最高索引值的实例,缩容删除哪个pod是可控预知的,而ReplicaSet是不知道会删除哪个实例。
由于StatefulSet缩容时是从高索引挨个删除,每次只会操作一个pod实例,所以有状态应用的缩容过程很慢。
在副本数缩容再扩容时,如果k8s没有保障机制,很容易出现正在缩容的pod还在运行,又新建一个一样标识的pod进行pvc绑定,这会带来问题。
对于ReplicaSet的pod来说,会以一个随机的标识来创建pod,不会存在两个相同标识的进程同时运行。而StatefulSet是必须在准确确认一个pod不再运行后,才会去创建替换的pod
,从而保证两个拥有相同标记和绑定相同PVC的有状态的pod实例不会同时运行,这便是at-most-one
的语义。
一个有状态的pod需要有自己专属的存储,该pod被重新调度室,新的pod与旧pod保持一致的标识,且新的pod实例挂载相同的存储。
如何在同一个pod模板中为所有pod实例关联不同的持久卷?
Statefulset在pod模板中添加了卷声明模板,自动创建的pvc名称将会符合规则:$(volumeClaimTemplates.metadata.name).$(pod_name)
当StatefulSet增加一个副本时,会创建对应的pvc持久卷声明。创建N个副本,就会有N个PVC与之对应。
当StatefulSet减少副本时,会从高索引值的pod名开始删除pod,但是PVC不会被删除。如果需要释放特定的持久卷,需要手动删除对应的持久卷声明
。
当先缩容,再扩容时,由于旧pod对应的pvc不会被自动删除,扩容重建的pod实例会绑定到对应序号的pvc上。
从上图我们可以看出,StatefulSet A先从副本2缩容为副本1,对应的Pod A-1会自动删除,但是PVC A-1仍然保留不删除。当StatefulSet A从副本1扩容到副本2时,自动创建新的Pod A-1与旧pod保持一致的标识,PVC A-1被重新挂载到Pod A-1上。
假设某个应用的StatefulSet的yaml模板为test-zk-ss.yaml,StatefulSet名称为test-zk-ss,副本数为3个。更新后的模板为test-zk-ss-new.yaml
基于模板创建kubectl create -f test-zk-ss.yaml
kubectl delete -f test-zk-ss.yaml
kubectl delete statefulset test-zk-ss
kubectl apply -f test-zk-ss-new.yaml
kubectl edit statefulset test-zk-ss
kubectl get statefulset test-zk-ss -o yaml
kubectl describe statefulset test-zk-ss
无状态:
有状态
这里假设有N个pod;