乐趣区

关于云计算:这两个原因使Kubernetes变得如此复杂

1、为什么 Kubernetes 这么难?

Anthropic 在 Kubernetes 内运行咱们的大部分零碎,因而我对该工具积攒了更多的教训,对其也更加相熟。尽管在它真的很棒,但我也的确经验了(大家会广泛经验的)其复杂性和调试的超高难度。

尽管在学习新零碎时,这些感觉相当广泛,但 Kubernetes 的确比我应用过的其余一些零碎感觉更大、更可怕、更辣手。在学习并应用它的过程中,我试图了解为什么它看起来是这样的,以及哪些设计决策和衡量导致它成为当初这样。这篇文章尝试写出两种特定的想法,并会解释为什么与 Kubernetes 一起工作有时会感到辣手。

2、Kubernetes 是一个集群操作系统

大家很容易将 Kubernetes 视为部署容器化应用程序或一些相似性能形容的零碎。尽管这可能是一个有用的视角,但我认为将 Kubernetes 视为通用集群操作系统内核会更正当。这两者之间有何区别?

传统操作系统的工作是应用一台计算机及其所有从属硬件,并公开程序可用于拜访该硬件的接口。尽管确切的细节各不相同,但通常这个界面有以下几个指标:

1)资源共享 ——咱们心愿将一台物理计算机的资源细分到多个程序中,以便在某种程度上互相隔离。

2)可移植性 ——咱们心愿在某种程度上形象底层硬件的准确细节,以便同一程序能够在不同的硬件上运行,而无需批改或仅进行小幅批改。

3)通用性 ——当咱们想出新型硬件或将新硬件插入计算机时,咱们心愿可能以增量的形式将这些硬件放入咱们的形象和接口中,最好不要大幅更改任何接口或毁坏任何不应用该硬件的现有软件。

4)整体性 ——与通用性相干,咱们心愿操作系统调解对硬件的所有拜访:软件简直不可能齐全绕过操作系统内核。软件能够应用操作系统内核设置与硬件的间接连贯,以便将来的交互间接产生(例如设置内存映射的命令管道),但初始调配和配置仍在操作系统的爱护下。

5)性能 ——与“间接编写一个专用软件,它间接运行在硬件上,并且对硬件有独占的间接拜访权”相比,咱们心愿为领有这种形象领取可承受的小性能老本。在某些状况下,咱们心愿通过提供 I / O 调度器或缓存层等优化,在实践中实现比此类零碎更高的性能。

尽管“编程的便捷性”通常是一个额定的指标,但在实践中,它往往因为上述担心而被忽视。操作系统内核通常围绕上述指标进行设计,而后编写用户空间库,将低级、通用、高性能的接口封装到更易于应用的形象中。操作系统开发人员更关怀“在我的操作系统上运行 nginx 有多快”,而不是“nginx 端口到我的操作系统的代码能短多少行?”

我认为 Kubernetes 在一个十分类似的设计空间中运行;然而,它的指标不是形象单个计算机,而是形象整个数据中心或云,或其中的很大一部分。

这种观点有帮忙的起因是,这个问题比“使在容器中部署 HTTP 应用程序成为可能”更艰难,也更广泛,它指出了 Kubernetes 如此灵便的具体起因。Kubernetes 渴望足够通用和弱小,能够在任何类型的硬件(或虚拟机实例)上部署任何类型的应用程序,而无需“绕过”或“跳开”Kubernetes 接口。

我不会试图在这里就它是否实现了这个指标(或者,它在实践中是否实现了这个指标)发表意见;只需将它视为一个要解决的问题,就能了解所遇到的许多设计决策,这样的视角是可行的。

从这个角度来看,Kubernetes 的可插拔性和可配置性可能是最大的设计抉择。一般来说,不可能做出对所有人都实用的抉择,特地是你心愿在没有昂扬的性能老本的状况下做出抉择。特地是在古代云环境中,部署的应用程序类型和硬件类型差别很大,是变动速度十分快的指标。因而,想成为所有人的万能工具,你最终须要高度可配置性,这最终会创立一个弱小的零碎,但这个零碎可能很难了解,甚至使“简略”工作变得复杂。

当然,还有另一个视角:

许多用户认为 Kubernetes 实质上是“Heroku”,也就是说,Kubernetes 实质上是一个部署应用程序的平台,形象了大多数传统的底层操作系统和分布式系统的细节。

Kubernetes 认为本人解决了更靠近“CloudFormation”的问题——在某种意义上,它心愿足以定义您的整个基础设施—它也试图以比底层云提供商或硬件通用的形式做到这一点。

