Docker风光不再?Gitlab高级工程师:曾经爱用,现在却恨之入骨!

J. B. Crawford 2023-12-02 10:53:00

系统管理的基本问题:打包软件

 

打包软件是系统管理的基本问题之一。它非常重要,显著影响着系统的使用方式,以至于让包管理器成为区分操作系统的一项主要指标。

 

以Windows为例,在许多“Linux派”眼中,该操作系统最令人担忧的缺陷是缺乏包管理。尽管微软多次尝试引入这个概念,但未能获得广泛认可。在 Linux 世界中,发行版的区别主要在于管理软件存储库(Software repository,简称repo)的方法。我所指的不仅包括 dpkg 和 rpm 之间的区别,而是指更基本的底层设计,例如,硬性指定还是上游配置、稳定存储库(repo)还是滚动发布。以 RHEL 和 Arch 为例,虽然两者共享绝大多数实现,但管理风格却大相径庭。

 

大多数 Linux 发行版都会强调某种软件应该如何打包(以及多久打包一次)的理念,同时,这些系统都有一项共同的基本概念——依赖项的集中化。应该将库声明为依赖项,并将所依赖的包安装在公共位置中,以供链接器使用。

 

这可能会带来挑战:不同的软件可能依赖于不同的库版本,而这些版本可能不相兼容。这也是维护 Linux 发行版时最典型的核心挑战:如何提供协作良好的软件存储库版本。像 RHEL 这类稳定发行版的优点之一是,它们在这方面非常可靠;而缺点则是,这种可靠性目标是通过拒绝大部分新版本软件实现的。

 

由于需要大量相互兼容的软件版本,并确保发行版遵守各种构建规范(包括自由软件等开发理念,以及配置文件布局等规则),所以将新软件引入 Linux 发行版往往非常繁琐。对软件维护人员来说,意味着使用一堆具有各种特定构建和配置差异的旧版本,并想方设法将其加入发行版。对发行版和软件包维护者来说,意味着需要考虑各种上游软件是否符合发行版策略,同时解决版本和依赖项问题。虽然目前存在一些行业规范,但这些工作量如此庞大,实际操作起来仍令人抓狂。

 

由此形成了无人满意的尴尬现状:开发人员希望其开发的软件被广泛使用,就必须忍受发行版的要求;而需要壮大软件生态的发行版也必须顺应开发人员的需求。每个人都饱含怨气。

 

现有问题促使人们尝试不同的解决方案,也正因为方法多种多样,社区尚未真正整合出统一的方法。在桌面环境中,Flatpak、Snap 和 AppImage 都是非常常见的软件分发方式。这些系统的镜像或应用程序将软件及其依赖项一起打包,提供一套完整的独立环境,进而保证在任何发行版上都能正常运行。

 

然而在实际使用过程中,我不得不多次拆解 flatpaks 以修复依赖项,这表明上述工具的实际效果并不完全符合其宣传效果。公平地说,软件必须与系统要素交互的时,难免出现运行时无法将各要素正确隔离等情况。视频技术栈就是典型示例,软件包中错误的 OpenGL 库往往需要被移除或替换,才能与特定图形驱动程序一起运行。

 

尽管如此,这些工具仍然运行得相当不错,足以让它们在市场上大行其道。这些工具所使用的桌面应用性质大有裨益,强调与用户交互,并通过自身界面接收用户的配置选项。虽然它在与文件系统交互时仍然略显粗糙卡顿,但将交互界面主要限制在 GUI 形式的做法,的确使沙箱变得可行且有益。

 

需要强调的是,本文不会过多提及沙箱问题。沙箱是一项重要的安全和稳定性保障技术,但本文讨论的主要是软件打包和分发问题。毕竟沙箱软件也可以通过更传统的方式分发,它现代外壳之下的打包机制其实远不如人们想象的那么先进。

 

总之,我真正想抱怨的是服务器上的软件运行体系。这一领域中,当之无愧的首选方案是:Docker,其次则是 Podman 等兼容工具的生态系统。Docker 的发布导致人们普遍认为的服务器操作最佳实践发生惊人变化。虽然 Docker 镜像是一种分发软件的方式,最初似乎主要是为了将容器编排引入大规模可扩展环境,但随着它与 Vagrant 等人的想法融合,Docker 成为一种面向开发人员和单节点用例的常见软件分发方式。

 

