Buildx 来助力

发布时间 2023-09-25 09:47:32作者: 技术颜良

国产化下,跨平台镜像越来2023-09-25越普遍,Buildx 来助力

k8s技术圈 2023-09-24 22:02 发表于四川
 

以下文章来源于云原生百宝箱 ,作者云原生百宝箱

云原生百宝箱.

行万里路,此处相逢,共话云原生之道。 偶逗趣事,明月清风,与君同坐。

点击上方蓝字 ?关注公众号,别让消息溜走

后台回复【加群】,与牛人=互动不停

 


 

 

Docker是一种流行的部署工具,使我们能够打包和运行应用程序。由于采用率很高,需要根据不同的要求扩展功能。因此,为了实现这一目标,第三方使用了docker 插件。

例如,如果我们希望数据能够跨不同主机保存数据,我们可以使用卷插件。另一个常用的插件是Docker buildx。它通过使用 BuildKit 构建器扩展了镜像的构建能力。因此,通过该插件,我们可以为不同的平台和架构构建镜像。此外,它还支持具有自定义上下文的并行多阶段构建。

docker buildx

默认的 docker build 命令无法完成跨平台构建任务,我们需要为 docker 命令行安装 buildx 插件扩展其功能。buildx 能够使用由 Moby BuildKit 提供的构建镜像额外特性,它能够创建多个 builder 实例,在多个节点并行地执行构建任务,以及跨平台构建。

启用 Buildx

首先,为了运行buildx ,我们需要安装 Docker。Docker buildx支持从Docker engine19.00 开始提供。

首先检查我们的 Docker 版本:

$ docker --version
Docker version 19.03.8, build afacb8b

接下来,我们通过设置环境变量来启用Docker实验功能:

$ export DOCKER_CLI_EXPERIMENTAL=enabled

为了确保我们的设置在会话后保留,我们将变量添加到Bash的$HOME/.bashrc中。完成后,我们现在应该可以访问buildx 了:

$ docker buildx

Usage:  docker buildx COMMAND

Build with BuildKit

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
  inspect     Inspect current builder instance
  ls          List builder instances
  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.

这显示了常用命令以及每个命令的语法。

使用 buildx 进行构建的方法如下:

docker buildx build .

buildx 和 docker build 命令的使用体验基本一致,还支持 build 常用的选项如 -t-f等。

新建 builder 实例

docker buildx 通过 builder 实例对象来管理构建配置和节点,命令行将构建任务发送至 builder 实例,再由 builder 指派给符合条件的节点执行。我们可以基于同一个 docker 服务程序创建多个 builder 实例,提供给不同的项目使用以隔离各个项目的配置,也可以为一组远程 docker 节点创建一个 builder 实例组成构建阵列,并在不同阵列之间快速切换。

使用 docker buildx create 命令可以创建 builder 实例,这将以当前使用的 docker 服务为节点创建一个新的 builder 实例。要使用一个远程节点,可以在创建示例时通过 DOCKER_HOST 环境变量指定远程端口或提前切换到远程节点的 docker context

创建实例:

# 创建一个名为 "multi-platform-builder" 的 Buildx 构建器,并启用它,以便支持多个平台的容器镜像构建
$ sudo docker buildx create --use --platform=linux/arm64,linux/amd64 --name multi-platform-builder
multi-platform-builder

# 检查和验证 Docker Buildx 构建器的命令,以确保它已正确设置和准备好进行构建任务
$ sudo docker buildx inspect --bootstrap
[+] Building 15.2s (1/1) FINISHED                                                                                 
 => [internal] booting buildkit                                                                                   
 => => pulling image moby/buildkit:buildx-stable-1                                                                
 => => creating container buildx_buildkit_multi-platform-builder0                                                 
Name:   multi-platform-builder
Driver: docker-container

Nodes:
Name:      multi-platform-builder0
Endpoint:  unix:///var/run/docker.sock
Status:    running
Platforms: linux/arm64*, linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386

使用buildx构建多架构镜像

buildx可以执行所有Docker构建功能。因此,我们可以轻松地运行并执行它们。例如,指定目标平台、构建缓存和输出配置。除此之外, buildx还提供了额外的功能。

首先,能够同时为多个平台构建镜像。其次,能够在构建过程中自定义输入、参数或变量。

