1. 概述
Docker鏡像支持多平臺,即單個鏡像可支持不同的OS和CPU架構(gòu),例如linux/amd64、linux/arm64等。
在DockerHub,可以查看每個鏡像支持的操作系統(tǒng)和處理器架構(gòu),如下圖,python支持windows和linux兩個操作系統(tǒng),多種CPU架構(gòu)。


在不同的操作系統(tǒng)和CPU架構(gòu)下,通過docker pull或docker run,docker daemon會幫助我們自動選擇適合的鏡像,非常方便。
那么,如何build不同CPU架構(gòu)的鏡像呢,本文重點來介紹。
2. Buildx
集成了Buildx的BuildKit,可以實現(xiàn)跨平臺的鏡像構(gòu)建,只需要通過--platform指定對應的平臺即可,例如linux/amd64、linux/arm64、darwin/amd64。
下面,先從一些術(shù)語來介紹其功能。
2.1. builder or builder實例
通過docker buildx ls可以查看,也支持create、rm等操作
% docker buildx --help
Usage: docker buildx [OPTIONS] COMMAND
Extended build capabilities with BuildKit
Options:
--builder string Override the configured builder instance
Management Commands:
imagetools Commands to work on images in registry
Commands:
bake Build from a file
build Start a build
create Create a new builder instance
du Disk usage
inspect Inspect current builder instance
ls List builder instances
prune Remove build cache
rm Remove a builder instance
stop Stop builder instance
use Set the current builder instance
version Show buildx version information
Run 'docker buildx COMMAND --help' for more information on a command.
2.2. builder node
一個builder可以包含多個node,作用:可提高build效率,也可支持更復雜的CPU架構(gòu)下鏡像構(gòu)建。
實例創(chuàng)建之后可以添加新的節(jié)點,通過docker buildx create命令的--append選項可將--node <node>節(jié)點加入到--name <builder>選項指定的 builder 實例。如下將把一個遠程節(jié)點加入 builder 實例:
$ docker buildx create --driver docker-container --platform linux/amd64 --name multi-builder
multi-builder
$ export DOCKER_HOST=tcp://10.10.150.66:2375
$ docker buildx create --name multi-builder --append --node remote-builder
剛創(chuàng)建的 builder 處于 inactive 狀態(tài),可以在 create 或 inspect 子命令中添加 --bootstrap 選項立即啟動實例(可驗證節(jié)點是否可用):
$ docker buildx inspect --bootstrap multi-builder
[+] Building 3.9s (2/2) FINISHED
=> [remote-builder internal] booting buildkit 3.9s
=> => pulling image moby/buildkit:buildx-stable-1 2.8s
=> => creating container buildx_buildkit_remote-builder 1.2s
=> [multi-builder0 internal] booting buildkit 3.7s
=> => pulling image moby/buildkit:buildx-stable-1 2.4s
=> => creating container buildx_buildkit_multi-builder0 1.3s
Name: multi-builder
Driver: docker-container
Nodes:
Name: multi-builder0
Endpoint: unix:///var/run/docker.sock
Status: running
Buildkit: v0.10.5
Platforms: linux/amd64*, linux/386
Name: remote-builder
Endpoint: tcp://10.10.88.20:2375
Status: running
Buildkit: v0.10.5
Platforms: linux/arm64
docker buildx ls 將列出所有可用的 builder 實例和實例中的節(jié)點:
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
multi-builder docker-container
multi-builder0 unix:///var/run/docker.sock running v0.10.5 linux/amd64*, linux/386
remote-builder tcp://10.10.88.20:2375 running v0.10.5 linux/arm64
default * docker
default default running linux/amd64, linux/386
如上就創(chuàng)建了一個支持多平臺架構(gòu)的 builder 實例,執(zhí)行docker buildx use <builder>將切換到所指定的 builder 實例。
docker buildx inspect、docker buildx stop 和 docker buildx rm 命令用于管理一個實例的生命周期。
參考:如何使用 docker buildx 構(gòu)建跨平臺 Go 鏡像 | Shall We Code? (waynerv.com)
2.3. builder driver
buildx 實例通過兩種方式來執(zhí)行構(gòu)建任務,兩種執(zhí)行方式被稱為使用不同的驅(qū)動:
- docker 驅(qū)動:使用 Docker 服務程序中集成的 BuildKit 庫執(zhí)行構(gòu)建
- docker-container 驅(qū)動:啟動一個包含 BuildKit 的容器并在容器中執(zhí)行構(gòu)建
docker驅(qū)動無法使用一小部分 buildx 的特性,例如不能在一次運行中同時構(gòu)建多個平臺鏡像。此外,在鏡像的默認輸出格式上也有所區(qū)別:docker驅(qū)動默認將構(gòu)建結(jié)果以 Docker 鏡像格式直接輸出到 docker 的鏡像目錄(通常是 /var/lib/overlay2),之后執(zhí)行 docker images 命令可以列出所輸出的鏡像。而,docker container則需要通過--output選項指定輸出格式為鏡像或其他格式。
為了一次性構(gòu)建多個平臺的鏡像,本文使用docker container作為默認的 builder 實例驅(qū)動。
2.4. dockerfile中的環(huán)境變量
- BUILDPLATFORM,構(gòu)建節(jié)點的平臺信息,例如
linux/amd64、linux/arm64 - BUILDARCH
- BUILDOS
- TARGETPLATFORM,目標平臺信息
- TARGETARCH
- TARGETOS
以下面的dockerfile為例,可以看到對應的env值:
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
FROM alpine
COPY --from=build /log /log
build命令:docker buildx build --platform linux/amd64,linux/arm64 -t test:0 -o type=oci,dest=./oci-image .