Docker的问题在哪?

 

如今,Docker 是 Linux 上最广泛的服务器端软件分发方式,但我却对他恨得咬牙切齿。

 

这并非对容器技术的批评。容器化非常精妙,虽然未必像宣传的那样秒杀轻量级虚拟机,但依然具有许多优点。我不确定 Docker 为我节省的时间是否超过其管理成本,但公平地说,作为一位 DevOps 顾问,实践经验告诉我,正确运行的 Docker 镜像不会带来过高的时间成本及金钱成本。

 

近期真正让我恼火的,并非在 DevOps 环境中使用 Docker 镜像,因为某种程度上,这些镜像是集中规划和管理的。问题在于应该使用 Docker 作为向最终用户分发软件的最小公共集,更准确地说,Docker 是最低的保障性选项。

 

每当看到开源的服务器端软件以 Docker 镜像或更糟糕的 Docker Compose 栈形式提交时,我的第一反应是恼怒。与作为传统Linux 软件包分发或使用源代码构建的同类软件相比,Docker通常需要耗费更长时间才能正常运行。

 

但这是怎么一回事?Docker 不是应该将所有元素都独立隔离,降低部署难度了吗?让我们一起来看看以下的常见问题,我将其称为“Docker 不适用的情况”。

 

 
1.配置

 

Docker 分发的最大问题之一是,缺乏统一的配置约定。绝大多数服务器端 Linux 软件通过读取文本文件的古老技术来获取其配置。这当然存在问题!但至少它具备统一框架。

 

Docker 镜像则不同。如果您遵循 12因素应用原则,那么 Docker 镜像的最佳配置方式是通过环境变量。这样做的好处是,启动容器时就可以在命令行上直接实现;而缺点是环境变量不适合传送结构化数据,而且因为通常通过 shell 脚本进行交互,这些脚本对长值或复杂值的处理较为笨拙。

 

DevOps 环境中使用的许多 Docker 镜像都从环境变量中获取配置,但它们往往通过避免复杂的配置(例如假设 TLS 将被“他方”终止)或控制信息获取量,以使其更加可行。配置内容来自网络上的数据库或服务。

 

但对于大多数最终用户软件来说,其配置过于复杂或冗长,仅凭环境变量无法容纳。因此,他们通常会求助于配置文件,即以某种方式将配置文件放入容器的文件系统中。Docker 提供了多种操作执行方法,不同软件包的文档会根据其推荐的方式而有所不同,所以经常出发关于所有权和权限的警告。

 

更糟糕的是,许多 Docker 镜像试图通过提供某种入口点 shell 脚本来降低配置难度,这些脚本为容器提供更简单的文档以生成完整配置。当然,这种抽象级别在实践中通常缺少或缺失相应的记录,令故障排查更加困难。经历过多次的软件无法启动的“惊喜”,由于脚本引用了一些配置中未提供的键,所以我们必须查阅 Docker 镜像构建说明和入口点脚本来反推它的启动过程。这一点也不好。

 

当配置入口点脚本有设计倾向性时,这种情况就变得棘手了,“设计倾向性”通常是“"不适合开发者以外的任何配置”的委婉说法。至少有十几次,我不得不构建自己的 Docker 镜像版本,来替换或增强不暴露底层软件可接受参数的入口点脚本。

 

最坏的情况是,某些 Docker 镜像根本不提供任何说明文档,用户必须深入研究、四处寻找,才能找出正在运行的软件使用的实际配置文件所在位置。我认为 Docker 镜像至少应该提供基本 README 信息,以供用户配置打包软件。

 

 
2.文件系统

 

Docker 的优点之一是沙箱或隔离机制,这同时也意味着 Docker 会遇到所有沙箱的共性问题,沙盒隔离机制与 Linux 文件系统兼容性不佳。即使不涉及 UID 行为,只需要在 Docker Compose 栈中使用命名分卷就足以引发事故。当必须使用虚拟容器与命名分卷中的文件进行交互时,诸如备份之类的日常操作任务(更不用说故障排除等需求),都可能出现意外。随着时间推移,命名分卷已经获得改进,但看似简单的操作在不同 Docker 版本之间仍然可能出现奇怪的不一致。更糟糕的是,还需要考虑与 Podman 等其他工具之间的兼容性。

 

