k8s in action实践笔记

10.2 了解Statefulset

2021-07-30  本文已影响0人  众神开挂

10.2 了解Statefulset

可以创建一个Statefulset资源替代ReplicaSet来运行这类pod。它是专门定制的一类应用,这类应用中每一个实例都是不可替代的个体,都拥有稳定的名字和状态。

10.2.1 对比Statefulset和ReplicaSet

要很好地理解Statefulset的用途,最好先与ReplicaSet或ReplicationControllers对比一下。首先拿一个通用的类比来解释它们。

通过宠物与牛的类比来理解有状态

你可能已经听说过宠物与牛的类比。如果没有,先简单介绍一下。可以把我们的应用看作宠物或牛。

注意 Statefulset最初被称为PetSet,这个名字来源于宠物与牛的类比。

我们倾向于把应用看作宠物,给每个实例起一个名字,细心照顾每个实例。但是也许把它们看成牛更为合适,并不需要对单独的实例有太多关心。这样就可以非常方便地替换掉不健康的实例,就跟农场主替换掉一头生病的牛一样。

对于无状态的应用实例来说,行为非常像农场里的牛。一个实例挂掉后并没什么影响,可以创建一个新实例,而让用户完全无感知。

另一方面,有状态的应用的一个实例更像一个宠物。若一只宠物死掉,不能买到一只完全一样的,而不让用户感知到。若要替换掉这只宠物,需要找到一只行为举止与之完全一致的宠物。对应用来说,意味着新的实例需要拥有跟旧的案例完全一致的状态和标识。

Statefulset与ReplicaSet或ReplicationController的对比

RelicaSet或ReplicationController管理的pod副本比较像牛,这是因为它们都是无状态的,任何时候它们都可以被一个全新的pod替换。然而有状态的pod需要不同的方法,当一个有状态的pod挂掉后(或者它所在的节点故障),这个pod实例需要在别的节点上重建,但是新的实例必须与被替换的实例拥有相同的名称、网络标识和状态。这就是StatefulSet如何管理pod的。

Statefulset保证了pod在重新调度后保留它们的标识和状态。它让你方便地扩容、缩容。与ReplicaSet类似,Statefulset也会指定期望的副本个数,它决定了在同一时间内运行的宠物的数量。与ReplicaSet类似,pod也是依据Statefulset的pod模板创建的(想象一下曲奇饼干模板)。与ReplicaSet不同的是,Statefulset创建的pod副本并不是完全一样的。每个pod都可以拥有一组独立的数据卷(持久化状态)而有所区别。另外“宠物”pod的名字都是规律的(固定的),而不是每个新pod都随机获取一个名字。

10.2.2 提供稳定的网络标识

一个Statefulset创建的每个pod都有一个从零开始的顺序索引,这个会体现在pod的名称和主机名上,同样还会体现在pod对应的固定存储上。这些pod的名称则是可预知的,因为它是由Statefulset的名称加该实例的顺序索引值组成的。不同于pod随机生成一个名称,这样有规则的pod名称是很方便管理的,如图10.5所示。

[图片上传失败...(image-bf81d6-1627605883814)]

图10.5 与ReplicaSet不同,由Statefulset创建的pod拥有规则的名称(和主机名)

控制服务介绍

让pod拥有可预知的名称和主机名并不是全部,与普通的pod不一样的是,有状态的pod有时候需要通过其主机名来定位,而无状态的pod则不需要,因为每个无状态的pod都是一样的,在需要的时候随便选择一个即可。但对于有状态的pod来说,因为它们都是彼此不同的(比如拥有不同的状态),通常希望操作的是其中特定的一个。

基于以上原因,一个Statefulset通常要求你创建一个用来记录每个pod网络标记的headless Service。通过这个Service,每个pod将拥有独立的DNS记录,这样集群里它的伙伴或者客户端可以通过主机名方便地找到它。比如说,一个属于default命名空间,名为foo的控制服务,它的一个pod名称为A-0,那么可以通过下面的完整域名来访问它:a-0.foo.default.svc.cluster.local。而在ReplicaSet中这样是行不通的。

另外,也可以通过DNS服务,查找域名foo.default.svc.cluster.local对应的所有SRV记录,获取一个Statefulset中所有pod的名称。我们将在10.4节中介绍SRV记录,解释如何通过它来发现一个Statefulset中的所有成员。

替换消失的宠物

当一个Statefulset管理的一个pod实例消失后(pod所在节点发生故障,或有人手动删除pod),Statefulset会保证重启一个新的pod实例替换它,这与ReplicaSet类似。但与ReplicaSet不同的是,新的pod会拥有与之前pod完全一致的名称和主机名(ReplicaSet和Statefulset的差异如图10.6所示)。

img

image

图10.6 Statefulset使用标识完全一致的新的pod替换,ReplicaSet则是使用一个不相干的新的pod替换

如你之前了解的那样,pod运行在哪个节点上并不重要,新的pod并不一定会调度到相同的节点上。对于有状态的pod来说也是这样,即使新的pod被调度到一个不同的节点,也同样可以通过主机名来访问。

扩缩容Statefulset

扩容一个Statefulset会使用下一个还没用到的顺序索引值创建一个新的pod实例。比如,要把一个Statefulset从两个实例扩容到三个实例,那么新实例的索引值就会是2(现有实例使用的索引值为0和1)。

