Adrian Mouat被譽為Docker Captain,他是Container Solutions 公司的首席科學家。目前,他正開發 Trow,這是一個容器鏡像註冊中心,用於安全管理 Kubernetes 集群中的鏡像流。
當前,Docker 鏡像已經成為測試和部署新的第三方軟體的標準工具。Adrian 是開源 Trow 註冊中心的主要開發者,而Docker 鏡像則是人們安裝該工具的主要方式。如果他不提供鏡像,其他人最終也會推出他們自己的鏡像,這樣會導致重複"造輪子」,並產生維護問題。
默認情況下,我們創建的Docker 鏡像運行在 linux/amd64 平臺上。它適用於大多數的開發機器和雲提供商,但卻忽略其他平臺的用戶。這個群體很龐大——想想基於樹莓派的家庭實驗室、生產物聯網設備的公司、運行在 IBM 大型機上的組織以及使用低功耗 arm64 晶片的雲。
一般來說,這些平臺的用戶通常會構建自己的鏡像或尋找其他解決方案。
那麼,你該如何為這些平臺構建鏡像?最明顯的方法是在目標平臺上構建鏡像。這適用於很多情況。但是如果你的目標是 s390x,我希望你有可以使用的 IBM 大型機。更常見的平臺,比如樹莓派、物聯網設備通常電量有限,速度慢或無法構建鏡像。
我們該怎麼做?有兩個選項:1. 目標平臺仿真,2. 交叉編譯。有趣的是,我發現有種方法可以將這兩個選項結合的效果最好。
讓我們從第一個選項——仿真開始。有一個很不錯的項目叫 QEMU,它可以模擬很多平臺。隨著最近 buildx 的預覽,將 QEMU 用於 Docker 變得更加容易。
https://www.qemu.org/
https://github.com/docker/buildx
QEMU 集成依賴於一個 Linux 內核特性,該特性有個稍顯神秘的名字 binfmt_misc handler。當 Linux 遇到其無法識別的可執行文件格式(例如,一個用於不同體系結構的文件格式)時,它將使用該處理程序檢查是否配置了什麼「用戶空間應用程式」來處理該格式(例如,模擬器或 VM)。如果有,它將把可執行文件傳遞給該應用程式。
為實現這一點,我們需要在內核中註冊我們關注的平臺。如果你正在使用 Docker Desktop,那麼對於大多數常見平臺,你就無需做這項工作。如果你正使用 Linux,你可以通過運行最新的docker/binfmt鏡像,以與 Docker Desktop 相同的方式註冊處理程序,例如:
docker run --privileged --rm docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64完成此操作後,你可能需要重啟 Docker。如果你想要對自己想註冊的平臺有更多控制或想使用更高深莫測的平臺(例如 PowerPC),請查看 qus 項目。
https://github.com/dbhi/qus
Buildx 有兩種不同的用法,但最簡單的方法可能是在 Docker CLI 上啟用實驗性特性(如果你還沒有這樣做的話),編輯~/.docker/config.json文件,使其包含以下內容:
{ ... "experimental": 「enabled」}你現在應該能運行docker buildx ls,並得到類似以下的輸出:
$ docker buildx lsNAME/NODE DRIVER/ENDPOINT STATUS PLATFORMSdefault docker default default running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6讓我們為另一個平臺構建一個鏡像,從 Dockerfile 開始:
FROM debian:busterCMD uname -m如果我們正常構建,運行以下命令:
$ docker buildx build -t local-build .…$ docker run --rm local-buildx86_64但是,如果我們顯式指定構建針對的平臺,則執行以下命令:
$ docker buildx build --platform linux/arm/v7 -t arm-build .…$ docker run --rm arm-buildarmv7l成功!我們已經成功地在 x86_64 筆記本上構建和運行了 armv7 鏡像,並且只做了很少工作。這種技術很有效,但對於更複雜的構建,你可能會發現它運行太慢,或者遇到 QEMU 中的 Bug。在這些情況下,有必要研究一下是否可以交叉編譯你的鏡像。
一些編譯器能為 foreign platforms 生成二進位代碼,最著名的包括 Go 和 Rust。通過 Trow 註冊中心項目,我們發現,交叉編譯是為其他平臺創建鏡像最快、最可靠的方法。例如,這裡是 Trow armv7 鏡像的 Dockerfile。
https://github.com/ContainerSolutions/trow/blob/master/docker/Dockerfile.armv7
最重要的一行是:
RUN cargo build --target armv7-unknown-linux-gnueabihf -Z unstable-options --out-dir ./out它明確告訴 Rust,我們希望二進位文件在哪個平臺上運行。然後,我們可以使用多級構建將這個二進位文件複製到目標體系結構的基本鏡像中(如果是靜態編譯,也能使用 scratch),這樣就完成了。然而,對於 Trow 註冊中心,我想在最終的鏡像中設置更多東西,所以最後階段實際上開始於:
FROM --platform=linux/arm/v7 debian:stable-slim因此,我實際上混合使用了仿真和交叉編譯——交叉編譯用來創建二進位文件,仿真用來運行和配置最終的鏡像。
在上面關於仿真的建議中,你可能已經注意到:我們使用--platform參數來設置構建平臺,但是我們在 FROM 行中將鏡像指定為 debian:buster。這看起來似乎沒有意義——平臺當然依賴於基本鏡像以及它是如何構建的,而不是用戶之後的決定。
這樣做是因為 Docker 使用了一種叫做清單列表的東西。對於給定鏡像,這些列表包含指向不同體系結構鏡像的指針。因為官方的 debian 鏡像有一個定義好的清單列表,當我在筆記本上拉取這個鏡像時,我會自動獲得 amd64 鏡像,當我在樹莓派上拉取它時,我將得到 armv7 鏡像。
為了讓用戶滿意,我們可以為自己的鏡像創建清單。如果我們回到前面的例子,首先我們需要重新構建並將鏡像推送到一個鏡像庫:
$ docker buildx build --platform linux/arm/v7 -t amouat/arch-test:armv7 .…$ docker push amouat/arch-test:armv7…$ docker buildx build -t amouat/arch-test:amd64 .…$ docker push amouat/arch-test:amd64接下來,我們創建一個清單列表指向這兩個單獨的鏡像,並推送它們:
$ docker manifest create amouat/arch-test:blog amouat/arch-test:amd64 amouat/arch-test:armv7Created manifest list docker.io/amouat/arch-test:blog$ docker manifest push amouat/arch-test:blogsha256:039dd768fc0758fbe82e3296d40b45f71fd69768f21bb9e0da02d0fb28c67648現在,Docker 將拉取並運行適合當前平臺的鏡像:
$ docker run amouat/arch-test:blogUnable to find image 'amouat/arch-test:blog' locallyblog: Pulling from amouat/arch-testDigest: sha256:039dd768fc0758fbe82e3296d40b45f71fd69768f21bb9e0da02d0fb28c67648Status: Downloaded newer image for amouat/arch-test:blogx86_64有樹莓派的讀者可以試著運行這個鏡像,並確認它確實能在那個平臺上工作!回顧一下:並不是 Docker 鏡像的所有用戶都運行 amd64。使用 buildx 和 QEMU,只需額外少量工作就可以為這些用戶提供支持。
延展閱讀:
https://www.docker.com/blog/multi-platform-docker-builds
從 0 開始構建一個億級請求的微服務架構
點個在看少個 bug 👇