当然, UID 也有问题。Docker 的一大罪过就是以 root 身份运行软件。

 

是的,Docker提供了一定程度的隔离,但从纵深防御的角度来看,以 root 身份运行任何用户暴露的操作仍然不是一个好做法。为此,我需要频繁重构软件来适应 Docker 的这种特性,且整个过程相当复杂。而在稍微繁琐的环境中使用 Docker,都有很大概率引发涉及 UID 分配的 NFS 难题。命名分卷可以缓解这些问题,但其本身也具备痛点。

 

 
3.容器可移植性不佳

 

对强调分布式特性的 Docker(尤其是 Docker Compose)来说,讽刺至极的是,有很多常规实践会对可移植性产生负面影响——却同时也是这一方法的主要好处。

 

在 Docker Compose中对网络执行任何非默认操作,都有可能导致其在网络设置复杂的计算机上无法工作。太多 Docker Compose 堆栈会将众所周知的端口默认为可供侦听器进行使用。它们会启用底层软件功能,又不提供禁用方法,同时设定很多可能在具体环境中不可用的通用值。

 

就个人而论,最让我不快的是 TLS。正如前文提到的那样,我认为 Docker 容器不应终止 TLS。接受 TLS 连接意味着可以访问私钥信息。虽然 90 天的临时 TLS 证书和普遍懈怠的氛围已经破坏了保障能力,但私钥信息也应该受到严密保护。这部分内容只应存储在一个地方,并且只能由一名主体访问。不过,您甚至不必担心陷入这些严重的安全问题,因为TLS 的配置有点复杂,很多用户都不会设置。

 

许多自行托管软件的人都会选择某种类型的 SNI 或虚拟托管方式,其中可能存在涉及多个子域的通配符证书,这些内容最好在单个点或少量专用点上处理。一旦遇到在构建中假设在各点上单独处理 TLS 的 Docker 镜像,就令人崩溃了。即使完全不考虑 TLS,我也可能永远不会将 Docker 应用容器直接暴露到互联网上,在前面部署反向代理无疑具备更多好处。

 

除此之外, Docker Compose 栈想要使用 ACME 为最终用户颁发自有证书,你必须深入研究文档才能明白如何禁用该行为。

 

 
4.单一用途计算设备

 

所有这些抱怨在我所说的业余人士开发的软件中最为常见。我脑海中浮现的两个例子是 HomeAssistant 和 Nextcloud。我称这些软件为“业余爱好级”并非指责它们,而是为了描述普通用户的使用习惯。

 

可惜的是,软件业余爱好者已经被 Raspberry Pi 的廉价优势所迷惑,失去了应有的严谨态度。措辞有些夸张,但这个问题确实存在。很多“自托管”软件包都假定自己能在专用硬件上运行,这实在太荒谬了。在我看来,软件产品名称中包含“pi”是个很大的危险信号,即“他们不会考虑如何在共享设备上运行这个软件”。你可以说我很守旧,但我认为计算机不该只执行一项任务,尤其是那些 24*7 全天候使用的计算机。

 

HomeAssistant 可能是这里最大的罪魁祸首,因为我在 Docker 中将它与其他几个应用程序一同运行,每次更新后它都会弹出“检测到不支持软件”的维护通知。您能想象 Postfix 检测到其他软件,在日志中抱怨的情景吗?

 

最近,我决定试试 NextCloud。由于时间久远,细节有些模糊,但我记得花了约两个小时试图让一体化的 Docker 镜像在自己的环境中运行。最后,我决定放弃,转为手动安装,结果发现它只是一款普通的 PHP 应用程序,和我在 2007 年经常设置的软件没什么区别。这是我与当下年轻一代的代沟吗?直接使用 config.php 难道不会更方便?

 

 
5.简单易用背后的不足

 

