1.直接从官方仓库拉去指定平台的镜像
docker pull --platform=<plartform> <image-name>:<tag>
例:
docker pull --platform=arm64 nginx:latest
2.打包指定平台的镜像
安装并使用 buildx
使用 builder
构建跨平台镜像
现在一些准备工作已经就绪,我们终于可以使用 builder
构建跨平台镜像了。
这里以一个 Go 程序为例,来演示如何构建跨平台镜像。
hello.go
程序如下:
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("Hello, %s/%s!\n", runtime.GOOS, runtime.GOARCH)
}
这个程序非常简单,执行后打印 Hello, 操作系统/CPU 架构
。
Go 程序还需要一个 go.mod
文件:
module hello
go 1.20
编写 Dockerfile
内容如下:
FROM golang:1.20-alpine AS builder
WORKDIR /app
ADD . .
RUN go build -o hello .
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/hello .
CMD ["./hello"]
这是一个普通的 Dockerfile
文件,为了减小镜像大小,使用了多阶段构建。它跟构建仅支持当前平台的镜像所使用的 Dockerfile
没什么两样。
$ ls
Dockerfile go.mod hello.go
以上三个文件需要放在同一个目录下,然后就可以在这个目录下使用 docker buildx
来构建跨平台镜像了。
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-go .
docker buildx build
语法跟 docker build
一样,--platform
参数表示构建镜像的目标平台,-t
表示镜像的 Tag,.
表示上下文为当前目录。
唯一不同的是对 --platform
参数的支持,docker build
的 --platform
参数只支持传递一个平台信息,如 --platform linux/arm64
,也就是一次只能构建单个平台的镜像。
而使用 docker buildx build
构建镜像则支持同时传递多个平台信息,中间使用英文逗号分隔,这样就实现了只用一条命令便可以构建跨平台镜像的功能。
执行以上命令后,我们将会得到一条警告:
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
这条警告提示我们没有为 docker-container
驱动程序指定输出,生成结果将只会保留在构建缓存中,使用 --push
可以将镜像推送到 Docker Hub 远程仓库,使用 --load
可以将镜像保存在本地。
这是因为我们新创建的 mybuilder
是启动了一个容器来运行 BuildKit
,它并不能直接将构建好的跨平台镜像输出到本机或推送到远程,必须要用户来手动指定输出位置。
我们可以尝试指定 --load
将镜像保存的本地主机。
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-go . --load
[+] Building 0.0s (0/0)
ERROR: docker exporter does not currently support exporting manifest lists
结果会得到一条错误日志。看来它并不支持直接将跨平台镜像输出到本机,这其实是因为传递了多个 --platform
的关系,如果 --platform
只传递了一个平台,则可以使用 --load
将构建好的镜像输出到本机。
那么我们就只能通过 --push
参数将跨平台镜像推送到远程仓库了。不过在此之前需要确保使用 docker login
完成登录。
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-go . --push
现在登录 Docker Hub 就可以看见推送上来的跨平台镜像了。
我们也可以使用 imagetools
来检查跨平台镜像的 manifest
信息。
$ docker buildx imagetools inspect jianghushinian/hello-go
Name: docker.io/jianghushinian/hello-go:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:51199dadfc55b23d6ab5cfd2d67e38edd513a707273b1b8b554985ff562104db
Manifests:
Name: docker.io/jianghushinian/hello-go:latest@sha256:8032a6f23f3bd3050852e77b6e4a4d0a705dfd710fb63bc4c3dc9d5e01c8e9a6
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64
Name: docker.io/jianghushinian/hello-go:latest@sha256:fd46fd7e93c7deef5ad8496c2cf08c266bac42ac77f1e444e83d4f79d58441ba
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
可以看到,这个跨平台镜像包含了两个目标平台的镜像,分别是 linux/arm64
和 linux/amd64
。
我们分别在 Apple M2 芯片平台和 Linux x86 平台来启动这个 Docker 镜像看下输出结果。
$ docker run --rm jianghushinian/hello-go
Hello, linux/arm64!
$ docker run --rm jianghushinian/hello-go
Hello, linux/amd64!
至此,我们使用 builder
完成了跨平台镜像的构建。
使用交叉编译
以上演示的构建跨平台镜像过程就是利用 QEMU 的能力,因为 Go 语言的交叉编译非常简单,所以我们再来演示一下如何使用交叉编译来构建跨平台镜像。
我们只需要对 Dockerfile
文件进行修改:
FROM --platform=$BUILDPLATFORM golang:1.20-alpine AS builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
ADD . .
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o hello .
FROM --platform=$TARGETPLATFORM alpine:latest
WORKDIR /app
COPY --from=builder /app/hello .
CMD ["./hello"]
其中 BUILDPLATFORM
、TARGETOS
、TARGETARCH
、TARGETPLATFORM
四个变量是 BuildKit
提供的全局变量,分别表示构建镜像所在平台、操作系统、架构、构建镜像的目标平台。
在构建镜像时,BuildKit
会将当前所在平台信息传递给 Dockerfile
中的 BUILDPLATFORM
参数(如 linux/arm64
)。
通过 --platform
参数传递的 linux/arm64,linux/amd64
镜像目标平台列表会依次传递给 TARGETPLATFORM
变量。
而 TARGETOS
、TARGETARCH
两个变量在使用时则需要先通过 ARG
进行声明,BuildKit
会自动为其赋值。
在 Go 程序进行编译时,可以通过 GOOS
环境变量指定目标操作系统,通过 GOARCH
环境变量指定目标架构。
所以这个 Dockerfile
所表示的含义是:首先拉取当前构建镜像所在平台的 golang
镜像,然后使用交叉编译构建目标平台的 Go 程序,最后将构建好的 Go 程序复制到目标平台的 alpine
镜像。
最终我们会通过交叉编译得到一个跨平台镜像。
笔记:通过 FROM --platform=$BUILDPLATFORM image
可以拉取指定平台的镜像,由此我们可以知道,其实 golang 和 alpine 镜像都是支持跨平台的。
构建镜像命令不变:
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-cross-go . --push
启动镜像后输出结果不变:
$ docker run --rm jianghushinian/hello-cross-go
Hello, linux/arm64!
$ docker run --rm jianghushinian/hello-cross-go
Hello, linux/amd64!
至此,我们利用 Go 语言的交叉编译完成了跨平台镜像的构建。
平台相关的全局变量
关于上面提到的几个全局变量,BuildKit
后端预定义了一组 ARG 全局变量(共 8 个)可供使用,其定义和说明如下:
变量 | 说明 |
---|---|
TARGETPLATFORM | 构建镜像的目标平台,如:linux/amd64,linux/arm/v7,windows/amd64。 |
TARGETOS | TARGETPLATFORM 的操作系统,如:linux、windows。 |
TARGETARCH | TARGETPLATFORM 的架构类型,如:amd64、arm。 |
TARGETVARIANT | TARGETPLATFORM 的变体,如:v7。 |
BUILDPLATFORM | 执行构建所在的节点平台。 |
BUILDOS | BUILDPLATFORM 的操作系统。 |
BUILDARCH | BUILDPLATFORM 的架构类型。 |
BUILDVARIANT | BUILDPLATFORM 的变体。 |
使用示例如下:
# 这里可以直接使用 TARGETPLATFORM 变量
FROM --platform=$TARGETPLATFORM alpine
# 稍后的 RUN 命令想要使用变量必须提前用 ARG 进行声明
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
删除 builder
我们已经实现了使用 builder
构建跨平台镜像。如果现在你想要恢复环境,删除新建的 builder
。则可以使用 docker buildx rm mybuilder
命令来完成。
$ docker buildx rm mybuilder
mybuilder removed
跟随 mybuilder
启动的 buildx_buildkit_mybuilder0
容器也会随之被删除。
现在再使用 docker buildx ls
命令查看构建器列表,已经恢复成原来的样子了。
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker
default default running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux docker
desktop-linux desktop-linux running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
功能清单
除了前文介绍的几个 buildx
常用命令,更多功能可以通过 --help
参数进行查看。
$ 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.
如 stop
、rm
可以管理 builder
的生命周期。每条子命令又可以使用 docker buildx COMMAND --help
方式查看使用帮助,感兴趣的同学可以自行学习。