2.5. output
執(zhí)行構(gòu)建命令時,除了指定鏡像名稱,另外兩個重要的選項是指定目標平臺和輸出格式。
當使用 docker-container 驅(qū)動時,--platform可以接受用逗號分隔的多個值作為輸入以同時指定多個目標平臺,所有平臺的構(gòu)建結(jié)果將合并為一個整體的鏡像列表作為輸出,因此,無法直接輸出為本地的 docker images 鏡像。
docker buildx build支持豐富的輸出行為,通過--output=[PATH,-,type=TYPE[,KEY=VALUE]選項可以指定構(gòu)建結(jié)果的輸出類型和路徑等,常用的輸出類型有以下幾種:
- local:構(gòu)建結(jié)果將以文件系統(tǒng)格式寫入 dest 指定的本地路徑, 如 --output type=local,dest=./output
- tar:構(gòu)建結(jié)果將在打包后寫入 dest 指定的本地路徑
- oci:構(gòu)建結(jié)果以 OCI 標準鏡像格式寫入 dest 指定的本地路徑
- docker:構(gòu)建結(jié)果以 Docker 標準鏡像格式寫入 dest 指定的本地路徑或加載到 docker 的鏡像庫中。同時指定多個目標平臺時無法使用該選項
- image:以鏡像或者鏡像列表輸出,并支持 push=true 選項直接推送到遠程倉庫,同時指定多個目標平臺時可使用該選項
- registry:type=image,push=true 的精簡表示
常用的也就兩種方式,分別是-o type=registry和-o type=docker,dest=./linux-amd64-image。
第一種,支持同時build多個鏡像,第二種每次只能build一個特定平臺的鏡像。
docker buildx build --platform linux/amd64,linux/arm64 -t test:0 -o type=registry
# 上面的命令等價與下面兩條命令
docker buildx build --platform linux/amd64 -t test:0 -o type=docker,dest=./linux-amd64-image .
docker buildx build --platform linux/arm64 -t test:0 -o type=docker,dest=./linux-arm64-image .
% docker buildx build --platform linux/amd64 -t test:0 -o type=docker,dest=./linux-amd64-image .
[+] Building 7.8s (11/11) FINISHED
=> [internal] load build definition from dockerfile 0.0s
=> => transferring dockerfile: 250B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:latest 7.6s
=> [internal] load metadata for docker.io/library/golang:alpine 7.4s
=> [auth] library/golang:pull token for registry-1.docker.io 0.0s
=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s
=> [build 1/2] FROM docker.io/library/golang:alpine@sha256:913de96707b0460bcfdfe422796bb6e559fc300f6c53286777805a9a3010a5ea 0.0s
=> => resolve docker.io/library/golang:alpine@sha256:913de96707b0460bcfdfe422796bb6e559fc300f6c53286777805a9a3010a5ea 0.0s
=> [stage-1 1/2] FROM docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126 0.0s
=> => resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126 0.0s
=> CACHED [build 2/2] RUN echo "I am running on linux/arm64, building for linux/amd64" > /log 0.0s
=> CACHED [stage-1 2/2] COPY --from=build /log /log 0.0s
=> exporting to docker image format 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:5f55c6c58da4636aa38363b1aed04cc2ff1921bfb12c42aa308430a8a20c12ac 0.0s
=> => exporting config sha256:f2ce60afc09e4bb74d905f6b1a58504c140f2090a1420d275e89381a36c9947e 0.0s
=> => sending tarball 0.1s
% ll
total 4108
-rw-r--r-- 1 shuzhang staff 211 5 7 16:34 dockerfile
-rw-r--r-- 1 shuzhang staff 3384832 5 7 17:26 linux-amd64-image
% docker load < linux-amd64-image
f1417ff83b31: Loading layer [==================================================>] 3.375MB/3.375MB
c5ddf0bc21cf: Loading layer [==================================================>] 148B/148B
Loaded image: test:0
% docker images | grep test
test 0 f2ce60afc09e 50 minutes ago 7.05MB
3. Example
下面將以一個簡單的 Go 項目作為示例,假設示例程序文件 main.go 內(nèi)容如下:
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("Hello world!")
fmt.Printf("Running in [%s] architecture.\n", runtime.GOARCH)
}
定義構(gòu)建過程的 Dockerfile 如下:
FROM --platform=$BUILDPLATFORM golang:1.14 as builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
COPY main.go /app/main.go
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -o output/main main.go
FROM alpine:latest
WORKDIR /root
COPY --from=builder /app/output/main .
CMD /root/main
構(gòu)建過程分為兩個階段:
- 在一階段中,我們將拉取一個和當前構(gòu)建節(jié)點相同平臺的 golang 鏡像,并使用 Go 的交叉編譯特性將其編譯為目標架構(gòu)的二進制文件。
- 然后拉取目標平臺的 alpine 鏡像,并將上一階段的編譯結(jié)果拷貝到鏡像中。
4. Setup builder
Docker Desktop provides binfmt_misc multi-architecture support, which means you can run containers for different Linux architectures such as arm, mips, ppc64le, and even s390x.
對于沒有Desktop的服務器而言,最新的docker版本默認支持buildx,可能需要安裝binfmt,以支持跨平臺構(gòu)建。
# 檢查我們當前的buildx可以使用的全部的構(gòu)建實例/構(gòu)建節(jié)點,我們發(fā)現(xiàn)還是使用默認的,沒有任何改變
[thinktik@thinkdev ~]$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
default * docker
default default running linux/amd64, linux/386
# 安裝binfmt,它內(nèi)置了QEMU能提供跨平臺構(gòu)建功能
[thinktik@thinkdev ~]$ docker run --privileged --rm tonistiigi/binfmt --install all
Unable to find image 'tonistiigi/binfmt:latest' locally
latest: Pulling from tonistiigi/binfmt
2b4d0e08bd75: Pull complete
c331be51c382: Pull complete
Digest: sha256:5bf63a53ad6222538112b5ced0f1afb8509132773ea6dd3991a197464962854e
Status: Downloaded newer image for tonistiigi/binfmt:latest
installing: s390x OK
installing: riscv64 OK
installing: mips64le OK
installing: mips64 OK
installing: arm64 OK
installing: arm OK
installing: ppc64le OK
{
"supported": [
"linux/amd64",
"linux/arm64",
"linux/riscv64",
"linux/ppc64le",
"linux/s390x",
"linux/386",
"linux/mips64le",
"linux/mips64",
"linux/arm/v7",
"linux/arm/v6"
],
"emulators": [
"qemu-aarch64",
"qemu-arm",
"qemu-mips64",
"qemu-mips64el",
"qemu-ppc64le",
"qemu-riscv64",
"qemu-s390x"
]
}
# 創(chuàng)建一個buildx構(gòu)建器
[thinktik@thinkdev ~]$ docker buildx create --name crossbuilder --driver docker-container
crossbuilder
# 使用新創(chuàng)建的構(gòu)建器
[thinktik@thinkdev ~]$ docker buildx use crossbuilder
# 再次檢查buildx可以使用的構(gòu)建實例/構(gòu)建節(jié)點,我們看到新加了一個crossbuilder,并且顯示支持了一大批CPU架構(gòu)
[thinktik@thinkdev ~]$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
crossbuilder * docker-container
crossbuilder0 unix:///var/run/docker.sock inactive
default docker
default default running linux/amd64, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6
補充,最新的跨平臺構(gòu)象鏡像,只在docker-desktop中設置一下即可