当缩容一个Statefulset时,比较好的是很明确哪个pod将要被删除。作为对比,ReplicaSet的缩容操作则不同,不知道哪个实例会被删除,也不能指定先删除哪个实例(也许这个功能会在将来实现)。缩容一个Statefulset将会最先删除最高索引值的实例(如图10.7所示),所以缩容的结果是可预知的。

[图片上传失败...(image-d8c3f0-1627605883814)]image

图10.7 缩容一个Statefulset将会最先删除最高索引值的实例

因为Statefulset缩容任何时候只会操作一个pod实例,所以有状态应用的缩容不会很迅速。举例来说,一个分布式存储应用若同时下线多个节点,则可能导致其数据丢失。比如说一个数据项副本数设置为2的数据存储应用,若同时有两个节点下线,一份数据记录就会丢失,如果它正好保存在这两个节点上。若缩容是线性的,则分布式存储应用就有时间把丢失的副本复制到其他节点,保证数据不会丢失。

基于以上原因,Statefulset在有实例不健康的情况下是不允许做缩容操作的。若一个实例是不健康的,而这时再缩容一个实例的话,也就意味着你实际上同时失去了两个集群成员。

10.2.3 为每个有状态实例提供稳定的专属存储

你已经知道了Statefulset如何保证一个有状态的pod拥有稳定的标识,那存储呢?一个有状态的pod需要拥有自己的存储,即使该有状态的pod被重新调度(新的pod与之前pod的标识完全一致),新的实例也必须挂载着相同的存储。那Statefulset是如何做到这一点的呢?

很明显,有状态的pod的存储必须是持久的,并且与pod解耦。在第6章中学习了持久卷和持久卷声明,通过在pod中关联一个持久卷声明的名称,就可以为pod提供持久化存储。因为持久卷声明与持久卷是一对一的关系,所以每个Statefulset的pod都需要关联到不同的持久卷声明,与独自的持久卷相对应。因为所有的pod实例都是依据一个相同的pod模板创建的,那它们是如何关联到不同的持久卷是的呢?并且由谁来创建这些持久卷是呢?当然你肯定不想手在动创建Statefulset之前,依据pod的个数创建相同数量的持久卷量。当然不用这么做!

在pod模板中添加卷声明模板

像Statefulset创建pod一样,Statefulset也需要创建持久卷声明。所以一个Statefulset可以拥有一个或多个卷声明模板,这些持久卷声明会在创建pod前创建出来,绑定到一个pod实例上(如图10.8所示)。

img

image

图10.8 一个Statefulset创建pod和持久卷声明

声明的持久卷既可以通过administrator用户预先创建出来,也可以如第6章所述,由持久卷的动态供应机制实时创建出来。

持久卷的创建和删除

扩容StatefulSet增加一个副本数时,会创建两个或更多的API对象(一个pod和与之关联的一个或多个持久卷声明)。但是对缩容来说,则只会删除一个pod,而遗留下之前创建的声明。当你知道一个声明被删除会发生什么的话,你就明白为什么这么做了。当一个声明被删除后,与之绑定的持久卷就会被回收或删除,则其上面的数据就会丢失。

因为有状态的pod是用来运行有状态应用的,所以其在数据卷上存储的数据非常重要,在Statefulset缩容时删除这个声明将是灾难性的,特别是对于Statefulset来说,缩容就像减少其replicas数值一样简单。基于这个原因,当你需要释放特定的持久卷时,需要手动删除对应的持久卷声明。

重新挂载持久卷声明到相同pod的新实例上

因为缩容Statefulset时会保留持久卷声明,所以在随后的扩容操作中,新的pod实例会使用绑定在持久卷上的相同声明和其上的数据(如图10.9所示)。当你因为误操作而缩容一个Statefulset后,可以做一次扩容来弥补自己的过失,新的pod实例会运行到与之前完全一致的状态(名字也是一样的)。

img

image

图10.9 Statefulset缩容时不删除持久卷声明,扩容时会重新挂载上

10.2.4 Statefulset的保障

如之前描述的,Statefulset的行为与ReplicaSet是不一样的。Statefulset不仅拥有稳定的标记和独立的存储,它的pod还有其他的一些保障。

稳定标识和独立存储的影响

通常来说,无状态的pod是可以替代的,而有状态的pod则不行。我们之前已经描述了一个有状态的pod总是会被一个完全一致的pod替换(两者有相同的名称、主机名和存储等)。这个替换发生在Kubernetes发现旧的pod不存在时(例如手动删除这个pod)。

那么当Kubernetes不能确定一个pod的状态时呢?如果它创建一个完全一致的pod,那系统中就会有两个完全一致的pod在同时运行。这两个pod会绑定到相同的存储,所以这两个相同标记的进程会同时写相同的文件。对于ReplicaSet的pod来说,这不是问题,因为应用本来就是设计为在相同的文件上工作的。并且我们知道ReplicaSet会以一个随机的标识来创建pod,所以不可能存在两个相同标识的进程同时运行。

介绍Statefulset的at-most-one的语义

Kubernetes必须保证两个拥有相同标记和绑定相同持久卷声明的有状态的pod实例不会同时运行。一个Statefulset必须保证有状态的pod实例的at-most-one语义。

也就是说一个Statefulset必须在准确确认一个pod不再运行后,才会去创建它的替换pod。这对如何处理节点故障有很大的影响,我们会在本章后面详细介绍。在我们做这些之前,需要先创建一个Statefulset,看看它是如何工作的。在这个过程中,你会学到更多的知识。

上一篇下一篇

猜你喜欢

热点阅读