源代码和 Dockerfile

下面将以一个简单的 Go 项目作为示例,假设示例程序文件 main.go 内容如下:

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

定义构建过程的 Dockerfile 如下:

FROM golang:1.17 as builder
WORKDIR /opt/app
COPY . .
RUN go mod init main && go build -o example


FROM alpine:latest
WORKDIR /opt/app
COPY --from=builder /opt/app/example ./example

构建过程分为两个阶段:

  • • 在一阶段中,我们将拉取一个 golang 镜像,并使用 Go 其编译为二进制文件。

  • • 然后拉取目标平台的 alpine 镜像,并将上一阶段的编译结果拷贝到镜像中。

执行跨平台构建

$ docker buildx build -f ./Dockerfile-4 --platform linux/amd64,linux/arm64 -t fly190712/example:v2 --push .
                                                                             
[+] Building 80.6s (27/27) FINISHED                                                                              
 => [internal] load build definition from Dockerfile-4                                                      0.0s
 => => transferring dockerfile: 337B                                                                        0.0s
 => resolve image config for docker.io/docker/dockerfile:1                                                 10.5s
 => [auth] docker/dockerfile:pull token for registry-1.docker.io                                            0.0s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab2021  0.0s
 => => resolve docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032e  0.0s
 => [linux/amd64 internal] load metadata for docker.io/library/golang:1.17                                  2.6s
 => [linux/arm64 internal] load metadata for docker.io/library/alpine:latest                                5.4s
 => [linux/amd64 internal] load metadata for docker.io/library/alpine:latest                                5.1s
 => [linux/arm64 internal] load metadata for docker.io/library/golang:1.17                                  4.5s
 => [auth] library/alpine:pull token for registry-1.docker.io                                               0.0s
 => [internal] load .dockerignore                                                                           0.0s
 => => transferring context: 2B                                                                             0.0s
 => [linux/arm64 builder 1/4] FROM docker.io/library/golang:1.17@sha256:87262e4a4c7db56158a80a18fefdc4fee5  0.0s
...
 => [linux/amd64 stage-1 1/3] FROM docker.io/library/alpine:latest@sha256:7144f7bab3d4c2648d7e59409f15ec52  0.0s
...
 => [internal] load build context                                                                           0.0s
 => => transferring context: 1.32kB                                                                         0.0s
 => [linux/amd64 builder 1/4] FROM docker.io/library/golang:1.17@sha256:87262e4a4c7db56158a80a18fefdc4fee5  0.0s
 ...                                                                  0.0s
 => [linux/amd64 builder 4/4] RUN go mod init main && go build -o example                                   0.7s
 => [linux/arm64 builder 4/4] RUN go mod init main && go build -o example                                   3.1s

 => exporting to image                                                                                     13.0s
                                                                                    10.1s
 => => pushing manifest for docker.io/fly190712/example:v2@sha256:507132b53d6c71d36dcedb916c18ceee9a742059  2.7s
 => [auth] fly190712/example:pull,push token for registry-1.docker.io                                       0.0s

注意:构建完成后,镜像在docker images中是查不到的,因为这个镜像仅位于 buildx 缓存中。我们这里使用--push标志可用于直接将镜像推送到 dockerhub。

图片多架构镜像

