四、Docker Buildx 构建支持多系统架构的Docker镜像

发布时间 2023-08-25 16:08:35作者: ShyuRongLi

一、前言

1. 本文主要内容

使用 Docker Buldx 构建支持AMD64、ARM、ARM64等架构的镜像并传送到Docker Hub。

2. 环境支持

  1. 安装Docker >= 19.03
    该版本包含 buildx,该功能仅适用于 Docker v19.03+ 版本。

  2. Linux kernel >= 4.8
    自该Linux内核版本 binfmt_misc 支持 fix-binary (F) flag。fix_binary 标志允许内核在容器或chroot内使用binfmt_misc注册的二进制格式处理程序,即使该处理程序二进制文件不是该容器或chroot内可见的文件系统的一部分。

3. 启用 Buildx

Docker在19.03引入了一个新的特性,使得Docker可以构建不同CPU体系结构的镜像,比如ARM镜像,这是不必引入模拟器的情况下,Docker自身所提供的原生统一构建机制,但是使用时需要进行设定才能进行使用。(从 v20.10 版本开始,Docker CLI 所有实验特性的命令均默认开启,无需再进行配置或设置系统环境变量。)

buildx 命令属于实验特性,因此首先需要开启该特性。

运行命令

docker buildx version

出现如下问题,因为没开启Buildx 特性

docker: 'buildx' is not a docker command.
See 'docker --help'

运行命令安装

docker buildx install

在此运行命令,出现版本即可

docker buildx version
github.com/docker/buildx v0.9.1-docker ed00243a0ce2a0aee75311b06e32d33b44729689

查看已有的builder 实例

docker buildx ls

NAME/NODE    DRIVER/ENDPOINT             STATUS  BUILDKIT PLATFORMS
mybuilder *  docker-container                             
  mybuilder0 unix:///var/run/docker.sock stopped          
default      docker                                       
  default    default                     running 20.10.21 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

如果缺少一些架构,可以执行以下命令增加

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

二、镜像制作准备

1. 准备应用代码

用 golang 简单的写了一个 http server,监听8000端口,默认输出helloworld,并返回操作系统、HostName以及IP地址信息

新建 main.go 保存以下代码

package main

import (
    "fmt"
    "log"
    "net"
    "net/http"
    "os"
    "runtime"
    "strings"
)

func getHostName() string {
    hostname, err := os.Hostname()
    if err != nil {
        log.Fatalf("Failed to open log file: %v", err)
    }
    return hostname
}

func getIpAddresses() string {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        log.Fatal(err)
        return ""
    }
    var ips []string
    for _, address := range addrs {
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                ips = append(ips, ipnet.IP.String())
            }
        }
    }
    return strings.Join(ips, ",")
}

func handler(w http.ResponseWriter, r *http.Request) {
    log.Println("received request from", r.RemoteAddr, r.URL.Path[1:])
    var welcome = r.URL.Path[1:]
    if len(welcome) == 0 {
        welcome = "World"
    }
    fmt.Fprintf(w, "Hello, %s!  ---ken.io \r\n", welcome)
	fmt.Fprintf(w, "OS:%s/%s,Host:%s,IP:%s\r\n", runtime.GOOS, runtime.GOARCH, getHostName(), getIpAddresses())
}

func main() {
    http.HandleFunc("/", handler)
    log.Println("starting server on port 8000")
    log.Fatal(http.ListenAndServe(":8000", nil))
}

2. 编写Dockerfile 文件

新建 Dockerfile.build 文件并保存以下内容

# 使用官方提供的 Go 镜像作为基础镜像
FROM --platform=$TARGETPLATFORM$ golang:1.20

# 将工作目录设置为 /app
WORKDIR /app

# 将helloworld.go复制到 /app 下
COPY main.go /app

# 设置go mod 镜像
RUN go env -w GO111MODULE=on
RUN go env -w  GOPROXY=https://goproxy.cn,direct

# 导入依赖的Redis go module
RUN go mod init main

# 允许宿主机访问容器的 8000 端口
EXPOSE 8000

# 设置容器进程为:go run helloworld.go
CMD go run main.go

$TARGETPLATFORM 是内置变量,由 --platform 参数来指定其值

由于是基于 golang 的镜像来制作的,而 golang 是支持以下 7 种系统架构的,因此我们制作的镜像也就跟着支持这 7 种系统架构

linux/amd64, linux/arm/v6, linux/arm/v7, linux/arm64/v8, linux/386, linux/ppc64le, linux/s390x

这里穿插一句吐槽,简单统计了一下,ARM 的系统架构有如下各种简称:

arm64, armv8l, arm64v8, aarch64
arm, arm32, arm32v7, armv7, armv7l, armhf
arm32v6, armv6, armv6l, arm32v5, armv5,  armv5l, armel, aarch32

而对比 Intel 和 AMD 的就简单多了:

x86, 386, i386, i686
x86_64, x64, amd64

三、镜像制作

1. 确认 基础镜像 支持架构

2. 登录账号

访问 https://hub.docker.com/signup 注册账号,然后在Docker Desktop登录账号,或者通过命令登录

# 登录命令
docker login

# 根据命令号交互输入注册时的账号密码即可成功登录
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: xxx
Password:
Login Succeeded

Logging in with your password grants your terminal complete access to your account.
For better security, log in with a limited-privilege personal access token. Learn more at https://docs.doc

3. 构建镜像

# 进入main.go 所在目录
cd /data/dev


# 构建镜像(默认为latest)(注意结尾一定要加.)
# 这里选择构建常用 linux/amd64,linux/arm64 的架构,如有需要可以根据支持的架构自行追加或删减
docker buildx build \
--platform linux/amd64,linux/arm64 \
-f Dockerfile.build \
-t shyurongli/go-buildx . --push

4. 查看构建结果

命令执行成功后,你就会在 Docker Hub 看到你上传的镜像啦。示例图如下:
image

5. 镜像测试

# 拉取镜像
docker pull shyurongli/go-buildx

# 启动容器
docker run -itd --name go-buildx -p 8000:8000 shyurongli/go-buildx

# 访问测试
curl localhost:8000

# 输出示例
Hello, World!  ---ken.io 
OS:linux/amd64,Host:68dc587fb24f,IP:172.17.0.3

6. 查看镜像信息

docker buildx imagetools inspect shyurongli/go-buildx

image

最后

在制作多系统架构的 Docker 镜像时,建议使用 CPU 比较强或者多核心的 VPS 来构建,否则会非常耗时,本篇文章主要讲的是手动进行多架构镜像的构建,也可以是用cicd工具来自动化进行构建,后续文章进行说明

参考链接