多平台容器镜像构建就看这一篇


前言

愿景与现实

早在1995年,就有“write once and run anywhere”(WORA,编写一次即可在任何地方运行)用于描述 Java 应用程序。时过20年,Docker 高声喊出了自己的口号——“Build Once, Run Anywhere”(一次构建,随处可用)。

愿望是美好的,然而,现实总比理想骨感。 Linux、Windows 这些不同的操作系统拥有不同的系统 API; x86、Arm、IBM PowerPC 这些不同的硬件平台的指令集不同,某些同平台的硬件甚至拥有不同的专用指令集用于加速应用。一次构建,随处可用面临着巨大的挑战,要构建能够在不同操作系统、不同硬件平台的运行的应用程序,仍然需要工程师们针对具体的操作系统和硬件平台进行海量的移植工作。

Why and How

既然多平台的支持这么麻烦、充满挑战,我们是不是可以放弃支持?然而随着国产化大潮和 IoT 物联网的来临,我们编写的应用程序不仅仅会在X86服务器上运行,新时代的工程师们不得不面对更多的硬件平台,放弃多平台的支持无疑是放弃更宽广的未来。多平台的支持,势在必行。

我们正处在一个波澜壮阔的大时代中,新技术、新工具在一次次的迭代升级,不断从Proposal (提议)到 Prototype(原型),再逐渐的实用化。

虚拟化技术使得我们可以做到模拟其它硬件平台;Docker 等容器技术打破混乱,让开发、编译、运行环境一致化;Golang、Rust 这样原生支持多系统多平台的编程语言屏蔽大量底层差异,降低跨平台应用的开发难度。这一系列前人智慧火花汇聚到一起,发生了奇妙的反应——WORA 真正变的触手可及,梦想的阳光已经照进现实。

本篇章会大量分析技术原理及实现细节,对于希望快速 GET 可执行方案的同学,可以直接跳转到【可执行方案回顾】查看。

如何支持多平台

要了解容器镜像是如何支持多平台的,那我们需要仔细聊聊 Manifest。使用过容器技术的同学都知道,我们运行容器所使用的镜像是由多层构成的,而这些层的清单和其它容器信息共同存放在 Manifest 当中。

Manifest

我们通常使用的容器镜像是x86平台的,执行 docker manifest inspect harbor-community.tencentcloudcr.com/multi-arch/alpine 命令可以查看镜像 harbor-community.tencentcloudcr.com/multi-arch/alpine 的 Manifest 内容是一个 JSON 对象(如代码段-01所示)。各个字段的解释如下:

  1. mediaType 字段声明这是一个V2 Manifes。
  2. schemaVersion 版本。
  3. config 镜像配置信息。
  4. layers 镜像层信息。
// 代码段-01
{
	"schemaVersion": 2,
	"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
	"config": {
		"mediaType": "application/vnd.docker.container.image.v1+json",
		"size": 1507,
		"digest": "sha256:f70734b6a266dcb5f44c383274821207885b549b75c8e119404917a61335981a"
	},
	"layers": [
		{
			"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
			"size": 2813316,
			"digest": "sha256:cbdbe7a5bc2a134ca8ec91be58565ec07d037386d1f1d8385412d224deafca08"
		}
	]
}

显而易见的是,Manifest 当中并没有任何字段描述镜像的平台信息。那应该怎么样支持多平台呢?

我们可以设想一个简单粗暴的,无视镜像的平台,强行把交叉编译出来的其它平台的二进制程序添加到镜像内,使用 Repository 名称或者 Tag 名称来区分不同平台的镜像,例如 coredns/coredns:coredns-arm64。在使用的时候,人工或者通过脚本判断应该拉取那个镜像。

Schema 2

Ohhhhh,这当然可以跑起来,但是难免太挫了吧?事实上,早在2015年底 Docker 社区的 manifest v2.2 规格文档(也叫 Schema 2,参见 https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md )中就提及了多平台镜像,该功能通过 manifest list(也叫做 fat manifest) 引用多个不同平台镜像的 Manifest 实现。

首先让我们看看 manifest 是什么样的,执行 docker manifest inspect alpine 命令可以查看Docker Hub 上的多平台镜像 alpine 的 Manifest。

