网站建设需要多钱,南通市做网站,做网站难还是app难,2016网站开发语言系统管理中的基础问题
打包软件一直是系统管理中的一大基础问题。它非常重要#xff0c;对系统的使用方式有着巨大影响#xff0c;甚至让包管理器成为区分操作系统的一项重要指标。
以 Windows 为例#xff1a;在很多“Linux 派”眼中#xff0c;这款操作系统最不讨喜的就…
系统管理中的基础问题
打包软件一直是系统管理中的一大基础问题。它非常重要对系统的使用方式有着巨大影响甚至让包管理器成为区分操作系统的一项重要指标。
以 Windows 为例在很多“Linux 派”眼中这款操作系统最不讨喜的就是缺乏包管理机制微软先后多次尝试引入这个概念也都没能得到广泛认可。相比之下Linux 世界中各发行版间的主要区别就集中在其管理软件 repo 的方式上。我所指的不只是 dpkb 和 rpm 之间的差异而是在强调更底层的设计例如硬性指定还是上游配置、稳定 repo 还是滚动发布。拿 RHEL 和 Arch 来说二者虽然共享绝大多数实现但管理风格却截然不同。
大多数 Linux 发行版都会明确强调某种软件应该如何打包甚至规定了多久打包一次而且这些系统也都有着一项共通理念将依赖项集中起来。应该把库声明为依赖项并把所依赖的包安装在公共位置以供链接器使用。但这也可能带来挑战因为不同的软件往往依赖于不同的库版本而各版本之间可能并不兼容。这也是在维护 Linux 发行版时最典型的老大难问题如何提供能够良好协作的软件 repo 版本。像 RHEL 这类稳定发行版的一大优势就是它们在这方面表现得相当可靠至于缺点就是这种可靠性其实是通过拒绝大部分新版本软件来实现的。
由于需要提供大量可以相互兼容的软件版本并确保发行版遵守各种构建规范例如支持自由软件等开发理念、以及配置文件布局等具体规则所以向 Linux 发行版引入新软件时往往极度麻烦且繁琐。对于软件维护人员来说他们需要面对一大堆有着特定构建和配置差异的旧版本并想办法把它们塞进发行版中去。而在发行版和软件包维护者这边则需要全盘考虑各种上游软件是否符合发行版策略并解决版本和依赖项问题。虽然行业中已经出台了一系列相关规范但具体操作仍然令人头痛庞大的工作量也几近疯狂。
这就形成了一种双输的诡异局面希望自己的软件能够广泛传播的开发者必须忍受发行版的怪癖而想要壮大自身软件生态的发行版也得顺应开发者。每个人都不开心每个人都很疲惫。
有问题自然有人尝试解决。业界已经在这方面做出了各种探索但也正是因为方法多种多样所以至今没有真正出现一种能够统领全局的解决方案。在桌面环境中常见的软件分发选项有 Flatpak、Snap 和 AppImage 等。这些系统的镜像或应用程序能够将软件及其依赖项共同打包提供一套完整的独立环境从而保证在任何发行版上都能正常工作。
但在实际使用时我自己曾不止一次对 flatpak 文件进行逆向工程以修改其中的依赖项所以说上述工具的宣传效果在日常应用时并不一定能发挥作用。但公平地讲软件在与各种要素的交互过程中难免会出现意料之外的状况——比如运行时无法正确将各种要素彼此隔离开来。视频技术栈就是个典型我们往往需要删除或替换掉包中错误的 OpenGL 库才能使其跟特定图形驱动程序共同运行。
尽管如此我还是承认上述工具的运行效果不错甚至值得进一步扩展并普遍使用。它们所依托的桌面应用形式主要强调与用户交互并通过自身界面接收用户发来的配置选项。尽管在与文件系统交互时仍然不够顺畅丝滑但这种将交互界面限制在 GUI 形式内的作法确实让沙箱变得既可行、又极具现实意义。
需要强调的是本文不会对沙箱问题做过多延伸。沙箱是一项重要的安全和稳定性保障技术但这里讨论的主要是软件打包和分发问题。毕竟沙箱软件也可以通过更传统的方式进行分发在其现代外壳之下的打包机制其实远没有人们想象的那么先进。
让我无法忍受的是什么
总而言之我想抱怨的其实是服务器端的软件运行体系。这里的软件打包方案基本只有一个Docker。当然人们偶尔也会使用 Podman 等兼容性的工具选项。
Docker 的出现被广泛视为服务器运营最佳实践的里程碑事件。尽管 Docker 是一种软件分发方式但其最初似乎主要是为了将容器编排引入大规模可扩展环境。但最终随着不断发展和思想融合Docker 成为一种面向开发者和单节点用例的常见软件分发方式。
现如今Docker 也成为 Linux 上最常见的服务器端软件分发选项。而我对它恨之入骨。
千万别误会我在这里要批评的并不是容器技术本身。容器化非常精妙、有着诸多优点虽然未必像炒作中说的那样全面碾压轻量化虚拟机但它的亮点还是非常突出。我不太确定 Docker 帮助节约的时间有没有超过对它的管理成本但公平地讲身为一名 DevOps 顾问实践经验告诉我正确运行 Docker 镜像并不算特别麻烦。
真正让我无法忍受的就是在非 DevOps 环境中盲目使用 Docker 镜像。镜像需要集中规划和管理也就是说 Docker 应该是向用户分发软件时的最小公共集应该是种最低的保障性选项。而每当看到开源服务器端软件以 Docker 镜像的形式提交过来甚至是更糟糕的 Docker Compose 栈时我的第一反应就是愤怒。跟传统 Linux 软件包分发、或者使用源代码直接构建的软件相比Docker 镜像总是需要耗费更多时间才能正常起效。
很多朋友可能不太理解Docker 不是把所有元素都独立隔离开来降低了部署难度吗确实但接下来我打算聊几个常见问题也就是“Docker 不适用的情况”。 配置
Docker 在分发中最大的问题之一就是缺少统一的配置约定。
绝大多数服务器端 Linux 软件需要读取文本文件来获取配置这种古老的方式当然有自己的问题……但至少它有着统一的框架和准则。
可 Docker 镜像就不同了。
如果大家听说过 12 因素应用原则就会意识到 Docker 镜像的最佳配置方式应该是通过环境变量。这样做的好处在于启动容器时可以在命令行上轻松实现至于缺点就是环境变量不太适合传递结构化数据而且由于大多需要通过 shell 脚本进行交互这些脚本在处理长值或复杂值也显得比较笨拙。
DevOps 环境中使用的许多 Docker 镜像确实会从环境变量中获取配置但出于前面这些现实问题它们往往要通过避免复杂配置例如假设 TLS 将被「他方」终止或者控制信息获取量来实现。而配置内容则来自网络上的数据库或服务。
不过对于大多数最终用户软件来说其配置过于复杂或冗长单靠环境变量根本无法容纳。这时他们就只能求助于配置文件即以某种方式将配置文件纳入容器的文件系统当中。Docker 倒是提供了多种操作执行方式于是不同软件包的文档会根据相关推荐而有所区别甚至经常触发关于所有权和权限的警告。
更糟糕的是很多 Docker 镜像还试图通过提供某种入口点 shell 脚本来降低配置难度这些脚本负责向容器提供更简单的文档以生成完整配置。这种抽象级别在实践中往往缺少相应的记录痕迹这就让故障排查变得更加困难。
我自己就无数次经历过软件无法正常启动的“惊喜”原因就是脚本引用了一些配置中未提供的键导致我们必须查阅 Docker 镜像构建说明和入口点脚本来反推它的启动过程。这好吗这一点也不好。
最要命的是很多配置入口点脚本还有自己的设计倾向性。别看“设计倾向性”这词好像比较中性但它的真实含义就是“除了开发者自己别人都弄不明白”。我至少有十几次都被迫自己构建 Docker 镜像版本来替换掉那些没有公开底层软件参数的入口点脚本。
更夸张的是某些 Docker 镜像甚至根本不提供任何说明文档。用户必须深入研究、四下探寻才能找出当前软件所使用的配置文件的具体位置。我认为 Docker 镜像至少也得给出最基本的 README 信息帮助我们了解打包的软件到底应该如何配置。 文件系统
Docker 提供的沙箱或者说隔离机制肯定是个优点但这也代表着它必然有着一切沙箱所面临的共同问题。
沙箱隔离机制跟 Linux 的文件系统兼容性很差哪怕不涉及 UID 行为、单纯在 Docker Compose 栈中使用命名分卷就足以引发意外。在需要使用虚拟容器跟命名分卷中的文件进行交互时比如执行备份之类的日常操作都不说故障排查这类更复杂的需求结果都很可能让人头痛欲裂。随着时间推移命名分卷得到了大幅改进但看似简单的操作在不同 Docker 版本之间仍经常出现奇怪的冲突更不用说还得考虑如何兼容 Podman 等其他工具了。
当然UID 也有自己的问题。Docker 的一大原罪就是要求以 root 身份运行软件。
没错Docker 确实提供一定程度的隔离但从纵深防御的角度来看以 root 身份公开运行用户操作仍然不是明智之举。为此我需要频繁重构软件来适应 Docker 的这种怪癖而且整个过程相当复杂。而且在稍微繁琐的环境中使用 Docker都有很大概率引发涉及 UID 分配的 NFS 难题。使用命名分卷当然能缓解这些问题但分卷本身又有自己的痛点简直是要人老命。 容器可移植性很差
对于强调分布式特性的 Docker特别是 Docker Compose来说最讽刺的还在于很多常规实践会大大影响其可移植性。
在 Docker Compose 中对网络执行的任何非默认操作都有可能导致其在网络设置比较复杂的计算机上无法正常工作。很多 Docker Compose 栈都会将那些众所周知的端口默认为可供侦听器使用。它们还会启用各种底层软件功能又不提供禁用的方法甚至用到很多在具体环境中并不可用的通用值。
就个人而言最让我恼火的就是 TLS。前文已经提到我认为 Docker 容器不应该终止掉 TLS。只有接受 TLS 连接才允许访问私钥信息。尽管长达 90 天的临时 TLS 证书和普遍懈怠的安全意识已经破坏了保障能力但我仍然认为私钥信息应该受到严密保护。这部分信息只应存储在一个位置且只能由一名主体进行访问。当然能不能实现这种安全破坏还在其次很多用户可能根本就搞不定 TLS 配置。
不少自行托管软件的朋友都会选择 SNI 或者虚拟托管的方式其中往往存在涉及多个子域的通用证书。这一切最好都能在少量、甚至是单一专用点上处理。而一旦遇到在构建中假设在各个点上单独处理 TLS 的 Docker 镜像可就倒了大霉了。即使完全不考虑 TLS我个人也永远不想把 Docker 应用容器直接暴露在互联网上在前面部署反向代理才是正确的选择。
此外Docker Compose 栈还总想要使用 ACME 为最终用户软件颁发自有证书我们得深入研究说明文档才能搞清如何禁用这一行为。 单一用途计算设备
我前面说的这些问题在业余人士开发的软件中最为常见我现在脑海中就浮现了 HomeAssistant 和 Nextcloud 这两个例子。请别误会我所说的“业余”并不质疑软件本身而是在强调普通用户的使用习惯。
遗憾的是由于 Raspberry Pi 设备的价格越来越低廉很多业余爱好者失去了那份严谨态度。可能我说的有点夸张但他们在专用硬件上运行的“自托管”软件包已经多到了荒谬的程度。在我看来软件名称中带有“pi”基本就是个危险信号代表着开发者“没考虑过在共享设备上运行需要做哪些改动”。大家可以说是我太守旧但我认为计算设备就不该只能执行一项任务特别是那些需要 24/7 全天候开机的设备。
HomeAsistant 可能就是其中最大的罪魁祸首我自己就在一台设备上通过 Docker 运行它还有其他几款应用程序。但 HomeAsistantr 明显不想跟其他软件共存每次更新后都会弹出“检测到不支持软件”的提醒。这也太过分了有必要管得这么宽吗
后来我决定尝试一下 Nextcloud大概花了两个小时想让打包的 Docker 镜像在自己的环境上正常运行。最终我决定放弃并转为手动安装结果发现它就是一款平平无奇的 PHP 应用跟十几年前的程序没有任何区别。这么简单的东西你把它打包成 Docker 镜像干什么嘛直接用 config.php 不好吗
“罪大恶极罄竹难书”
当然大家可能觉得前面的问题都是操作细节只要认真制作 Docker 镜像就能避免。没错确实可以也许 Docker 最大的问题就是它“门槛太低”了。创建 RPM 或者 Debian 软件包就有一定的技术难度即使是经验丰富的开发者需要多次尝试才能让 rpmbuild 顺利运行起来这里建议只用 copr 和 rpkg。
我的抱怨在于纯以 Docker 镜像的形式做分发往往代表着开发者并没有对自己的项目投入足够的心力、或者至少没有专门做过项目分发设计。要想让自己的成果在各种非标环境中运行起来就一定得预先考虑到可能出现的意外。
当然大家应该能明白我的用词只是种夸张和讽刺。我曾经把 Docker 誉为稳定可靠的终极解决方案只是后来发现必须得有一定的配置经验和管理水平才能实现。
写在最后
当然以上只是我的一点个人观点相信很多朋友会抱有完全不同的意见——比如我坚信 Docker Compose 是容器时代最大的错误之一。
15 年前我曾写过一篇类似的文章讲述自己在开发小型项目时在 RPM 中遇到的各种问题。Docker 最让我惊讶的一点就是它能让项目发展到很大的规划、获得企业的广泛支持同时还通过 Docker Compose 栈大大降低了分发的技术门槛。
我在前面提到的很多问题也确实源自这种“简单性”因为很多项目根本就没有固定的分布式工程人员。面对不断变化的软件发展格局他们只是想用廉价的单片机搭配上 Docker 容器技术回避掉相对更麻烦的虚拟机镜像。当然我也承认随着年纪愈长我这人说话也越来越不中听了。
原文链接
https://computer.rip/2023-11-25-the-curse-of-docker.pdf