自定义Kubernetes调度程序来编排高可用性应用程序

作者:克里斯·塞托(蟑螂实验室)

只要您愿意遵守规则,就可以在Kubernetes上进行部署和乘飞机旅行。事情往往会“正常”。但是,如果有兴趣与必须生存的鳄鱼一起旅行或对必须保持可用状态的数据库进行扩展,则情况可能会变得更加复杂。为此,甚至可能更容易构建自己的飞机或数据库。除了爬行动物之外,扩展高可用性的有状态系统也不是一件容易的事。

扩展任何系统都有两个主要组成部分:

  1. 添加或删除系统将在其上运行的基础结构,以及
  2. 确保系统知道如何处理自己添加和删除的其他实例。

大多数无状态系统(例如Web服务器)都是在无需了解对等对象的情况下创建的。有状态的系统(包括CockroachDB之类的数据库)必须与它们的对等实例进行协调,并在数据周围进行重新整理。幸运的是,CockroachDB处理数据的重新分发和复制。棘手的部分是通过确保数据和实例跨许多故障域(可用性区域)分布,能够容忍这些操作期间的故障。

Kubernetes的职责之一是将“资源”(例如磁盘或容器)放入群集中,并满足它们所要求的约束。例如:“我必须在可用区中 A”(请参见 在多个区域中运行),或“我不能与其他Pod放在同一节点上”(请参见 亲和力和反亲和力)。

除了这些限制之外,Kubernetes还提供 状态集 为Pod提供身份以及“遵循”这些已标识Pod的持久存储。 有状态集中的身份由Pod名称末尾的递增整数处理。重要的是要注意,该整数必须始终是连续的:在StatefulSet中,如果容器1和3存在,则容器2也必须存在。

在幕后,CockroachCloud将CockroachDB的每个区域作为StatefulSet部署在其自己的Kubernetes集群中-请参阅 在单个Kubernetes集群中编排CockroachDB. 在本文中,我将研究一个单独的区域,一个StatefulSet和一个Kubernetes集群,该集群分布在至少三个可用性区域中。

一个三节点的CockroachCloud集群如下所示:

3节点,多区域蟑螂群集

在向集群添加其他资源时,我们还将它们分布在区域之间。为了获得最快的用户体验,我们同时添加了所有Kubernetes节点,然后扩展了StatefulSet。

阶段说明:将Kubernetes节点添加到多区域cockroachdb集群中

请注意,无论Pod分配给Kubernetes节点的顺序如何,都满足反亲和性。在此示例中,分别将Pod 0、1和2分配给区域A,B和C,但分别将Pod 3和4以不同的顺序分配给区域B和A。由于吊舱仍放置在不同的区域中,因此仍然可以满足抗亲和力要求。

为了从集群中删除资源,我们以相反的顺序执行这些操作。

我们首先缩小StatefulSet,然后从群集中删除所有缺少CockroachDB pod的节点。

阶段图示:在Kubernetes中缩小多区域cockroachdb集群中的Pod

现在,请记住StatefulSet中的Pod n must have ids in the range [0,n). When scaling down a 有状态集 by m,Kubernetes删除 m 豆荚,从最高的标准开始向最低的标准发展, 相反,它们被添加. 考虑以下群集拓扑:

插图:cockroachdb集群:6个节点分布在3个可用区中

当从该群集中删除序号5到3时,有状态集将在所有3个可用性区域中继续存在。

插图:从6节点3区域cockroachdb集群中删除3节点

但是,Kubernetes的调度程序没有 保证 如我们最初预期的那样,将其放置在上方。

我们对以下方面的综合了解是导致这种误解的原因。

考虑以下拓扑:

插图:分布在3个可用区中的6节点cockroachdb集群

这些吊舱是按顺序创建的,它们分布在集群中的所有可用性区域中。当序数5至3终止时,此群集将失去在C区的存在!

插图:终止在6节点群集中的3个节点,分布在3个可用区中,其中在同一可用区中的2/2个节点被终止,从而消除了AZ

更糟糕的是,当时我们的自动化将删除节点A-2,B-2和C-2。使CRDB-1处于非计划状态,因为持久卷仅在最初创建它们的区域中可用。

为了更正后一个问题,我们现在采用“狩猎和啄食”的方法从群集中删除计算机。不会从群集中盲目删除Kubernetes节点,而只会删除没有CockroachDB pod的节点。更艰巨的任务是打乱Kubernetes调度程序。

一次头脑风暴会议为我们提供了3个选择:

1.升级到kubernetes 1.18并利用Pod拓扑扩展约束

虽然这似乎是一个完美的解决方案,但在编写本文时,公共云中的两个最常见的托管Kubernetes服务(EKS和GKE)尚无法使用Kubernetes 1.18。 Furthermore, 吊舱拓扑扩展约束 仍然是 1.18中的beta功能 这意味着它 不能保证可在托管集群中使用 即使v1.18可用。 整个工作令人联想到检查 caniuse.com 当Internet Explorer 8仍然存在时。

2.部署状态集 每个区域.

与其在所有可用区域上分布一个StatefulSet,不如在每个区域具有节点亲和力的单个StatefulSet将允许对我们的区域拓扑进行手动控制。 过去,我们的团队一直将此视为一种选择,这使其特别具有吸引力。 最终,我们决定放弃此选项,因为这将需要对我们的代码库进行大修,并且在现有客户集群上执行迁移将是一项同样艰巨的任务。

3.编写一个自定义的Kubernetes调度程序。

多亏了一个例子 凯尔西高塔 和来自的博客文章 万岁云,我们决定先潜水再写自己的 自定义Kubernetes调度程序. 部署并运行概念验证后,我们很快发现Kubernetes的调度程序还负责将持久卷映射到其调度的Pod。 The output of kubectl get events 让我们相信还有另外一个系统在起作用。 在寻找负责存储声明映射的组件的过程中,我们发现了 kube-scheduler插件系统. 我们的 next POC was a Filter plugin that determined the appropriate availability zone by pod ordinal, and it worked flawlessly!

我们的 自定义调度程序插件 是开源的,可在我们所有的CockroachCloud集群中运行。 控制StatefulSet Pod的调度方式使我们充满信心地进行扩展。 一旦GKE和EKS中提供了Pod拓扑扩展约束,我们可能会考虑淘汰我们的插件,但是维护开销却非常低。 Better still: the plugin's implementation is orthogonal to our business logic. Deploying it, or retiring it for that matter, is as simple as changing the schedulerName field in our 有状态集 definitions.


克里斯·塞托 是Cockroach Labs的软件工程师,致力于他们的Kubernetes自动化 蟑螂云,CockroachDB。