为什么我不再推荐你使用 Alpine Linux 镜像
DevOps运维实践 DevODevOps运维实践ps运维实践转自:DevOps运维实践
如今,Alpine Linux 是最流行的容器基础镜像选项之一。许多人(也许包括您)将它用于任何事情。有些人因为它的体积小而使用它,有些人因为习惯而使用它,有些人只是因为他们从一些教程中复制粘贴了一个Dockefile。然而,有很多原因导致您不应该将 Alpine 用于您的容器镜像,其中一些可能会给您带来极大的痛苦......
所有悲伤的根源
要了解是什么让 Alpine 在某些情况下成为糟糕的选择,我们首先需要谈谈musl. musl是C标准库的一个实现。它比glibc其他 Linux 发行版(例如 Ubuntu)使用的更轻量、更快和更简单。这两种实现在很大程度上是可以互换的,这就是为什么在大多数情况下您可以从 Ubuntu 切换到 Alpine 而不会注意到任何差异。
然而,微小的差异可能会导致所有的悲伤。其中一些源于musl(因此也是 Alpine)处理 DNS(它始终是 DNS)的方式,更具体地说,musl(按设计)不支持DNS-over-TCP。通常,您不会注意到这种差异,因为大多数时候单个 UDP 数据包(512 字节)就足以解析主机名......几个月突然开始为一个特定的(非常关键的)主机名抛出“未知主机”异常。最糟糕的是,当某些外部网络更改导致某些特定域的解析需要超过单个 UDP 数据包中可用的 512 字节时,这可能会随机出现。
另外也有一种其他可能触发的bug,比如在获取pod的可用cpu核数时出现异常,详见下图Caused by: java.lang.IllegalArgumentException: availableProcessors: 0 (expected: > 0)
然而实际设置:cpu request=limit=4c,java版本:8u362
这是因为alpine镜像基于musl库,并不是标准的glibc的库,与其他的标准库glibc并不兼容。因为glibc有很多额外的扩展,并且很多程序都用到了这些扩展,而 musl libc 是不包含这些扩展的。详情可以参考musl的文档。也就是说,如果想让程序跑在Alpine镜像中,必须在编译时使用musl libc作为动态库。Java库并不是完全独立于系统库的,某些Java函数最终还是会调用系统库,例如打开文件时需要调用 open(), fopen() 或它们的变体,因此 JVM 本身可能会与系统库动态链接。
通过使用 Alpine,您可以获得集群的“免费”混沌工程。
有关此特定问题的更多详细信息,请查看这篇精彩的文章:Alpine 是否正确解析 DNS?
如果你运行数十甚至数百个微服务/应用程序都基于 Alpine,并且它们突然停止工作,唯一的解决办法是切换到不同的 Linux 发行版,这需要重建所有应用程序并重新部署它们,那么你可能会面临极具破坏性的多日中断。
最后,这个 DNS 问题不会出现在 Docker 容器中。它只会发生在 Kubernetes 中,所以如果你在本地测试,一切都会正常,只有当你将应用程序部署到集群时,你才会发现无法修复的问题。此外,Kubernetes 文档声称 DNS 问题仅与“Alpine 3.3 版或更早版本”相关,但我在 Alpine 3.16 上遇到了上述问题,所以请看图。
作为一个额外的好处,许多流行的工具也使用 Alpine 作为基础镜像,例如nicolaka/netshoot或giantswarm/tiny-tools,前者专门针对网络故障排除。当您的故障排除工具也损坏时,祝您在网络故障排除方面好运。
超越 DNS 问题
虽然 DNS 是最常见的问题musl,但有更多理由重新考虑使用它。任何依赖 C 标准库的编程语言或其库都会受到 和 之间的所有差异的musl影响glibc。
例如,对于 Python,许多流行的库(如 NumPy 或Cryptography)都依赖 C 代码进行优化。幸运的是,至少对于一些像 Numpy 这样的库,你很可能会找到基于 Alpine 的编译包和相关依赖项。然而,对于不太受欢迎的那些,您可能必须自己编译它们,这真的值得吗?在我看来……不。此外,即使您设法构建了一个包含 example 的图像numpy,它的大小也将达到 ~400MB,此时使用 Alpine 因为它的小尺寸并没有多大帮助。
另外,这样的镜像构建时间会非常长,你可以自己试试,下面Dockerfile构建需要将近 10 分钟:
FROM python:3.11-alpine
RUN apk --update add gcc build-base
RUN pip install --no-cache-dir numpy
显然,类似的问题也可能发生在其他语言中。例如,Node.js 使用用 C++ 编写并node-gyp使用glibc.
另一个例子是 Golang,它的标准库——或者更具体地说net/http是os/user模块——依赖于 C 库,因此依赖于glibc. 即使您不使用那些特定的模块,如果您的应用程序需要CGO_ENABLED=1,您显然也会遇到 Alpine 的问题。
用什么代替?
如果上述问题促使您重新考虑使用 Alpine,那么您可能想知道应该使用什么。有很多选择,它们都有一些优点/缺点/权衡。
Alpine 最大的吸引力在于它的体积小,所以如果你真的很在意这一点,那么Wolfi(例如cgr.dev/chainguard/wolfi-base只有 12MB)或Distroless是不错的选择。
如果您正在寻找不基于 的合理大小的通用基础映像musl,那么您可以考虑使用Red Hat 制作的UBI(通用基础映像) ,它的“微型”版本 ( )只有 26.7MB,registry.access.redhat.com/ubi8-micro这也是相当接近阿尔卑斯山。
选择 Alpine 的另一个原因是安全性。这也与它的小尺寸有关,因为小尺寸通常意味着更少的包,因此也意味着更少的漏洞。上述 Wolfi 图像在这方面是特别好的选择。
不过,说实话,由于 Alpine 很小,所以节省几兆字节的空间可能并不重要,除非你拉图像数千次(你可能无论如何都不应该这样做),所以使用 Ubuntu 或 Debian -based 的基础图像也不是一个糟糕的选择。
结论
虽然使用 Alpine 没有任何问题,而且它可以musl成为一个很棒的基础容器镜像操作系统,但由于前面描述的 DNS 问题,我——就个人而言——可能永远不会再信任它,或者任何用于此目的的东西。
这篇文章的重点不是要对 Alpine 说废话,而是要起到警告的作用。虽然 Alpine 似乎是一个不错的选择 - 考虑到上面列出的问题 - 使用它至少是有风险的,可能是鲁莽的,这取决于你打算在哪里使用它。然而,它符合很多条件并且有很多优点,所以如果您不担心或不受本文所述问题的影响,您应该继续使用它。
话虽如此,这里的要点应该是——在承诺使用任何东西(无论是容器操作系统、框架、库)之前做一个合理数量的研究——它很受欢迎和广受好评并不一定意味着它自动是一个好的选择。
END
官方站点:www.linuxprobe.com
Linux命令大全:www.linuxcool.com
刘遄老师QQ:5604215
Linux技术交流群:3861509
(新群,火热加群中……)
想要学习Linux系统的读者可以点击"阅读原文"按钮来了解书籍《Linux就该这么学》,同时也非常适合专业的运维人员阅读,成为辅助您工作的高价值工具书!
微信扫码关注该文公众号作者