只要你愿意遵守规则,在Kubernetes上的部署和航空旅行可以是相当愉快的。更多时候,事情会 "顺利进行"。然而,如果一个人对与必须保持活力的鳄鱼一起旅行或对必须保持可用的数据库进行扩展感兴趣,情况可能会变得有点复杂。在这个问题上,建立自己的飞机或数据库甚至可能更容易。撇开与爬行动物的旅行不谈,扩展一个高度可用的有状态系统并不是一件小事。
任何系统的扩展都有两个主要部分。
- 增加或删除系统运行的基础设施,以及
- 确保系统知道如何处理自身的额外实例被添加和删除。
大多数无状态系统,例如网络服务器,在创建时不需要知道对等物。有状态的系统,包括像CockroachDB这样的数据库,必须与它们的对等实例协调,并对数据进行洗牌。运气好的话,CockroachDB可以处理数据的重新分配和复制。棘手的部分是在这些操作中能够容忍故障,确保数据和实例分布在许多故障域(可用性区域)。
Kubernetes的职责之一是将 "资源"(如磁盘或容器)放入集群中,并满足它们要求的约束。例如。"我必须在可用性区域A",或者 "我不能和这个其他Pod放在同一个节点上"。
作为这些约束条件的补充,Kubernetes提供了Statefulsets,为Pod提供身份,以及 "跟随 "这些识别的Pod的持久性存储。在StatefulSet中,身份是由Pod名称后面的一个增加的整数来处理的。值得注意的是,这个整数必须始终是连续的:在一个StatefulSet中,如果pod1和3存在,那么pod2也必须存在。
在架构上,CockroachCloud将CockroachDB的每个区域作为一个StatefulSet部署在自己的Kubernetes集群中--参见在单个Kubernetes集群中协调CockroachDB。在这篇文章中,我将着眼于一个单独的区域,一个StatefulSet和一个Kubernetes集群,它至少分布在三个可用区。
一个三节点的CockroachCloud集群看起来会是这样的。
当向集群添加额外的资源时,我们也会将它们分布在各个区域。为了获得最快的用户体验,我们同时添加所有的Kubernetes节点,然后扩大StatefulSet的规模。
请注意,无论pod被分配到Kubernetes节点的顺序如何,都会满足反affinities。在这个例子中,pod 0、1和2分别被分配到A、B和C区,但是pod 3和4以不同的顺序被分配到B和A区。反亲和性仍然得到满足,因为吊舱仍然被放置在不同的区域。
为了从集群中移除资源,我们以相反的顺序执行这些操作。
我们首先缩小StatefulSet的规模,然后从集群中删除任何缺少CockroachDB pod的节点。
现在,请记住,在一个大小为n的StatefulSet中,pod的id必须在[0,n) 。当把一个StatefulSet缩小到m时,Kubernet会移除m个pod,从最高的序号开始,向最低的序号移动,与它们被添加的顺序相反。考虑一下下面的集群拓扑结构。
当5到3的顺序被从这个集群中移除时,这个状态集继续存在于所有3个可用区中。
然而,Kubernetes的调度器并没有像我们一开始预期的那样保证上面的位置。
我们对以下内容的综合认识是导致这种误解的原因。
- Kubernetes自动跨区分布Pod的能力
- 一个有n个副本的StatefulSet的行为,当Pod被部署时,它们是按顺序创建的,顺序从
{0..n-1}。
考虑下面的拓扑结构。
这些Pod是按顺序创建的,它们分布在集群的所有可用区。当序号5到3被终止时,这个集群将失去在C区的存在!这时,我们的自动化系统就会出现在C区。
更糟糕的是,我们的自动化,当时会删除节点A-2、B-2和C-2。使得CRDB-1处于非计划状态,因为持久性卷只在它们最初创建的区域内可用。
为了纠正后一个问题,我们现在采用了一种 "猎杀和啄食 "的方法来从集群中移除机器。与其盲目地从集群中删除Kubernetes节点,不如只删除没有CockroachDB pod的节点。更为艰巨的任务是管理Kubernetes的调度器。
一场头脑风暴让我们有了3个选择。
1.升级到kubernetes 1.18,并利用Pod拓扑扩展约束。
虽然这似乎是一个完美的解决方案,但在撰写本文时,Kubernetes 1.18在公有云中两个最常见的Kubernetes管理服务(EKS和GKE)上不可用。此外,pod拓扑结构的传播限制仍然是一个(1.18的测试版功能),这意味着即使在v1.18版本可用时,也不能保证它在管理集群中可用。整个努力让人联想到当Internet Explorer 8还在的时候查看caniuse.com。
2.2.在每个区部署一个statefulset。
与分布在所有可用区的StatefulSet相比,每个区有节点亲和力的单一StatefulSet将允许手动控制我们的分区拓扑结构。我们的团队过去曾考虑过这个选项,这使得它特别有吸引力。最终,我们决定放弃这个方案,因为这需要对我们的代码库进行大规模的修改,而且在现有的客户集群上进行迁移也是一个同样大的工程。
3.编写一个自定义的Kubernetes调度器。
由于Kelsey Hightower的一个例子和Banzai Cloud的一篇博文,我们决定一头扎进去,编写我们自己的自定义Kubernetes调度器。一旦我们的概念验证被部署和运行,我们很快发现,Kubernetes的调度器也负责将持久化卷映射到它所调度的Pod上。kubectl get events 的输出使我们相信有另一个系统在起作用。在我们寻找负责存储要求映射的组件的过程中,我们发现了kube-scheduler插件系统。我们的下一个POC是一个Filter 插件,它通过pod的顺序来确定适当的可用区域,并且工作得非常完美
我们的自定义调度器插件是开源的,在我们所有的CockroachCloud集群中运行。对StatefulSet豆荚的调度方式进行控制,让我们有信心扩大规模。一旦GKE和EKS中的pod拓扑传播约束可用,我们可能会考虑让我们的插件退役,但维护的开销出乎意料地低。更好的是:该插件的实现与我们的业务逻辑是正交的。部署它,或者让它退役,就像改变我们的StatefulSet定义中的schedulerName 字段一样简单。
进一步阅读。
本文章的一个版本最初出现在Kubernetes博客上。