使用 buildx 构建多平台 Docker 镜像

原文连接:使用 buildx 构建多平台 Docker 镜像linux

在工做和生活中,咱们可能常常须要将某个程序跑在不一样的 CPU 架构上,好比让某些不可描述的软件运行在树莓派或嵌入式路由器设备上。特别是 Docker 席卷全球以后,咱们能够轻松地在 ARM 设备上经过容器部署各类好玩的应用,而不用在乎各类系统的差别性。git

可是想要跨平台构建 Docker 镜像可不是一件轻松的活,要么到不一样 CPU 架构的系统上所有构建一遍,要么就得在当前系统上经过虚拟化技术模拟不一样的 CPU 架构,最后可能还要想办法合并镜像,费力不讨好。github

不过值得庆幸的是,Docker 19.03 引入了一个新的实验性插件,该插件使得跨平台构建 Docker 镜像比以往更加容易了。在介绍这个新特性以前,咱们先来了解一下跨 CPU 架构构建程序的基础知识。golang

1. 跨 CPU 架构编译程序的方法

先来快速回顾一下当前跨 CPU 架构编译程序的不一样方法。docker

方法一:直接在目标硬件上编译

若是你可以访问目标 CPU 架构的系统,而且该操做系统支持运行构建所需的各类工具,那么你能够直接在目标系统上编译程序。json

以构建 Docker 镜像为例,你能够在树莓派上安装 Docker,而后在树莓派上经过 Dockerfile 直接构建 arm 平台的镜像。bootstrap

若是没法访问目标 CPU 架构的系统该怎么办?有没有办法经过某种方式直接在当前系统上构建目标 CPU 架构的程序?请看下文...bash

方法二:模拟目标硬件

还记得咱们小时候在各类网吧台球室之类的场合玩的街机游戏吗?放张图给大家回忆一下:微信

若是如今咱们想从新体验之前玩过的街机游戏该怎么办?这时候就须要用到模拟器(Emulator)了。借助模拟器,咱们可让时光倒流,体验经典游戏的乐趣。架构

模拟器除了能够用来玩游戏以外,还能够用来跨 CPU 架构构建程序。最经常使用的模拟器是开源的 QEMU,QEMU 支持许多常见的 CPU 架构,包括 ARMPower-PCRISC-V 等。经过模拟一个完整的操做系统,能够建立通用的 ARM 虚拟机,该虚拟机能够引导 Linux,设置开发环境,也能够在虚拟机内编译程序。

然而,模拟整个操做系统仍是有点浪费,由于在这种模式下,QEMU 将会模拟整个系统,包括计时器、内存控制器、总线控制器等硬件。但编译程序根本不须要关心这些,还能够再精简些。

方法三:经过 binfmt_misc 模拟目标硬件的用户空间

在 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 镜像就比之前容易多了,至于这个插件究竟是啥,下文会详细介绍。

2. 构建多平台 Docker 镜像

利用 Docker 19.03 引入的插件 buildx,能够很轻松地构建多平台 Docker 镜像。buildx 是 docker build ... 命令的下一代替代品,它利用 BuildKit 的所有功能扩展了 docker build 的功能。

下面就来演示一下如何在短短几分钟内使用 buildx 构建出不一样平台的 Docker 镜像。步骤以下:

启用 buildx 插件

要想使用 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
复制代码

启用 binfmt_misc

若是你使用的是 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 会经过 QEMUbinfmt_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!

3. 总结

回顾一下,本文带你们了解了在不一样的 CPU 架构上运行软件的挑战性,以及 buildx 如何帮助咱们解决了其中的一些挑战。使用 buildx,咱们无需对 Dockerfile 进行任何修改,就能够建立支持多种 CPU 架构的 Docker 镜像,而后将其推送到 Docker Hub。任何安装了 Docker 的系统均可以拉取到与它的 CPU 架构相对应的镜像。

将来 buildx 可能会成为 docker build 命令的一部分,最终全部上面提到的功能都会变成默认的功能,下沉到基础设施中交叉编译程序的作法将会变成远古时代的愚蠢行为。

4. 参考资料

微信公众号

扫一扫下面的二维码关注微信公众号,在公众号中回复◉加群◉便可加入咱们的云原生交流群,和孙宏亮、张馆长、阳明等大佬一块儿探讨云原生技术

相关文章
相关标签/搜索