// 代码段-02
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:71465c7d45a086a2181ce33bb47f7eaef5c233eace65704da0c5e5454a79cee5",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v6"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:c929c5ca1d3f793bfdd2c6d6d9210e2530f1184c0f488f514f1bb8080bb1e82b",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:3b3f647d2d99cac772ed64c4791e5d9b750dd5fe0b25db653ec4976f7b72837c",
         "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:90baa0922fe90624b05cb5766fa5da4e337921656c2f8e2b13bd3c052a0baac1",
         "platform": {
            "architecture": "386",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:5d950b30f229f0c53dd7dd7ed6e0e33e89d927b16b8149cc68f59bbe99219cc1",
         "platform": {
            "architecture": "ppc64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:a5426f084c755f4d6c1d1562a2d456aa574a24a61706f6806415627360c06ac0",
         "platform": {
            "architecture": "s390x",
            "os": "linux"
         }
      }
   ]
}

可以看出来 manifest list 是一个 JSON 数组,数组当中应用了不同平台镜像的 Manifest。所以,推送多平台镜像时,我们需要先分别推送不同平台的镜像层;然后创建 manifest list , 再引用平台镜像的 Manifest,最后把 manifest list 上传到 Registry 服务。而拉取镜像时,客户端应当设置 HTTP 的请求头字段 Accept 值为 application/vnd.docker.distribution.manifest.v2+jsonapplication/vnd.docker.distribution.manifest.list.v2+json,然后检查服务端返回的响应头字段Content-Type 判断是旧镜像格式,新镜像格式或者时镜像清单。

抛开规格文档来说,只要我们使用的 Registry 服务的 Distribution 版本不低于 v2.3,Docker CLI 版本不低于 v1.10 就能过支持多平台镜像功能。

构建多平台镜像

要构建多平台的容器镜像,我们需要确保容器基础镜像和应用程序的代码或者二进制都是目标平台的。

程序代码

一些编程语言的编译器能够为其它平台编译二进制文件,最为著名的包括 Golang 和 Rust。我们将使用 Golang 编写一个演示用 web 程序——通过 HTTP 访问查看 web 服务程序的操作系统、硬件平台等信息。具体代码如代码段-03 所示。

// 代码段-03
package main
import (
	"net/http"
	"runtime"
	"github.com/gin-gonic/gin"
)
var (
	r = gin.Default()
)
func main() {
	r.GET("/", indexHandler)
	r.Run(":9090")
}
func indexHandler(c *gin.Context) {
	var osinfo = map[string]string{
		"arch":    runtime.GOARCH,
		"os":      runtime.GOOS,
		"version": runtime.Version(),
	}
	c.JSON(http.StatusOK, osinfo)
}

我们在 MacOS 上使用 go run 运行代码段-03, httpie 工具访问本机:9090 端口,将会看见如下信息。

代码准备好了,现在我们有两种构建方法:手动编译,使用 docker build 构建镜像;使用 docker buildx 工具自动化编译构建。

手动编译构建

前置条件

  • Dockerd 启用 experimental

我们需要在 Docker daemon 配置文件中配置 "experimental": true开启实验性功能:

$ vi /etc/docker/daemon.json
{
  "experimental": true
}

修改 Docker daemon 配置需要重启服务使配置生效:

$ sudo systemctl restart docker.service

使用 docker version 命令查看版本信息,配置生效后可以看到 Server: Docker Engine 中有 Experimental: true

