有些人认为,使用容器后,Linux 发行版不再重要。像无发行版容器 (distroless) 和 scratch 容器这样的替代方法似乎风靡一时。但看起来我们在考虑和制定技术决策时,更多是基于时尚感和即时的情感满足,而不是认真思考我们选择的次要影响。我们应该问这样的问题:这些选择将如何在六个月后的维护中产生影响?工程方面的权衡是什么?这种范式转变将如何大规模地影响我们的构建系统?
看到这种情况令人沮丧。如果我们忘记了工程是一场零和博弈,其中存在可衡量的权衡——不同方法的优缺点、成本和收益——那么我们就是在损害自己、损害雇主,也损害最终维护我们代码的同事。最后,我们没有欣赏维护人员所做的工作,也是对所有维护人员(向维护人员致敬!)的一种损害。
理解问题
为了理解问题,我们必须调查我们最初为什么开始使用 Linux 发行版。我将原因归为两大类:内核和其他软件包。实际上,编译内核相当容易。Slackware 和 Gentoo(我仍然对它们情有独钟)教会了我们这一点。
另一方面,为可用的 Linux 系统打包大量的开发和运行时软件可能令人生畏。此外,确保数百万种软件包组合可以安装并协同工作的唯一方法是使用旧的范式:编译它,然后作为一个整体(即 Linux 发行版)一起发布。那么,Linux 发行版为什么要将内核和所有软件包一起编译呢?原因很简单:为了确保它们协同工作。
首先,我们来谈谈内核。内核是特殊的。在没有编译内核的情况下启动 Linux 系统有点困难。它是 Linux 操作系统的核心,也是系统启动时我们首先依赖的东西。内核在编译时有很多不同的配置选项,这些选项会对硬件和软件的运行方式产生巨大影响。这一类别的第二个问题是,系统软件(如编译器、C 库和解释器)必须针对你在内核中构建的选项进行调整。Gentoo 以一种切身体验的方式教会了我们这一点,这让每个人都变成了微型发行版维护者。
令人尴尬的是(因为我已经使用容器五年了),我必须承认,我最近才编译过内核。我必须让嵌套 KVM 在 RHEL 7 上工作,这样我才能在笔记本电脑的 KVM 虚拟机中运行 OpenShift on OpenStack 虚拟机,以及 我们的容器开发工具包 (CDK)。#justsayin 毋庸置疑,当时我在全新的 4.X 内核上启动了 RHEL7。像任何优秀的系统管理员一样,我有点担心我错过了一些重要的配置选项和补丁。当然,我确实错过了一些东西。睡眠模式停止正常工作,我的扩展坞停止正常工作,并且还有许多其他小的、随机的错误。但它确实工作得足够好,可以在我的笔记本电脑上的单个 KVM 虚拟机中现场演示 OpenShift on OpenStack。拜托,这有点有趣,对吧?但跑题了……
现在,我们来谈谈所有其他软件包。虽然内核和相关的系统软件可能难以编译,但从工作负载的角度来看,更大的问题是编译成千上万个软件包,以提供可用的 Linux 系统。每个软件包都需要主题专业知识。有些软件只需要运行三个命令:./configure、make 和 make install。其他软件则需要大量的主题专业知识,从添加用户和在 etc 中配置特定默认值,到运行安装后脚本和添加 systemd 单元文件。对于您可能使用的数千种不同软件,所需的技能组合对于任何一个人来说都是令人生畏的。但是,如果您想要一个可用的系统,并且能够随时尝试新软件,那么您必须先学习如何编译和安装新软件,然后才能开始学习如何使用它。这就是没有 Linux 发行版的 Linux。这就是当您放弃 Linux 发行版时所同意的工程问题。
关键在于,您必须将所有东西构建在一起,以确保它们以合理的可靠性协同工作,并且构建一组可用的软件包需要大量的知识。这比任何单个开发人员或系统管理员合理学习和保留的知识还要多。我描述的每个问题都适用于您的 容器主机(内核和系统软件)和 容器镜像(系统软件和所有其他软件包)——请注意重叠之处;容器镜像中也有编译器、C 库、解释器和 JVM。
解决方案
您已经知道这一点,但 Linux 发行版是解决方案。停止阅读,给您最近的软件包维护者(再次,向维护者致敬!)发送一张电子贺卡(等等,我是否暴露了我的年龄?)。但说真的,这些人做了大量的工作,而且真的被低估了。Kubernetes、Istio、Prometheus 和 Knative:我正在看着你们。你们的时候也快到了,到那时你们将处于维护模式、被过度使用和被低估。我将在大约七到十年后再次撰写这篇文章,可能关于 Kubernetes。
容器构建的首要原则
从头开始构建和从基础镜像构建都有权衡。
从基础镜像构建
从基础镜像构建的优势在于,大多数构建操作只不过是软件包安装或更新。它依赖于 Linux 发行版中软件包维护者完成的大量工作。它还具有这样的优势:六个月甚至 10 年后(使用 RHEL)的补丁事件是操作/系统管理员事件 (yum update),而不是开发人员事件(需要检查代码以找出为什么某些函数参数不再起作用)。
让我们稍微深入探讨一下。应用程序代码依赖于许多库,从 JSON 处理库到对象关系映射器。与 Linux 内核和 Glibc 不同,这些类型的库在更改时很少考虑破坏 API 兼容性。这意味着三年后,您的补丁事件很可能变成代码更改事件,而不是 yum 更新事件。明白了吗,好好想想。开发人员们,如果安全团队找不到防火墙漏洞来阻止漏洞利用,你们将在凌晨 2 点收到警报。
从基础镜像构建并非完美;它也有缺点,例如拖入的所有依赖项的大小。这几乎总是会使您的容器镜像比从头开始构建更大。另一个缺点是您不一定总是可以访问最新的上游代码。这可能会让开发人员感到沮丧,尤其是在您只想尽快完成某件事时,但与被叫醒查看一个您已经三年没想过的库相比,这还算不上什么,而上游维护人员一直在更改它。
如果您是一名 Web 开发人员,并且对我翻白眼,那么我只想对您说一个词:DevOps。这意味着您要带寻呼机,我的朋友。
从头开始构建
从头开始构建的优势在于体积非常小。当您不依赖容器中的 Linux 发行版时,您拥有很大的控制权,这意味着您可以根据自己的需求自定义一切。这是一种一流的模型,在某些用例中是有效的。另一个优势是您可以访问最新的软件包。您不必等待 Linux 发行版更新任何内容。您处于控制之中,因此您可以选择何时花费工程工作来集成新软件。
请记住,控制一切是有成本的。通常,更新到具有新特性的新库会带来不必要的 API 更改,这意味着要修复代码中的不兼容性(换句话说,就是 “剃牦牛”)。在凌晨 2 点应用程序无法工作时 “剃牦牛” 并不好玩。幸运的是,使用容器,您可以回滚并在下一个工作日 “剃牦牛”,但这仍然会占用您为业务交付新价值、为应用程序交付新功能的时间。欢迎来到系统管理员的生活。
好吧,即便如此,在某些时候,从头开始构建是有意义的。我完全承认,静态编译的 Golang 程序和 C 程序是从头开始/无发行版构建的两个不错的选择。对于这些类型的程序,每次容器构建都是一个编译事件。您仍然需要担心三年后的 API 破坏,但如果您是 Golang 公司,您应该有能力随着时间的推移解决问题。
结论
基本上,Linux 发行版做了大量工作来为您节省时间——无论是在常规 Linux 系统上还是在使用容器时。维护人员拥有的知识是巨大的,并且在没有真正被欣赏的情况下被如此多地利用。容器的采用使问题变得更加严重,因为它被进一步抽象化了。
对于容器主机,Linux 发行版为您提供了访问广泛硬件生态系统的途径,从微小的 ARM 系统到巨大的 128 CPU x86 服务器,再到云提供商的虚拟机。它们开箱即用地提供可用的容器引擎和容器运行时,因此您可以启动容器,让其他人担心如何使它们工作。
对于容器镜像,Linux 发行版为您提供了轻松访问大量项目软件的途径。即使您从头开始构建,您也可能会查看软件包维护者是如何构建和发布东西的——优秀的艺术家是优秀的窃贼——所以,不要低估这项工作。
所以,感谢 Fedora、RHEL(Frantisek,你是我的英雄)、Debian、Gentoo 以及所有其他 Linux 发行版中的所有维护人员。我感谢你们所做的工作,即使我是一个“容器人”。
8 条评论