除此之外,docker buildx build 支持丰富的输出行为,通过--output=[PATH,-,type=TYPE[,KEY=VALUE] 选项可以指定构建结果的输出类型和路径等,常用的输出类型有以下几种:

  • • local:构建结果将以文件系统格式写入 dest 指定的本地路径, 如 --output type=local,dest=./output

  • • tar:构建结果将在打包后写入 dest 指定的本地路径。

  • • oci:构建结果以 OCI 标准镜像格式写入 dest 指定的本地路径。

  • • docker:构建结果以 Docker 标准镜像格式写入 dest 指定的本地路径或加载到 docker 的镜像库中。同时指定多个目标平台时无法使用该选项。

  • • image:以镜像或者镜像列表输出,并支持 push=true 选项直接推送到远程仓库,同时指定多个目标平台时可使用该选项。

  • • registrytype=image,push=true 的精简表示。

我们执行如下 docker buildx build 命令:

$ docker buildx build -f ./Dockerfile-4 --platform linux/amd64,linux/arm64 -t fly190712/example:v2 -o type=registry .

该命令将在当前目录同时构建 linux/amd64、 linux/arm64 和 linux/arm 三种平台的镜像,并将输出结果直接推送到远程的阿里云镜像仓库中。

构建过程可拆解如下:

  1. 1. docker 将构建上下文传输给 builder 实例。

  2. 2. builder 为命令行 --platform 选项指定的每一个目标平台构建镜像,包括拉取基础镜像和执行构建步骤。

  3. 3. 导出构建结果,镜像文件层被推送到远程仓库。

  4. 4. 生成一个清单 JSON 文件,并将其作为镜像标签推送给远程仓库。

更重要的补充

构建驱动

buildx 实例通过两种方式来执行构建任务,两种执行方式被称为使用不同的「驱动」

  • • docker 驱动:使用 Docker 服务程序中集成的 BuildKit 库执行构建。

  • • docker-container 驱动:启动一个包含 BuildKit 的容器并在容器中执行构建。

docker 驱动无法使用一小部分 buildx 的特性(如在一次运行中同时构建多个平台镜像),此外在镜像的默认输出格式上也有所区别:docker 驱动默认将构建结果以 Docker 镜像格式直接输出到 docker 的镜像目录(通常是 /var/lib/overlay2),之后执行 docker images 命令可以列出所输出的镜像;

而 docker container 则需要通过 --output 选项指定输出格式为镜像或其他格式

为了一次性构建多个平台的镜像,本文使用 docker container 作为默认的 builder 实例驱动。

解决 ERROR merging manifest list

如果你使用了较新版本的 buildx(>=0.10) 和 buildkit(>=0.11) 以及较旧版本的镜像仓库,在构建跨平台镜像时可能会遇到构建成功但推送失败的问题,报错如下:

 => => pushing layers                                                                                                                                          12.3s
 => => pushing manifest for registry.xxx.com/xxx/postgres-vb                                                                                          0.9s
 => [auth] xxx/postgres-vb:pull,push token for registry.xxx.com                                                                                       0.0s
 => ERROR merging manifest list registry.xxx.com/xxx/postgres-vb:v1.2.0

这是因为新版本默认开启了 Build attestations 功能以增强供应链安全,但同时带来了兼容性问题。如果你并不需要,可添加如下 build 选项禁用它以解决该问题:

docker buildx build --sbom=false --provenance=false

buildx 的跨平台构建策略

根据构建节点和目标程序语言不同,**buildx 支持以下三种跨平台构建策略**:

  1. 1. 通过 QEMU 的用户态模式创建轻量级的虚拟机,在虚拟机系统中构建镜像。

  2. 2. 在一个 builder 实例中加入多个不同目标平台的节点,通过原生节点构建对应平台镜像。

  3. 3. 分阶段构建并且交叉编译到不同的目标架构

QEMU 通常用于模拟完整的操作系统,它还可以通过用户态模式运行:以 binfmt_misc 在宿主机系统中注册一个二进制转换处理程序,并在程序运行时动态翻译二进制文件,根据需要将系统调用从目标 CPU 架构转换为当前系统的 CPU 架构。最终的效果就像在一个虚拟机中运行目标 CPU 架构的二进制文件。

Docker Desktop 内置了 QEMU 支持,其他满足运行要求的平台可通过以下方式安装

$ docker run --privileged --rm tonistiigi/binfmt --install all

这种方式不需要对已有的 Dockerfile 做任何修改,实现的成本很低,但显而易见效率并不高。

将不同系统架构的原生节点添加到 builder 实例中可以为跨平台编译带来更好的支持,而且效率更高,但需要有足够的基础设施支持。

如果构建项目所使用的程序语言支持交叉编译(如 C 和 Go),可以利用 Dockerfile 提供的分阶段构建特性:首先在和构建节点相同的架构中编译出目标架构的二进制文件,再将这些二进制文件复制到目标架构的另一镜像中。

k8s技术圈
专注容器、专注 kubernetes 技术......
361篇原创内容
 
阅读 1409
k8s技术圈
 
精选留言
写留言
  •  
    交叉编译
     
     
  •  
    新版 docker 已经将 buildx 作为默认构建工具了,docker build 命令现在是 docker buildx 命令的别名。
     
     
已无更多数据