当然,你可能会认为,对于认真制作 Docker 镜像的人来说,以上问题都可以避免。没错,完全正确!也许 Docker 的问题之一就是它太容易使用了。创建 RPM 或 Debian软件包需要一定的入门门槛,即使是我这样经验丰富的开发人员,也需要多次尝试才能启动 rpmbuild (建议:使用 copr 和 rpkg 即可)。

 

我抱怨的核心问题是,仅以 Docker 镜像的形式分发应用程序通常证明项目相对不成熟,或者至少是没有专人负责项目分发。在非标准环境中运行这类程序时,就必须提前考虑可能出现的意外情况。

 

Docker 曾被我誉为“可靠的终极解决方案”,然而讽刺的是,它似乎只会在更高级的配置级别上出现同样的情况。

 

写在最后

 

当然,以上都是个人观点,相信您会在某些方面有不同意见,比如我坚信 Docker Compose 是我们这个时代最大的错误之一。

 

十五年前,我曾写过一篇类似的文章,讲述自己在开发小型项目时在 RPM 中遇到的各种问题。Docker 最让我惊讶的是,它能保证项目发展到很大规模,并获得企业广泛支持,以简单的 Docker Compose 栈分发。

 

正是 Docker 这种“简单性”,导致该类项目上缺乏固定的分布式工程人员。还有一部分原因是,这类软件的发展格局正不断变化,使用廉价的单片机搭配 Docker 堆栈,比过去使用的虚拟机镜像专业性更低、更容易让人接受。更有一部分原因是,我年纪大了,脾气越发暴躁,说话不太中听。

 

网友观点

 

这篇贴文在 Hacker News 上发布后,立刻引发热烈讨论,不少网友发表了自己的看法。

 

 
网友 @chillfox

 

确切地说,Docker + Compose 是当今运行服务器软件的最佳方式。 

 

我将撰写文件保存在源代码管理中,同时具备一个 CI 服务器为任何没有第一方镜像的东西构建镜像。

 

更新也非常简单,只需更新 compose 文件顶部的固定版本(如果不使用最新版本的话),然后更新“docker-compose pull”与 “docker-compose up -d”。整个系统比我以前管理的 RedHat/Ubuntu/FreeBSD 系统稳定得多,也更容易管理(使用 Alpine Linux + ZFS 作为主机操作系统)。 

 

 
网友 @forrestthewoods 

 

这是个不受欢迎的观点:(译者注:具备包管理器的)Linux 系统错了,(缺少包管理器的)Windows 系统正确了(或者至少更接近正确)。程序不应使用集中式依赖项,程序应该传送它们的所有依赖项。运行程序应该像下载 zip、解压并单击/键入运行一样简单。

 

Docker 之所以产生,是因为构建和运行软件非常复杂,需要完整的系统镜像。事实证明 Docker 并没有真正解决这个问题!

 

 
网友@alexey-salmin

 

我认为大多数评论很大程度上都没有抓住文章的要点。作者并没有抱怨整个Docker,也没有批评它在软件部署中的使用,而软件部署通常是主要应用程序。他抱怨 Docker 被用作软件分发方法,即替代 Debian 软件包、pip 软件包、npm 软件包等。

 

因此,为了使用该软件,你需要从互联网上运行 Docker 镜像,它通常制作粗糙并且与你的基础设施不兼容。如果有可用的软件包,只需根据已有基础设施构建自己镜像,并在其中进行“安装”即可。

 

我完全同意:Docker 与 docker-compose 都是糟糕的软件分发方式,除了在演示和软件不按正常意义上分发给客户而是直接部署到客户系统的极少数情况外,都不应该使用它们。

 

但 Docker 和 docker-compose 仍然是非常好的软件部署方法。

 

>>>>

参考资料

 

  • https://computer.rip/2023-11-25-the-curse-of-docker.html

  • https://news.ycombinator.com/item?id=38418359

 

作者丨J. B. Crawford

编译丨onehunnit

*本文为dbaplus社群编译整理,如需转载请取得授权并标明出处!欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

 
最新评论
访客 2023年08月20日

230721

访客 2023年08月16日

1、导入Mongo Monitor监控工具表结构(mongo_monitor…

访客 2023年08月04日

上面提到: 在问题描述的架构图中我们可以看到,Click…

访客 2023年07月19日

PMM不香吗?

访客 2023年06月20日

如今看都很棒

活动预告