3、Kubernetes 中的所有内容都是一个管制循环

大家能够设想一个十分必要的“集群操作系统”,上文中,它裸露了“分 5 个 CPU 的计算能力”或“创立新的虚构网络”等原语,这些原语反过来又支持系统外部形象中的配置更改或对 EC2 API(或其余底层云提供商)的调用。

Kubernetes 作为外围设计决策,并不是这样工作的。相同,Kubernetes 做出了外围设计决策,即所有配置都是申明性的,所有配置都是通过作为管制回路的“操作员”实现的:他们一直将所需的配置与事实状态进行比拟,而后试图采取行动使事实与所需的状态保持一致。

这是一个十分三思而行的设计抉择,而且是有充沛理由的。一般来说,任何不是设计为管制回路的零碎都会不可避免地偏离预期的配置,因而,在规模上,须要有人编写管制回路。通过内部化它们,Kubernetes 心愿容许大多数外围管制循环只编写一次,并由领域专家编写,从而更容易在它们之上构建牢靠的零碎。

对于一个实质上是分布式、为构建分布式系统而设计的零碎而言,这也是一个天然的抉择。分布式系统的定义实质是部分故障的可能性,这要求超过肯定规模的零碎可能自我修复,并在不思考部分故障的状况下收敛到正确的状态。

然而,这种设计抉择也带来了产生微小的复杂性和凌乱的可能性。以下为两个具体的例子:

1)谬误被提早。 在 Kubernetes 中创建对象(例如 pod),通常只需在配置存储中创立一个对象,断言该对象的预期存在。如果事实证明无奈理论满足该申请,要么是因为资源限度,要么是因为对象在某些方面外部不统一(援用的容器映像不存在),通常不会在创立时发现该谬误。配置创立将会进行,而后,当相干操作符唤醒并尝试实现更改时,才会创立谬误。

这种间接性使调试和推理变得更加艰难,因为你不能用“创立胜利”作为“生成的对象存在”的良好标记。这也意味着与失败相干的日志音讯或调试输入不会呈现在创建对象的流程的上下文中。

编写良好的控制器会收回 Kubernetes 事件来解释正在产生的事件,或以其余形式正文有问题的对象;但对于测试较差的控制器或更常见的故障,您可能只会在控制器本人的日志中获取日志垃圾邮件。一些更改可能波及多个控制器,会独立或联合行动,这使得跟踪某一段代码变得更加艰难。

2)运算符可能有破绽。 申明性管制环模式提供了隐含的承诺,即您作为用户无需放心如何从状态 A 到状态 B;您只需将状态 B 写入配置数据库,而后期待。当它运行良好时,这实际上是一个微小的简化。

然而,有时候从状态 A 到状态 B 是不可能的,即便状态 B 能够本人实现。或者这是可能的,但须要停机工夫。尽管这是可能的,但这是一个常见的用例,所以控制器的作者忘了实现它。

对于 Kubernetes 中的外围内置原语,您能够保障它们通过良好的测试和应用,并心愿它们能很好地工作。但当您开始增加第三方资源、治理 TLS 证书或云负载平衡器或托管数据库或内部 DNS 名称时,您会偏离惯例,所有门路的测试成果会变得不那么分明。

而且,与之前对于提早谬误的要点统一,故障模式很奥妙,而且产生在较远的地位;很难辨别“更改尚未被接管”和“更改永远不会被接管”之间的区别。

4、论断

本文始终试图防止就这些设计决策的好坏做出价值判断。因为对于 Kubernetes 何时成为什么样的有价值的零碎才是有意义的。

我发现以本人的形式对 Kubernetes 有很好的了解,并更好地了解其复杂性来自哪里,以及它正在服务的指标,这是十分有价值的。

这种剖析能够利用于当初应用的任何零碎。即便一个零碎的设计形式在以后的环境中并不现实,但出于某种原因,它总是以这种形式呈现。只有这是一个你必须与之互动、推理和决策的零碎,如果你能了解这些起因、动机和将零碎推向这一点的外部逻辑,而不是立刻将其漠视,则会有更好的应用体验。

心愿这篇文章能帮忙其余对在生产中应用 Kubernetes 不相熟、或正在思考采纳 Kubernetes 的人,帮忙大家提供一些有用的框架来解释为什么(我置信)它看起来的样子,以及对它有什么正当的冀望。

如果咱们想更细致入微,咱们能够认为它事后加载复杂性,而不是增加复杂性。这种设计让你事后解决可能长期漠视的理论问题。这是否是一个现实的抉择取决于您的指标、规模、工夫范畴和相干因素。

退出移动版