$ sudo docker version
Client: Docker Engine - Community
 Version:           19.03.12
 API version:       1.40
 Go version:        go1.13.10
 Git commit:        48a66213fe
 Built:             Mon Jun 22 15:45:36 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Server: Docker Engine - Community
 Engine:
  Version:          19.03.12
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.10
  Git commit:       48a66213fe
  Built:            Mon Jun 22 15:44:07 2020
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
`

如果您使用的 Docker CLI 版本低于 v20.10 ,执行 docker manifest 命令会看到报错提示 docker manifest is only supported on a Docker cli with experimental cli features enabled ,此时我们需要执行 export DOCKER_CLI_EXPERIMENTAL="enabled" 开启客户端实验特性支持。在 v20.10 及以上版本的 Docker CLI 会默认开启实验特性,无需额外操作。

交叉编译

在我们的 Golang 代码中没有使用 CGO 的时候,通过简单设置环境变量就能够交叉编译出其它平台和操作系统上能够执行的二进制文件。其中:

  1. GOARCH 用于指定编译的目标平台,如 amd64arm64riscv64 等平台。
  2. GOOS 用于指定编译的目标系统,如 darwinlinux

本篇中,我们构建能够在 Linux 发行版中执行的容器镜像,所以编译目标系统环境变量GOOS统一设置为linux。执行代码段0-4中的命令构建出二进制文件备用。

// 代码段-04
#!/bin/bash
IMAGE?=kofj/multi-demo
NOCOLOR:='\033[0m'
RED:='\033[0;31m'
GREEN:='\033[0;32m'
BUILD_ARCH?=$(uname -m)
BUILD_OS?=$(uname -s)
BUILD_PATH:=build/docker/linux
LINUX_ARCH?=amd64 arm64 riscv64
LDFLAGS:="-s -w -X github.com/kofj/multi-arch-demo/cmd/info.BuildArch=$(BUILD_ARCH) -X github.com/kofj/multi-arch-demo/cmd/info.BuildOS=$(BUILD_OS)"
for arch in ${LINUX_ARCH}; do
	echo ===================;
	echo ${GREEN}Build binary for ${RED}linux/$$arch${NOCOLOR};
	echo ===================;
	GOARCH=$$arch GOOS=linux go build -o ${BUILD_PATH}/$$arch/webapp -ldflags=${LDFLAGS} -v cmd/main.go;
done

构建各个平台的镜像

首先,我们编写一个 Dockerfile用于构建镜像。

FROM scratch
LABEL authors="Fanjian Kong"
ADD webapp /app/
WORKDIR /app
CMD ["/app/webapp"]

然后,分别构建不同平台的镜像,可以使用如代码段-05的脚本帮助构建。

// 代码段-05
#!/bin/bash
IMAGE?=kofj/multi-demo
NOCOLOR:='\033[0m'
RED:='\033[0;31m'
GREEN:='\033[0;32m'
LINUX_ARCH?=amd64 arm64 riscv64
BUILD_PATH:=build/docker/linux
for arch in ${LINUX_ARCH}; do
	echo =================== ;
	echo ${GREEN}Build docker image for ${RED}linux/$$arch${NOCOLOR} ;
	echo =================== ;
	cp Dockerfile.slim ${BUILD_PATH}/$$arch/Dockerfile ;
	docker build -t ${IMAGE}:$$arch ${BUILD_PATH}/$$arch ;
done

创建 Manifest List

我们使用docker manifest 子命令管理 manifest list。其中,docker manifest create 子命令用于在本地创建一个 manifest list。该命令需要指定待 manifest list 地址和一系列的 manifests。例如需要创建包含amd64arm64 两个平台镜像的 manifest list,则命令如:

docker manifest create kofj/multi-demo kofj/multi-demo:amd64 kofj/multi-demo:arm64

docker manifest create 命令的详细帮助信息如下所示:

# docker manifest create --help
Usage:	docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...]
Create a local manifest list for annotating and pushing to a registry
Options:
  -a, --amend      Amend an existing manifest list
      --insecure   Allow communication with an insecure registry

我们按照上述方法创建出来的 manifest list 中并没有说明其中的 manifest 是什么操作系统和平台的,docker manifest annotate 命令用于注释创建出来的 manifest list。例如注释某个 manifest 是 linxu系统 arm64 平台的,则命令:

docker manifest annotate kofj/multi-demo kofj/multi-demo:arm64 --os linux --arch arm64

docker manifest annotate 命令的详细帮助信息如下所示:

# docker manifest annotate --help
Usage:	docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST
Add additional information to a local image manifest
Options:
      --arch string           Set architecture
      --os string             Set operating system
      --os-features strings   Set operating system feature
      --variant string        Set architecture variant

注意:

  1. 创建 manifest list 清单的过程中会检查远端仓库中 manifests 是否存在,所以我们必修提前推送镜像到远端。如果远端仓库是不安全的,在创建的过程中需要添加参数 --inseure
  2. 使用 docker manifest annotate 注释 manifest list 的时候不需要使用--insecure

为了方便使用,我们可以使用下述代码段-06 的脚本创建 manifest list。

// 代码段-06
#!/bin/bash
IMAGE?=kofj/multi-demo
NOCOLOR:='\033[0m'
RED:='\033[0;31m'
GREEN:='\033[0;32m'
LINUX_ARCH?=amd64 arm64 riscv64
IMAGES=$(foreach arch,$(LINUX_ARCH),$(IMAGE):$(arch))
@echo ${GREEN}Create manifest for ${RED} \( ${LINUX_ARCH}\) ${NOCOLOR};
@docker manifest create ${IMAGE} ${IMAGES}
@for arch in ${LINUX_ARCH}; do \
	echo ${GREEN}Annotate manifest ${RED}linux/$$arch${NOCOLOR}; \
	echo ===================; \
	docker manifest annotate ${IMAGE} ${IMAGE}:$$arch --os linux --arch $$arch; \
done

推送 manifest list

当我们完成 manifest list 的创建工作后,它还是存储在本地的。这时候,还需要推送到远端的镜像仓库。与推送普通镜像不同,推送 manifest list 需要使用 docker manifest push 命令进行。如果我们要推送 kofj/multi-demo 这个 manifest list,则命令如:

docker manifest push kofj/multi-demo

使用 docker manifest push 命令可以通过附加--purge 选项在推送完成后删除存储在本地的 manifest list; 当我们的目标仓库没有使用或者使用了非可信 TLS 证书的时候,则需要使用 --insecure 选项。

buildx 自动构建

软件依赖

  • Docker >= 19.03: 自该 Docker 版本包含 buildx。
  • Linux kernel >= 4.8: 自该Linux内核版本 binfmt_misc 支持 fix-binary (F) flag。fix_binary 标志允许内核在容器或chroot内使用binfmt_misc注册的二进制格式处理程序,即使该处理程序二进制文件不是该容器或chroot内可见的文件系统的一部分。
  • binfmt_misc file system mounted: 需要挂载binfmt_misc文件系统,以便用户空间工具可以控制此内核功能,即注册和启用处理程序。
  • Docker Desktop >= 2.1.0 如果是使用的 Docker Desktop。
Environment Docker 安装包 Kernel binfmt-support (F) Flag
需求 >= 19.03 >= 4.8 >= 2.1.7 yes
Ubuntu:
18.04 (bionic) 17.12.1 docker.io 4.15.0 2.1.8 yes
19.04 (disco) 18.09.5 docker.io 5.0 2.2.0 yes
19.10 (eoan) 19.03.2 docker.io 5.3 2.2.0 yes
20.04 (focal) 19.03.2 docker.io 5.5 2.2.0 yes
Debian:
9 (stretch) - 4.9.0 2.1.6 no
10 (buster) 18.09.1 docker.io 4.19.0 2.2.0 yes
11 (bullseye/testing) 19.03.4 docker.io 5.4 2.2.0 yes
腾讯云
Ubuntu 16.04 (xenial) 18.09.7 docker.io 4.4.0 2.1.6-1 no
Ubuntu 18.04 (bionic) 19.03.6 docker.io 4.15.0 2.1.8-2 yes
亚马逊 EC2:
Ubuntu 16.04 (xenial) 18.09.7 docker.io 4.4.0 2.1.6 no
Ubuntu 18.04 (bionic) 18.09.7 docker.io 4.15.0 2.1.8 yes
Travis (谷歌 GCP):
Ubuntu 14.04 (trusty) 17.09.0 docker-ce 4.4.0 2.1.4 no
Ubuntu 16.04 (xenial) 18.06.0 docker-ce 4.15.0 2.1.6 no
Ubuntu 18.04 (bionic) 18.06.0 docker-ce 4.15.0 2.1.8 yes
Github Actions (微软 Azure):
Ubuntu 16.04 (xenial) 3.0.8 moby-engine 4.15.0 2.1.6 no
Ubuntu 18.04 (bionic) 3.0.8 moby-engine 5.0.0 2.1.8 yes

配置 Buildx

buildx 从 19.03 开始与 Docker CE 捆绑发布,但是需要我们在 Docker CLI 上启用实验性功能开开启。可以通过两种方式启用它:

  1. "experimental": "enabled” 添加到 Docker CLI 的配置文件 ~/.docker/config.json
  2. 另外一种方法时设置环境变量 DOCKER_CLI_EXPERIMENTAL=enabled

使用 Docker Desktop 的同学可以通过 UI 菜单 PreferencesCommand Line 进入 Docker CLI 配置界面,通过Switch 开关 Enable experimental features启用实验性功能。

image-20201006100313617.png

如果需要使用最新版本的 buildx,可以从 https://github.com/docker/buildx/releases/latest 下载最新的二进制发行版,并将其复制到~/.docker/cli-plugins文件夹中,重命名为docker-buildx,然后更改执行权限:

chmod +x ~/.docker/cli-plugins/docker-buildx

最后让我们验证 buildx 是否已经可用了:

$ docker buildx version
github.com/docker/buildx v0.3.1-tp-docker 6db68d029599c6710a32aa7adcba8e5a344795a7

配置 binfmt_misc

QEMU 是一个很棒的开源项目,它可以模拟很多平台。将 QEMU 和 Docker 结合起来使用能使得我们更容易的构建跨平台的容器镜像。集成 QEMU依赖于 Linux 内核功能 。Linux 内核中的 binfmt_misc功能可以使得内核识别任意类型的可以执行文件格式,并传递到特定的用户空间应用程序和虚拟机(https://zh.wikipedia.org/wiki/Binfmt_misc)。当 Linux 遇到一种无法识别的可执行文件格式(比如说其它平台的可执行文件格式)时,它会检查有没有配置任何“用户空间应用程序”用于处理它。如果检测到了,就将可执行文件传递给该应用程序。

为此,我们需要在内核当中注册其它平台的可执行文件格式。

对于使用 Docker Desktop(MacOS 和 Windows 上都是)的同学,因为默认配置了 binfmt_misc,可以跳过这一步。而使用 Linux 发行版操作系统的同学则需要自行安装配置 binfmt_misc,以便能够非原生的其它平台的镜像。

要在宿主机上执行其它 CPU 平台的指令,需要安装 QEMU 模拟器。因为程序执行时会在当前程序可见的文件系统中查找动态库,而在容器或chroot环境中注册的处理程序在其它的 cgroup namespace 中可能无法找到,所以需要静态编译连接的QEMU。同时,我们需要安装一个包含足够新的update-binfmts二进制文件的包,以便能够支持fix-binary(F)标志,并在注册QEMU模拟器时实际使用,这样才能结合 buildx 一起镜像跨平台构建。

QEMU 和 binfmt_misc 支持工具可以通过宿主机或者Docker 容器镜像安装。但是,使用Docker镜像安装配置能让事情变得更加简单。镜像 docker/binfmt 中包含QEMU二进制文件和在binfmt_misc中注册QEMU的安装脚本。

docker run --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d

执行完后,我们验证下是否注册成功了。成功注册后,/proc/sys/fs/binfmt_misc 目录中会有多个qemu-前缀的文件。查看 /proc/sys/fs/binfmt_misc/qemu-aarch64 文件内容,可以看到 falgs 标志为 OCF,说明这个处理程序是通过 (F)标志注册的,能够正常的结合 buildx 完成跨平台构建。

 ? root@kofj-hk ~ ls -al /proc/sys/fs/binfmt_misc
total 0
drwxr-xr-x 2 root root 0 Oct 12 20:19 .
dr-xr-xr-x 1 root root 0 Oct 12 20:19 ..
-rw-r--r-- 1 root root 0 Oct 12 20:19 python2.7
-rw-r--r-- 1 root root 0 Oct 12 20:19 python3.6
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-aarch64
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-arm
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-ppc64le
-rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-s390x
--w------- 1 root root 0 Oct 12 20:19 register
-rw-r--r-- 1 root root 0 Oct 12 20:19 status
 ? root@kofj-hk ~ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b7
mask ffffffffffffff00fffffffffffffffffeffff

使用 buildx 构建

前置依赖注备好后,我们终于可以使用 buildx 构建多平台镜像了。与其它方案不同的是,使用 buildx 可以让我们不必改动 dockerfile。

Buildx 始终使用 BuildKit 引擎构建镜像,不需要配置环境变量DOCKER_BUILDKIT=1。BuildKit 可以很好的用于多个平台的构建,而不仅适用于我们当前构建镜像时所使用的平台和操作系统。进行构建时,使用 --platform标志可以用于指定构建输出的目标平台(例如 linux/amd64linux/arm64,linux/riscv64)。

首先,我们先准备好 Dockerfile 文件:

FROM golang:1.14 as builder
COPY . /src
WORKDIR /src
RUN ls -al && go build -a -tags netgo -ldflags '-w' -mod=vendor -v -o /src/bin/webapp /src/cmd/main.go
# Final image
FROM ubuntu:18.04
LABEL authors="Fanjian Kong"
COPY --from=builder /src/bin/webapp /app/
WORKDIR /app
CMD ["/app/webapp"]

然后,让我们尝试下运行 buildx。

? ? root@kofj-hk ~ docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t harbor-community.tencentcloudcr.com/multi-arch/demo:2020-10-12 . --push
error: auto-push is currently not implemented for docker driver, please create a new builder instance

居然报错 error: auto-push is currently not implemented for docker driver, please create a new builder instance 了!别担心,这是因为 Docker 默认的 builder 是不支持多平台构建的。我们可以通过 docker buildx ls 查看当前节点上的 builder 有哪些。

 ? root@kofj-hk ~ docker buildx  ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker
  default default         running linux/amd64, linux/386

为了使用多平台构建功能,我们需要新建一个 builder,并设置当前 builder 为新建的。

# 新建同时切换 builder 
docker buildx create --use --name mybuilder
# 只新建,然后再切换 builder
docker buildx create --name mybuilder
docker buildx use mybuilder

现在,让我们再次执行 buildx,看着一切向着期待的方向发展了。

注意事项

??注意1:到目前位置,buildx支持 linux/amd64, linux/386, linux/arm/v7, linux/arm/v6, linux/arm64, linux/ppc64le, linux/s390x。所以 docker/binfmt 镜像仅注册了 arm、ppc64le 和 s390x 的处理程序。如果你需要构建、运行 RISC-V 平台的容器镜像,建议使用 multiarch/qemu-user-static 镜像镜像配置。

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

??注意2:在 软件依赖 中我们提到需要 Linux 内核版本 >= 4.8.0;如果在内核版本为 3.10.0 的系统(比如 CentOS)上运行 docker/binfmt,会出现报错 Cannot write to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument,这是由于内核不支持 (F)标志造成的。出现这种情况,建议您升级系统内核或者换使用较高版本内核的 Linux 发行版。

小结

多年前,大规模部署应用程序是一项非常耗费人力、财力、时间,还需要大量技能和技巧的事务,工程师们还需要应对应用程序所运行的每一台服务器的环境差异。这对大公司而言是个极其沉重的负担,小公司更是无力应对。

正如多年前人们无法想象大规模部署复杂的应用程序只需要一个 kubectl create 命令,不久前我们也不会想到构建多平台的容器镜像只需要一个 docker buildx build。但是,我们还有更加广阔的想象空间,自动化流程、更多平台的支持、更智能简单的工具,你能想到的都有可能在不久的将来变成现实。

技术的发展进步,不断降低了生产活动中社会平均劳动时间,提升了生产力,能够释放劳动者去做更多有益的探索。让我们不断学习、拥抱、应用新技术,在时代的浪潮中勇往直前。

可执行方案回顾

  1. 确保使用的 Linux 发行版内核>=4.8.0(推荐使用 Ubuntu 18.04 以上的 TLS 发行版),且 Docker >= 19.03
  2. 启用Docker CLI 实验性功能: export DOCKER_CLI_EXPERIMENTAL=enabled
  3. 配置其它平台的模拟器:docker run --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
  4. 新建 Docker builder 实例支持多平台构建: docker buildx create --use --name mybuilder
  5. 在项目目录中执行构建: docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t harbor-community.tencentcloudcr.com/${YOUR_NAMESPACE}/multi-arch:2020-10-12 . --push
  6. 相关演示代码、脚本可以在 https://github.com/kofj/multi-arch-demo.git 获取。

推广时间

目前,腾讯云容器镜像服务 TCR已完成公测进入商业化阶段。我们也已经对部分用户开放了 Multi Arch 镜像和 OCI 云原生制品支持。如果您对该功能感兴趣,欢迎联系客服开通。

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!