多平臺Docker鏡像構建教程

2022-01-07 InfoQ 架構頭條

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 👇

相關焦點

  • 理解Docker的多階段鏡像構建
    對於已經接納和使用Docker技術在日常開發工作中的開發者而言,構建Docker鏡像已經是家常便飯。但這是否意味著Docker的image構建機制已經相對完美了呢?不是的,Docker官方依舊在持續優化鏡像構建機制。這不,從今年發布的Docker 17.05版本起,Docker開始支持容器鏡像的多階段構建(multi-stage build)了。什麼是鏡像多階段構建呢?
  • Docker系列教程04-Docker構建鏡像的三種方式
    簡介創建鏡像的方法主要有三種:基於已有鏡像的容器創建、基於本地模板導入、基於Dockerfile創建。今天就逐一講述為大家講述,如何構建屬於自己的docker鏡像。1、基於容器構建鏡像基於已有容器構建鏡像主要是通過docker commit命令來構建新的鏡像,語法規則如下:docker commit 語法下面將演示下如何基於已有容器構建鏡像1、首先,創建一個容器,並在其容器內創建一個test文件,之後退出。
  • Maven構建Docker鏡像
    SpringBoot應構建Docker鏡像 本文主要介紹使用Maven將SpringBoot應用打包成Docker鏡像,並上傳到私有鏡像倉庫Docker Registry.鏡像倉庫Docker Registry的搭建 1.Pull Registry鏡像docker pull registry:22.鏡像倉庫的容器實例docker run -d -p 5000:5000 --restart=always --name registry
  • Dockerfile構建beego鏡像
    Beego是Go語言下用戶使用比較多的Web框架,國人開發,中文文檔完善,本文通過Docker搭建一個簡單的Beego鏡像,記錄一次學習的摸索流程。
  • Docker 鏡像如何做到「一次構建,到處運行」?
    本文將主要介紹 Docker 19.03 如何簡化支持多硬體平臺鏡像的構建。在每個黑客的職業生涯中總有這麼一個時刻需要為另一種 CPU 架構編譯應用程式。這種場景可能出現在為樹莓派項目編譯應用程式,為嵌入式設備創建自定義鏡像,或者讓自己的軟體支持不同平臺。
  • Docker 鏡像的構建原理和四種製作鏡像方式
    Docker 鏡像的構建原理和方式Docker構建鏡像的方式有多種,先介紹下最常用的兩種通過docker commit命令,基於一個已存在的容器構建出鏡像。編寫 Dockerfile 文件,並使用docker build命令來構建鏡像。
  • Spring Boot 分層構建 Docker 鏡像實戰
    要想了解這些層具體是什麼,那得知道如何構建 Docker 鏡像了。平時我們構建 Docker 鏡像時候,都是編寫 Dockerfile 腳本,然後使用 Docker 鏡像構建命令,按照腳本一行行執行構建。
  • 【超全教程】SpringBoot 2.3.x 分層構建 Docker 鏡像實踐
    目錄SpringBoot 2.3.x 新增對分層的支持普通鏡像構建腳本文件 dockerfile-number分層鏡像構建腳本文件 dockerfile-layer系統環境:Open JDK 基礎鏡像版本:openjdk:8u275私有的 Harbor 鏡像倉庫:自建 Harbor
  • 使用Gradle構建SpringBoot項目Docker鏡像
    使用Gradle快速構建SpringBoot項目Docker鏡像,將鏡像推送到阿里雲遠程鏡像倉庫。
  • 構建安全可靠、最小化的 Docker 鏡像
    2.3 鏡像的存儲現在我們在這臺新的 docker-machine上構建一個 1.2中所描述的 Docker鏡像: busybox:autobuild。除了通過將多命令通過 &&連接到一個構建指令外,在 Docker鏡像的構建過程中,還可以通過 --squash的方式,開啟鏡像層的壓縮功能,將多個變化的鏡像層,壓縮成一個新的鏡像層。
  • 基於Dockerfile構建容器鏡像的最佳實踐
    任何鏡像都需要有一個基礎鏡像,那麼問題來了,就好比是先有雞還是先有蛋的問題,基礎鏡像的「祖宗」是什麼呢?能不能在構建時不以任何鏡像為基礎呢?答案是肯定的,可以選用scratch,具體就不展開了,可以參考:baseimages[2],使用scratch鏡像的例子pausebusybox:對比scratch,多了常用的linux工具等3.3.2.2 多階段構建多階段構建非常適用於編譯性語言,簡單來說就是允許一個Dockerfile中出現多條FROM指令,只有最後一條FROM指令中指定的基礎鏡像作為本次構建鏡像的基礎鏡像
  • SpringBoot 項目構建 Docker 鏡像調優實踐
    作者:超級小豆丁http://www.mydlq.club/article/16/PS:已經在生產實踐中驗證,解決在生產環境下,網速帶寬小,每次推拉鏡像影響線上服務問題,按本文方式構建鏡像,除了第一次拉取、推送、構建鏡像慢,第二、三…次都是幾百K大小傳輸,速度非常快,構建、打包、推送幾秒內完成。
  • Docker鏡像
    Docker 鏡像是一個特殊的文件系統,除了提供容器運行時所需的程序、庫、資 源、配置等文件外,還包含了一些為運行時準備的一些配置參數(如匿名卷、環境 變量、用戶等)。鏡像不包含任何動態數據,其內容在構建之後也不會被改變。Docker運行容器前需要本地存在對應的鏡像。
  • 玩轉Docker鏡像
    busybox(只有1M多)創建一個文件夾(/tmp/foo)和一個文件(bar),該文件分配了100M大小實際上它最終什麼也沒做,我們把它構建成鏡像(構建可以參考一期[2]):docker build -t busybox:test .
  • Windows下構建Node.js的Docker Nano Server基礎鏡像
    Node.js的Docker鏡像:docker build -t node:4.4.5 .我們需要運行一個Docker容器,然後把這個目錄拷貝到宿主機的臨時目錄裡:docker run --name=node-temp node:4.4.5 node --versiondocker cp "node-temp:c:\Program Files\nodejs" nodejsdocker rm -vf node-temp第三步:拷貝部署到Nano
  • 再見 Dockerfile,擁抱新型鏡像構建技術 Buildpacks
    有一部分舊的 Docker 鏡像在 OCI 規範之前就已經存在了,它們被稱為 Docker v1 規範,與 Docker v2 規範是不兼容的。而 Docker v2 規範捐給了 OCI,構成了 OCI 規範的基礎。如今所有的容器鏡像倉庫、Kubernetes 平臺和容器運行時都是圍繞 OCI 規範建立的。
  • Docker 教程——理解 Docker 鏡像和容器的存儲路徑
    許多公司投入越來越多的精力來優化本地和遠程 Docker 容器中的開發流程,由此也帶來了諸多好處。執行以下命令可以查看 Docker 的配置信息:$ docker info...目錄中保存著各種信息,例如:容器數據、卷、構建文件、網絡文件和集群數據。
  • Docker鏡像瘦身與優化
    在實際製作鏡像的過程中,一味的合併層不可取,需要學會充分的利用Docker的緩存機制,提取公共層,加速構建。Scratch,https://hub.docker.com/_/scratchScratch是空白鏡像,一般用於基礎鏡像構建,例如Alpine鏡像的Dockerfile便是從Scratch開始的:FROM scratchADD alpine-minirootfs-20190228-x86_64.tar.gz /CMD ["/bin/sh"]
  • 優化Docker 鏡像小技巧
    在本文中,我將介紹一些經常被忽視的概念,這些概念將有助於優化Docker鏡像的開發和構建過程。讓我們從Docker構建過程開始。Docker構建是通過使用Docker CLI工具中的docker build命令觸發。docker build命令根據Dockerfile文件中指定的指令構建Docker鏡像。Dockerfile是一個文本文檔,其中包含用戶組裝鏡像所有的有序命令。
  • 【Docker】系列教程01-使用Docker鏡像
    當同一個鏡像擁有多個標籤時,docker rmi命令只是刪除該鏡像的一個標籤副本,並不影響鏡像文件。2)使用鏡像ID刪除鏡像 docker rmi 命令後跟鏡像ID的前綴即可匹配刪除docker pull ubuntu:18.043、列出本地鏡像列表docker images4、查看鏡像詳細信息docker inspect ubuntu:18.045、查看鏡像歷史信息docker history ubuntu