有些人说,有了容器,Linux 发行版不再重要。像无发行版(distroless)和空白(scratch)容器这样的替代方法似乎风靡一时。看起来,我们在考虑和做出技术决策时,更多的是基于时尚感和即时的情感满足,而不是认真思考我们选择的次要影响。我们应该问这样的问题:这些选择将如何影响六个月后的维护?工程上的权衡是什么?这种范式转变如何影响我们大规模的构建系统?
眼看着这种情况发生,我感到沮丧。如果我们忘记工程是一场零和博弈,其中包含可衡量的权衡——不同方法的优缺点、成本和收益——我们就是在亏待自己,亏待我们的雇主,也亏待最终将维护我们代码的同事。最后,我们不欣赏维护人员(向维护人员致敬!)的工作,也是在亏待他们。
理解问题
为了理解这个问题,我们必须探究最初我们为什么要使用 Linux 发行版。我将原因归为两大类:内核和其他软件包。编译内核实际上相当容易。Slackware 和 Gentoo(我仍然对它们怀有美好的回忆)教会了我们这一点。
另一方面,构建一个可用的 Linux 系统所需的庞大数量的开发和运行时软件的打包工作可能令人望而生畏。此外,确保数百万种软件包的排列组合可以安装并协同工作的唯一方法是使用旧的范式:将它们编译并作为一个整体(即 Linux 发行版)交付。那么,为什么 Linux 发行版要将内核和所有软件包一起编译呢?原因很简单:为了确保它们协同工作。
首先,让我们谈谈内核。内核是特殊的。在没有编译内核的情况下启动 Linux 系统是一个挑战。它是 Linux 操作系统的核心,也是系统启动时我们首先依赖的东西。内核在编译时有很多不同的配置选项,这些选项会对硬件和软件的运行方式产生巨大影响。这一类中的第二个问题是,系统软件(如编译器、C 库和解释器)必须针对你在内核中构建的选项进行调整。Gentoo 以一种深刻的方式教会了我们这一点,这使得每个人都变成了一个微型发行版维护人员。
令人尴尬的是(因为我已经使用容器五年了),我必须承认,我最近才编译过内核。我需要在 RHEL 7 上启用嵌套 KVM,这样我才能在我的笔记本电脑上的 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:我正在看着你们。你们的时间也快到了,当你们进入维护模式,被过度使用,并且不受重视时。我将在大约 7 到 10 年后再次写这篇文章,可能关于 Kubernetes。
容器构建的第一性原理
从头开始构建和从基础镜像构建之间存在权衡。
从基础镜像构建
从基础镜像构建的优势在于,大多数构建操作只不过是软件包安装或更新。它依赖于 Linux 发行版中软件包维护人员的大量工作。它还具有一个优势,即六个月后——甚至 10 年后(对于 RHEL)——的补丁事件是一个运维/系统管理员事件(yum update),而不是一个开发人员事件(需要仔细检查代码以找出为什么某些函数参数不再起作用)。
让我们更深入地探讨一下。应用程序代码依赖于大量的库,从 JSON 处理库到对象关系映射器。与 Linux 内核和 Glibc 不同,这些类型的库在更改时很少考虑破坏 API 兼容性。这意味着,三年后,你的补丁事件很可能变成一个代码更改事件,而不是 yum update 事件。明白了吗?让这一点深入人心。开发人员们,如果安全团队找不到防火墙漏洞来阻止漏洞利用,你们将在凌晨 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 条评论