关注微信公众号:CodingTechWork,一起学习进步。
引言
??我们都知道在k8s集群中,Deployment是用来部署无状态的服务,那有状态的服务是用什么资源对象来部署呢?无状态和有状态服务部署的区别是什么?有状态的pod肯定需要独立的存储卷,这样才能保证故障后寻找数据就地恢复原状态,那如何实现多个pod拥有自己独立存储卷?下面我们来看看如何演进方案。
演进
手动创建多个pod
??手动创建多个pod,每个pod使用一个独立的持久卷声明,但是需要我们手动管理这些pod,当发生故障后,需要重新手动创建这些pod,从而保证有状态恢复。
1个RS对应1个pod
??手动创建pod,肯定不便于维护,我们在每个pod的上一层来操作,创建多个ReplicaSet,每个ReplicaSet的副本数设置为1,这样pod和ReplicaSet是一一对应的,每个ReplicaSet的pod模板都关联一个独立的持久卷声明。这种可以达到某个节点故障或pod误删时自动重新调度创建pod的效果,但是对于伸缩副本时,又需要手动创建或删除ReplicaSet,还是达不到一次性创建、更新、删除后,后期自动调度的效果。
所有pod共享同一个PV
??创建多个ReplicaSet对应多个pod,还是会有伸缩问题,且不好维护,如果只创建一个ReplicaSet,让所有pod共享同一持久卷,但每个pod是使用同一持久卷的不同目录。
创建StatefulSet
??所有pod使用同一数据卷中不同目录要求实例之间相互协作,由于不能在一个pod模板中给所有pod做不同目录的指定,需要让pod自己识别选择一个其他实例没有使用的目录,这种共享存储将会给集群带来性能问题。
??若每个pod拥有自己的网络标识,就算失败了,再恢复时,还是原有的稳定的网络标识。这就需要使用StatefulSet资源来部署这些服务,这些服务中每个实例都是不可替代的,都有稳定的名字和网络标识。
StatefulSet介绍
有状态服务集群特点
- 每个节点都有固定的身份ID,通过ID可以使集群内成员相互发现并通信;
- 集群规模比较固定;
- 集群内每个节点有状态,一般会持久化数据到永久存储中,这样失败了的实例可以通过持久化数据再恢复原有状态;
- 磁盘损坏,则某个节点无法正常工作,集群功能将受损受阻;
Statefulset概述
??StatefulSet是k8s从1.4版本引入的PetSet
资源对象发展到1.5版本更名而来,在k8s集群中用于部署有状态服务
。为何之前叫PetSet?是因为拿宠物和牛作类比,把应用看做是宠物,给每个实例都起一个名字,在宠物店里,若一个宠物死掉,我们买不到一只一模一样的,用户肯定会察觉到差异,若要代替这只宠物,我们必须找到一只属性及行为和之前完全一致的宠物,同样的,对于应用而言,我们需要找到状态和标识和之前一致的实例来代替之前故障实例。
StatefulSet特点
- StatefulSet中每个Pod有稳定、唯一的网络标识(用于发现集群内其他成员),Pod名称由StatefulSet名+有序数字组成,如ZK服务对应的StatefulSet名为test-zk,副本数为3,则第一个pod名为test-zk-0,第二个pod名称为test-zk-1,第三个pod名称为test-zk-2。
- StatefulSet所控制的pod副本启停顺序是有序的。
- StatefulSet中的Pod采用稳定的持久化存储卷(PV或者PVC实现),删除pod时,默认不会删除与StatefulSet相关的存储卷。
- StatefulSet需要和
Headless Service
(没有Cluster IP的Service)进行配合,一般在StatefulSet中指定spec.serviceName
的名称与Service资源中的metadata.name
保持一致。
稳定的网络标识
pod名有序
??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。
pvc名有序
??如果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
??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启停过程
??StatefulSet控制的pod启停过程,类似于扩缩容过程。
??假设有N个副本数。
??启动时,先启动pod序号为0的,然后依次递增至N-1,操作第N个pod时,前N-1个pod已经是运行并准备好的状态(Running状态)。
??停止时,先停止pod序号为最大的N-1,然后依次递减至0,操作第N-1个pod时,第N个pod已经是停止状态。
重启pod流程
??我们先看一下ReplicaSet管理的一个pod如果消失时,如何重启一个新的pod来替换旧的。
??当一个StatefulSet管理的一个pod因为发生故障或被人为删除而消失后,StatefulSet可以保证再去重启一个新的pod实例去替换它,这个新pod实例与之前的pod保持完全一致的行为(pod名、主机名)。
??我们可以从ReplicaSet和StatefulSet重启pod替换消失pod的过程中看出两种过程中的标识是很明显的差别,也是无状态和有状态的差异。
pod扩缩容过程
pod扩容过程
??假设StatefulSet名称为A,从副本数1扩容到3。扩容一个StatefulSet时会使用下一个还没有使用到的顺序索引值进行新pod实例的创建,依次递增1。
pod缩容过程
??假设StatefulSet名称为A,从副本数3缩容到1。缩容一个StatefulSet时,StatefulSet是明确知道先删除最高索引值的实例,缩容删除哪个pod是可控预知的,而ReplicaSet是不知道会删除哪个实例。
??由于StatefulSet缩容时是从高索引挨个删除,每次只会操作一个pod实例,所以有状态应用的缩容过程很慢。
StatefulSet的at-most-one语义
??在副本数缩容再扩容时,如果k8s没有保障机制,很容易出现正在缩容的pod还在运行,又新建一个一样标识的pod进行pvc绑定,这会带来问题。
??对于ReplicaSet的pod来说,会以一个随机的标识来创建pod,不会存在两个相同标识的进程同时运行。而StatefulSet是必须在准确确认一个pod不再运行后,才会去创建替换的pod
,从而保证两个拥有相同标记和绑定相同PVC的有状态的pod实例不会同时运行,这便是at-most-one
的语义。
StatefulSet提供稳定的独立存储
??一个有状态的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使用命令
假设某个应用的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
Q&A
k8s中无状态服务和有状态服务部署的区别?
无状态:
- pod命名:pod名由资源名+随机的字符串组成;
- 数据存储:多个实例pod可以共享相同的持久化数据,存储不是必要条件;
- 扩缩容:可以随意扩缩容某个pod,不会指定某个pod进行扩缩容;
- 启停顺序:因为pod名的序号是随机串,无启停顺序之分;
- 无状态k8s资源:ReplicaSet、ReplicationController、Deployment、DaemonSet、Job等资源;
- 无状态服务:tomcat、nginx等;
有状态
这里假设有N个pod;
- pod命名:pod名由statefulset资源名+有序的数字组成(0,1,2...N-1),且pod有特定的网络标识;
- 数据存储:有状态的服务对应实例需要有自己的独立持久卷存储;
- 扩缩容:扩缩容不可随意,缩容是从数字最大的开始递减,扩容是在原有pod序号基础上递增1。
- 启停顺序:pod启停是有顺序的,启动时,先启动pod序号为0的,然后依次递增至N-1;停止时,先停止pod序号为最大的N-1,然后依次递减至0;
- 有状态k8s资源:StatefulSet资源;
- 有状态服务:Kafka、ZooKeeper、MySql、MongoDB以及一些需要保存日志的应用等服务;