?作者丨Martin Heinz
編譯丨千山
在 Linux 的發(fā)展史上,各種 Linux 發(fā)行版本起了巨大的作用,正是它們加速了 Linux 的應(yīng)用。其中比較著名的便是商業(yè)公司維護(hù)的 Red Hat 系列以及社區(qū)組織維護(hù)的 Debian 系列。
在眾多的 Linux 版本中,每個(gè)版本都有自己的特點(diǎn)。今天故事的主角就是一款非商業(yè)性的通用 Linux 發(fā)行版——Alpine Linux,它是由社區(qū)開發(fā)的輕型 Linux 發(fā)行版,重點(diǎn)關(guān)注安全、性能和資源利用率。
區(qū)別于其他常見的 Linux 發(fā)行版,Alpine Linux 采用了 musl libc 和 busybox,從而減小系統(tǒng)體積,降低運(yùn)行時(shí)的資源消耗。更重要的是,盡管體積小,但 Apline 提供了完整的 Linux 環(huán)境,其存儲(chǔ)庫中還包含了大量的軟件包備選??梢苑Q得上是“小而全”的典范。
Alpine Docker 鏡像繼承了 Alpine Linux 發(fā)行版的這些優(yōu)勢。相比于其他 Docker 鏡像,Alpine 只有 5 MB 大小,并且擁有很友好的包管理機(jī)制。
在此背景下,Alpine Linux 逐漸成為當(dāng)下容器基礎(chǔ)鏡像最流行的選項(xiàng)之一。好處是顯而易見的,鏡像下載速度更快、鏡像安全性提升、占用更少磁盤空間等等,但是也有人在實(shí)踐后發(fā)現(xiàn):使用 Alpine 作為容器鏡像,也要承受一些不可避免的麻煩和風(fēng)險(xiǎn)。
1、痛苦之源:處理 DNS 的方式
Alpine 在某些情況下是一個(gè)糟糕的選擇,要理解這一點(diǎn),首先需要談?wù)?musl。
musl 是構(gòu)建在 Linux 系統(tǒng)調(diào)用 API 之上的 C 標(biāo)準(zhǔn)庫的實(shí)現(xiàn),相比其他 Linux 發(fā)行版(如Ubuntu)使用的 glibc 更輕量級(jí)、更快、更簡單。
這兩種實(shí)現(xiàn)在大多數(shù)情況下都是可替換的。這就是為什么在大多數(shù)情況下,你可以從 Ubuntu 切換到 Alpine,而不會(huì)注意到任何不同。
但是再微小的差異也可能導(dǎo)致悲劇。其中主要的問題源于 musl 處理 DNS 的方式,更具體地說,musl(在設(shè)計(jì)上)不支持 DNS-over- tcp。
通常你不會(huì)注意到這個(gè)區(qū)別,因?yàn)榇蠖鄶?shù)情況下,一個(gè) UDP 數(shù)據(jù)包(512字節(jié))就足以解析主機(jī)名……直到它不夠用了。
之前正常工作了數(shù)月的應(yīng)用程序(運(yùn)行在 Kubernetes 上),突然開始拋出一個(gè)特定(非常關(guān)鍵)主機(jī)名的“未知主機(jī)”異常。
最糟糕的是,這可能是隨機(jī)出現(xiàn)的,可能發(fā)生在任何時(shí)候,當(dāng)一些外部網(wǎng)絡(luò)變化導(dǎo)致某些特定域的分辨率需要超過單個(gè) UDP 數(shù)據(jù)包中可用的 512 字節(jié)。
如果你運(yùn)行數(shù)十個(gè)甚至數(shù)百個(gè)基于 Alpine 的微服務(wù)/應(yīng)用程序,它們都突然停止工作,唯一的解決辦法是切換到不同的 Linux 發(fā)行版,這需要重新構(gòu)建所有應(yīng)用程序并重新部署它們,那么你可能會(huì)面臨一個(gè)令人抓狂的現(xiàn)實(shí)——強(qiáng)破壞性的、持續(xù)多日的停機(jī)。
總而言之,這個(gè) DNS 問題不會(huì)在 Docker 容器中暴露出來。這只會(huì)發(fā)生在 Kubernetes 中,所以如果你在本地測試,一切都會(huì)正常工作,并且只有在將應(yīng)用程序部署到集群時(shí)才會(huì)發(fā)現(xiàn)不可修復(fù)的問題。此外,Kubernetes 文檔聲稱 DNS 問題只與“Alpine 3.3 或更早版本”相關(guān),但我在 Alpine 3.16 上也遇到了上述問題,所以無需贅言。
另外,值得一提的是,許多流行的工具,例如 nicolaka/netshoot 或 giantswarm/tiny-tools,也使用 Alpine 作為基本鏡像,前者是用于容器網(wǎng)絡(luò)問題的解決工具??梢韵胂?,當(dāng)你的故障排除工具也壞了的時(shí)候,那只能祝你好運(yùn)了。
2、交叉編譯的風(fēng)險(xiǎn)
雖然 DNS 是 musl 最常見的問題,但有更多理由需要你審慎考慮。Alpine 使用 Musl Libc 作為傳統(tǒng)的 glibc 的替代,編譯軟件的時(shí)候可能會(huì)遇到一些不可預(yù)知的問題,這一點(diǎn)會(huì)導(dǎo)致我們耗費(fèi)不少不必要的時(shí)間。任何依賴于 C 標(biāo)準(zhǔn)庫的編程語言或其庫都會(huì)受到 musl 和 glibc 之間差異的影響。
例如,對于 Python,許多流行的庫(如 NumPy 或 Cryptography)都依賴于 C 代碼進(jìn)行優(yōu)化。幸運(yùn)的是,至少對于 Numpy 這樣的一些庫,你可能會(huì)找到基于 alpine 的編譯包和相關(guān)依賴項(xiàng)。
然而,對于不太受歡迎的編譯器,你可能不得不自己編譯。這樣做真的值得嗎?在我看來,不值得。此外,即使你設(shè)法構(gòu)建了一個(gè)包含 numpy 的鏡像,其大小將是 400MB 左右,在這種情況下,因?yàn)轶w積小而使用 Alpine 的理由無疑也站不住腳了。
此外,構(gòu)建這樣一個(gè)鏡像的時(shí)間將是殘酷的。你可以自己試試,下面的 Dockerfile 構(gòu)建大約需要 10 分鐘:
顯然,類似的問題在其他語言中也會(huì)發(fā)生。例如,Node.js 使用附加組件,這些附加組件是用 C++ 編寫的,并使用 node-gyp 編譯,這些附加組件將依賴于 C 庫,因此依賴于 glibc。
另一個(gè)例子是 Golang,它的標(biāo)準(zhǔn)庫——或者更具體地說是 net/http 或 os/user 模塊——依賴于 C 庫,因此依賴于 glibc。如果應(yīng)用程序需要 CGO_ENABLED=1,即使不使用這些特定的模塊,使用 Alpine 顯然也會(huì)遇到問題。
此外,不可忽視的一點(diǎn)是,在 Docker Hub 中,大部分鏡像是沒有 Alpine 版本的,比如 Mysql 和 PHP-Apache,如果我們需要基于這些環(huán)境開發(fā),就不得不自己編寫 Alpine 版本,或者找一些第三方鏡像。
3、用什么替代
如果上述問題促使你重新考慮使用 Alpine,那么你可能想知道應(yīng)該使用什么替代。有很多選擇,它們都有一些利弊需要權(quán)衡。
Alpine 最大的吸引力在于它的體積小,所以如果你真的在乎這一點(diǎn),那么 Wolfi(例如,cgrd .dev/chainguard/Wolfi -base只有 12MB)或 Distroless 都是不錯(cuò)的選擇。
如果你正在尋找具有合理大小的通用基礎(chǔ)鏡像,而不是基于 musl,那么你可以考慮使用 Red Hat 公司的 UBI(通用基礎(chǔ)鏡像),它的“微型”版本(registry.access.redhat.com/ubi8-micro)只有 26.7MB,這也非常接近 Alpine。
選擇 Alpine 的另一個(gè)原因是它的安全性。這也與它的小尺寸有關(guān),因?yàn)樾〕叽缤ǔR馕吨俚陌?,因此漏洞也更少。在這方面,上述的 Wolfi 是一個(gè)特別好的選擇。
讓我們實(shí)事求是地說,由于 Alpine 很小而節(jié)省幾兆字節(jié)的空間并不重要,除非你要拉成千上萬次鏡像(你可能不應(yīng)該這樣做),所以使用 Ubuntu 或基于 Debian 的基本鏡像也不是一個(gè)糟糕的選擇。
可能有人知道,Docker 官方的 Debian 鏡像有個(gè) slim 版本,這個(gè)版本的大小比默認(rèn)的版本要小一倍多。
slim 顧名思義就是“瘦身版”。Debian-slim 是一個(gè)很好的折中方案,它比 Alpine 大,但也沒那么大。
有一些上層的鏡像會(huì)基于 Debian-slim 進(jìn)行編寫,比如 Python。如果我們開發(fā) Python 的項(xiàng)目,可以使用 python:slim 這個(gè)基礎(chǔ)鏡像。
另外,在 Docker 17.05 版本以后,新引入了多階段編譯(multi-stage builds) 這一概念,這將會(huì)極大地簡化所有操作。
簡單來說,多階段編譯支持我們將 Docker 鏡像的編譯分成多個(gè)“階段”。比如常見的軟件編譯的情況,我們可以將編譯階段單獨(dú)提出來,軟件編譯完成后直接將二進(jìn)制文件拷貝到一個(gè)新的基礎(chǔ)鏡像中,這樣做最大的好處就是,第二個(gè)鏡像不再包含任何編譯階段使用的中間依賴,干干凈凈明明白白。
4、結(jié)語
雖然使用 Alpine 沒有什么問題,而且它可以作為基本容器鏡像操作系統(tǒng),但就我個(gè)人而言,由于前面描述的 DNS 問題,我可能永遠(yuǎn)不會(huì)再信任它,或者任何使用 musl 的操作系統(tǒng)。
本文的重點(diǎn)不是要詆毀 Alpine。相反,這是一種預(yù)警。雖然考慮到上面列出的問題,Alpine 似乎是一個(gè)不錯(cuò)的選擇,但使用它至少是有風(fēng)險(xiǎn)的,你的決定可能是魯莽的。不過這最終取決于你計(jì)劃在哪里使用它。它滿足了很多要求,也有很多優(yōu)點(diǎn),所以如果你不擔(dān)心或不受本文所描述的問題的影響,你可能應(yīng)該繼續(xù)使用它。
綜上所述,這里的結(jié)論應(yīng)該是——在使用任何東西(無論是容器操作系統(tǒng)、框架還是庫)之前進(jìn)行合理的研究——它很受歡迎并受到好評并不一定意味著它就一定是一個(gè)好的選擇。
參考鏈接:
https://betterprogramming.pub/why-i-will-never-use-alpine-linux-ever-again-a324fd0cbfd6
https://www.ewbang.com/community/article/details/960423446.html