原文連接:使用 buildx 構建多平臺 Docker 鏡像linux
在工做和生活中,咱們可能常常須要將某個程序跑在不一樣的 CPU 架構上,好比讓某些不可描述的軟件運行在樹莓派或嵌入式路由器設備上。特別是 Docker 席捲全球以後,咱們能夠輕鬆地在 ARM 設備上經過容器部署各類好玩的應用,而不用在乎各類系統的差別性。git
可是想要跨平臺構建 Docker 鏡像可不是一件輕鬆的活,要麼到不一樣 CPU 架構的系統上所有構建一遍,要麼就得在當前系統上經過虛擬化技術模擬不一樣的 CPU 架構,最後可能還要想辦法合併鏡像,費力不討好。github
不過值得慶幸的是,Docker 19.03
引入了一個新的實驗性插件,該插件使得跨平臺構建 Docker 鏡像比以往更加容易了。在介紹這個新特性以前,咱們先來了解一下跨 CPU 架構構建程序的基礎知識。golang
先來快速回顧一下當前跨 CPU 架構編譯程序的不一樣方法。docker
若是你可以訪問目標 CPU 架構的系統,而且該操做系統支持運行構建所需的各類工具,那麼你能夠直接在目標系統上編譯程序。json
以構建 Docker 鏡像爲例,你能夠在樹莓派上安裝 Docker,而後在樹莓派上經過 Dockerfile
直接構建 arm 平臺的鏡像。bootstrap
若是沒法訪問目標 CPU 架構的系統該怎麼辦?有沒有辦法經過某種方式直接在當前系統上構建目標 CPU 架構的程序?請看下文...bash
還記得咱們小時候在各類網吧檯球室之類的場合玩的街機遊戲嗎?放張圖給大家回憶一下:微信
若是如今咱們想從新體驗之前玩過的街機遊戲該怎麼辦?這時候就須要用到模擬器(Emulator)了。藉助模擬器,咱們可讓時光倒流,體驗經典遊戲的樂趣。架構
模擬器除了能夠用來玩遊戲以外,還能夠用來跨 CPU 架構構建程序。最經常使用的模擬器是開源的 QEMU,QEMU 支持許多常見的 CPU 架構,包括 ARM
、Power-PC
和 RISC-V
等。經過模擬一個完整的操做系統,能夠建立通用的 ARM 虛擬機,該虛擬機能夠引導 Linux,設置開發環境,也能夠在虛擬機內編譯程序。
然而,模擬整個操做系統仍是有點浪費,由於在這種模式下,QEMU 將會模擬整個系統,包括計時器、內存控制器、總線控制器等硬件。但編譯程序根本不須要關心這些,還能夠再精簡些。
在 Linux 上,QEMU
除了能夠模擬完整的操做系統以外,還有另一種模式叫用戶態模式
(User mod)。該模式下 QEMU 將經過 binfmt_misc 在 Linux 內核中註冊一個二進制轉換處理程序,並在程序運行時動態翻譯二進制文件,根據須要將系統調用從目標 CPU 架構轉換爲當前系統的 CPU 架構。最終的效果看起來就像在本地運行目標 CPU 架構的二進制文件。
經過 QEMU 的用戶態模式,咱們能夠建立輕量級的虛擬機(chroot 或容器),而後在虛擬機系統中編譯程序,和本地編譯同樣簡單輕鬆。後面咱們就會看到,跨平臺構建 Docker 鏡像用的就是這個方法。
最後介紹一種嵌入式系統社區經常使用的方法:交叉編譯(cross-compilation)。
交叉編譯器是專門爲在給定的系統平臺上運行而設計的編譯器,可是能夠編譯出另外一個系統平臺的可執行文件。例如,amd64
架構的 Linux 系統上的 C++ 交叉編譯器能夠編譯出運行在 aarch64
(64-bit ARM) 架構的嵌入式設備上的可執行文件。再舉個真實的例子,安卓設備的 APP 基本上都是經過這種方法來編譯的。
從性能角度來看,該方法與方法一沒什麼區別,由於不須要模擬器的參與,幾乎沒有性能損耗。但交叉編譯不具備通用性,它的複雜度取決於程序使用的語言,若是使用 Golang 的話,那就超級容易了。
在全民容器時代,咱們討論構建時不只包括構建單個可執行文件,還包括構建容器鏡像。並且構建容器鏡像比上面說的方法更復雜,再加上 Docker 自己的複雜性,這幾乎是一個老大難的問題。
但引入了新的實驗性插件以後,構建多平臺架構的 Docker 鏡像就比之前容易多了,至於這個插件究竟是啥,下文會詳細介紹。
利用 Docker 19.03 引入的插件 buildx,能夠很輕鬆地構建多平臺 Docker 鏡像。buildx 是 docker build ...
命令的下一代替代品,它利用 BuildKit 的所有功能擴展了 docker build
的功能。
下面就來演示一下如何在短短几分鐘內使用 buildx
構建出不一樣平臺的 Docker 鏡像。步驟以下:
要想使用 buildx
,首先要確保 Docker 版本不低於 19.03
,同時還要經過設置環境變量 DOCKER_CLI_EXPERIMENTAL
來啓用。能夠經過下面的命令來爲當前終端啓用 buildx 插件:
🐳 → export DOCKER_CLI_EXPERIMENTAL=enabled
複製代碼
驗證是否開啓:
🐳 → docker buildx version
github.com/docker/buildx v0.3.1-tp-docker 6db68d029599c6710a32aa7adcba8e5a344795a7
複製代碼
若是在某些系統上設置環境變量 DOCKER_CLI_EXPERIMENTAL
不生效(好比 Arch Linux),你能夠選擇從源代碼編譯:
🐳 → export DOCKER_BUILDKIT=1
🐳 → docker build --platform=local -o . git://github.com/docker/buildx
🐳 → mkdir -p ~/.docker/cli-plugins && mv buildx ~/.docker/cli-plugins/docker-buildx
複製代碼
若是你使用的是 Docker 桌面版(MacOS 和 Windows),默認已經啓用了
binfmt_misc
,能夠跳過這一步。
若是你使用的是 Linux,須要手動啓用 binfmt_misc
。大多數 Linux 發行版都很容易啓用,不過還有一個更容易的辦法,直接運行一個特權容器,容器裏面寫好了設置腳本:
🐳 → docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
複製代碼
建議將 Linux 內核版本升級到 4.x 以上,特別是 CentOS 用戶,你可能會遇到錯誤。
驗證是 binfmt_misc 否開啓:
🐳 → ls -al /proc/sys/fs/binfmt_misc/
總用量 0
總用量 0
-rw-r--r-- 1 root root 0 11月 18 00:12 qemu-aarch64
-rw-r--r-- 1 root root 0 11月 18 00:12 qemu-arm
-rw-r--r-- 1 root root 0 11月 18 00:12 qemu-ppc64le
-rw-r--r-- 1 root root 0 11月 18 00:12 qemu-s390x
--w------- 1 root root 0 11月 18 00:09 register
-rw-r--r-- 1 root root 0 11月 18 00:12 status
複製代碼
驗證是否啓用了相應的處理器:
🐳 → cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b7
mask ffffffffffffff00fffffffffffffffffeffff
複製代碼
Docker 默認會使用不支持多 CPU 架構的構建器,咱們須要手動切換。
先建立一個新的構建器:
🐳 → docker buildx create --use --name mybuilder
複製代碼
啓動構建器:
🐳 → docker buildx inspect mybuilder --bootstrap
[+] Building 5.0s (1/1) FINISHED
=> [internal] booting buildkit 5.0s
=> => pulling image moby/buildkit:buildx-stable-1 4.4s
=> => creating container buildx_buildkit_mybuilder0 0.6s
Name: mybuilder
Driver: docker-container
Nodes:
Name: mybuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
複製代碼
查看當前使用的構建器及構建器支持的 CPU 架構,能夠看到支持不少 CPU 架構:
🐳 → docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
mybuilder * docker-container
mybuilder0 unix:///var/run/docker.sock running linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
default docker
default default running linux/amd64, linux/386
複製代碼
如今咱們就能夠構建支持多 CPU 架構的鏡像了!假設有一個簡單的 golang 程序源碼:
🐳 → cat hello.go
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("Hello, %s!\n", runtime.GOARCH)
}
複製代碼
建立一個 Dockerfile 將該應用容器化:
🐳 → cat Dockerfile
FROM golang:alpine AS builder
RUN mkdir /app ADD . /app/ WORKDIR /app RUN go build -o hello .
FROM alpine
RUN mkdir /app WORKDIR /app COPY --from=builder /app/hello . CMD ["./hello"] 複製代碼
這是一個多階段構建 Dockerfile,使用 Go 編譯器來構建應用,並將構建好的二進制文件拷貝到 alpine 鏡像中。
如今就可使用 buildx 構建一個支持 arm、arm64 和 amd64 多架構的 Docker 鏡像了,同時將其推送到 Docker Hub:
🐳 → docker buildx build -t yangchuansheng/hello-arch --platform=linux/arm,linux/arm64,linux/amd64 . --push
複製代碼
須要提早經過
docker login
命令登陸認證 Docker Hub。
如今就能夠經過 docker pull mirailabs/hello-arch
拉取剛剛建立的鏡像了,Docker 將會根據你的 CPU 架構拉取匹配的鏡像。
背後的原理也很簡單,以前已經提到過了,buildx 會經過 QEMU
和 binfmt_misc
分別爲 3 個不一樣的 CPU 架構(arm,arm64 和 amd64)構建 3 個不一樣的鏡像。構建完成後,就會建立一個 manifest list,其中包含了指向這 3 個鏡像的指針。
若是想將構建好的鏡像保存在本地,能夠將 type
指定爲 docker
,但必須分別爲不一樣的 CPU 架構構建不一樣的鏡像,不能合併成一個鏡像,即:
🐳 → docker buildx build -t yangchuansheng/hello-arch --platform=linux/arm -o type=docker .
🐳 → docker buildx build -t yangchuansheng/hello-arch --platform=linux/arm64 -o type=docker .
🐳 → docker buildx build -t yangchuansheng/hello-arch --platform=linux/amd64 -o type=docker .
複製代碼
因爲以前已經啓用了 binfmt_misc
,如今咱們就能夠運行任何 CPU 架構的 Docker 鏡像了,所以能夠在本地系統上測試以前生成的 3 個鏡像是否有問題。
首先列出每一個鏡像的 digests
:
🐳 → docker buildx imagetools inspect yangchuansheng/hello-arch
Name: docker.io/yangchuansheng/hello-arch:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:ec55f5ece9a12db0c6c367acda8fd1214f50ee502902f97b72f7bff268ebc35a
Manifests:
Name: docker.io/yangchuansheng/hello-arch:latest@sha256:38e083870044cfde7f23a2eec91e307ec645282e76fd0356a29b32122b11c639
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm/v7
Name: docker.io/yangchuansheng/hello-arch:latest@sha256:de273a2a3ce92a5dc1e6f2d796bb85a81fe1a61f82c4caaf08efed9cf05af66d
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64
Name: docker.io/yangchuansheng/hello-arch:latest@sha256:8b735708d7d30e9cd6eb993449b1047b7229e53fbcebe940217cb36194e9e3a2
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
複製代碼
運行每個鏡像並觀察輸出結果:
🐳 → docker run --rm docker.io/yangchuansheng/hello-arch:latest@sha256:38e083870044cfde7f23a2eec91e307ec645282e76fd0356a29b32122b11c639
Hello, arm!
🐳 → docker run --rm docker.io/yangchuansheng/hello-arch:latest@sha256:de273a2a3ce92a5dc1e6f2d796bb85a81fe1a61f82c4caaf08efed9cf05af66d
Hello, arm64!
🐳 → docker run --rm docker.io/yangchuansheng/hello-arch:latest@sha256:8b735708d7d30e9cd6eb993449b1047b7229e53fbcebe940217cb36194e9e3a2
Hello, amd64!
複製代碼
So cool!
回顧一下,本文帶你們瞭解了在不一樣的 CPU 架構上運行軟件的挑戰性,以及 buildx
如何幫助咱們解決了其中的一些挑戰。使用 buildx
,咱們無需對 Dockerfile 進行任何修改,就能夠建立支持多種 CPU 架構的 Docker 鏡像,而後將其推送到 Docker Hub。任何安裝了 Docker 的系統均可以拉取到與它的 CPU 架構相對應的鏡像。
將來 buildx 可能會成爲 docker build
命令的一部分,最終全部上面提到的功能都會變成默認的功能,下沉到基礎設施中交叉編譯程序的作法將會變成遠古時代的愚蠢行爲。
掃一掃下面的二維碼關注微信公衆號,在公衆號中回覆◉加羣◉便可加入咱們的雲原生交流羣,和孫宏亮、張館長、陽明等大佬一塊兒探討雲原生技術