Categories
程式開發

多平台Docker鏡像構建教程


多平台Docker鏡像構建教程 1

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變得更加容易。

QEMU集成依賴於一個Linux內核特性,該特性有個稍顯神秘的名字binfmt_misc handler。當Linux遇到其無法識別的可執行文件格式(例如,一個用於不同體系結構的文件格式)時,它將使用該處理程序檢查是否配置了什麼“用戶空間應用程序”來處理該格式(例如,模擬器或VM)。如果有,它將把可執行文件傳遞給該應用程序。

為實現這一點,我們需要在內核中註冊我們關注的平台。如果你正在使用Docker Desktop,那麼對於大多數常見平台,你就無需做這項工作。如果你正使用Linux,你可以通過運行最新的docker/binfmt鏡像,以與Docker Desktop相同的方式註冊處理程序,例如:

docker run --privileged --rm docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64

完成此操作後,你可能需要重啟Docker。如果你想要對自己想註冊的平台有更多控製或想使用更高深莫測的平台(例如PowerPC),請查看qus項目

Buildx有兩種不同的用法,但最簡單的方法可能是在Docker CLI上啟用實驗性特性(如果你還沒有這樣做的話),編輯~/.docker/config.json文件,使其包含以下內容:

{
    ...
     "experimental": “enabled”
}

你現在應該能運行docker buildx ls,並得到類似以下的輸出:

$ docker buildx ls
NAME/NODE     DRIVER/ENDPOINT             STATUS   PLATFORMS
default       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:buster
CMD uname -m

如果我們正常構建,運行以下命令:

$ docker buildx build -t local-build .
…
$ docker run --rm local-build
x86_64

但是,如果我們顯式指定構建針對的平台,則執行以下命令:

$ docker buildx build --platform linux/arm/v7 -t arm-build .
…
$ docker run --rm arm-build
armv7l

成功!我們已經成功地在x86_64筆記本上構建和運行了armv7鏡像,並且只做了很少工作。這種技術很有效,但對於更複雜的構建,你可能會發現它運行太慢,或者遇到QEMU中的Bug。在這些情況下,有必要研究一下是否可以交叉編譯你的鏡像。

交叉編譯

一些編譯器能為foreign platforms生成二進制代碼,最著名的包括Go和Rust。通過Trow註冊中心項目,我們發現,交叉編譯是為其他平台創建鏡像最快、最可靠的方法。例如,這裡是Trow armv7鏡像的Dockerfile。最重要的一行是:

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:armv7
Created manifest list docker.io/amouat/arch-test:blog
$ docker manifest push amouat/arch-test:blog
sha256:039dd768fc0758fbe82e3296d40b45f71fd69768f21bb9e0da02d0fb28c67648

現在,Docker將拉取並運行適合當前平台的鏡像:

$ docker run amouat/arch-test:blog
Unable to find image 'amouat/arch-test:blog' locally
blog: Pulling from amouat/arch-test
Digest: sha256:039dd768fc0758fbe82e3296d40b45f71fd69768f21bb9e0da02d0fb28c67648
Status: Downloaded newer image for amouat/arch-test:blog
x86_64

有樹莓派的讀者可以試著運行這個鏡像,並確認它確實能在那個平台上工作!
回顧一下:並不是Docker鏡像的所有用戶都運行amd64。使用buildx和QEMU,只需額外少量工作就可以為這些用戶提供支持。

英文原文:

Multi-Platform Docker Builds