DockerGo

Go - 跨平台构建Docker镜像

2023-05-06  本文已影响0人  红薯爱帅

1. 概述

Docker镜像支持多平台,即单个镜像可支持不同的OS和CPU架构,例如linux/amd64linux/arm64等。
在DockerHub,可以查看每个镜像支持的操作系统和处理器架构,如下图,python支持windows和linux两个操作系统,多种CPU架构。

python
ubuntu

在不同的操作系统和CPU架构下,通过docker pulldocker rundocker daemon会帮助我们自动选择适合的镜像,非常方便。
那么,如何build不同CPU架构的镜像呢,本文重点来介绍。

2. Buildx

集成了Buildx的BuildKit,可以实现跨平台的镜像构建,只需要通过--platform指定对应的平台即可,例如linux/amd64linux/arm64darwin/amd64

下面,先从一些术语来介绍其功能。

2.1. builder or builder实例

通过docker buildx ls可以查看,也支持createrm等操作

% 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.

2.2. builder node

一个builder可以包含多个node,作用:可提高build效率,也可支持更复杂的CPU架构下镜像构建。

实例创建之后可以添加新的节点,通过docker buildx create命令的--append选项可将--node <node>节点加入到--name <builder>选项指定的 builder 实例。如下将把一个远程节点加入 builder 实例:

$ docker buildx create --driver docker-container --platform linux/amd64 --name multi-builder
multi-builder
$ export DOCKER_HOST=tcp://10.10.150.66:2375
$ docker buildx create --name multi-builder --append --node remote-builder

刚创建的 builder 处于 inactive 状态,可以在 create 或 inspect 子命令中添加 --bootstrap 选项立即启动实例(可验证节点是否可用):

$ docker buildx inspect --bootstrap multi-builder
[+] Building 3.9s (2/2) FINISHED
 => [remote-builder internal] booting buildkit                                                                                                                                            3.9s
 => => pulling image moby/buildkit:buildx-stable-1                                                                                                                                        2.8s
 => => creating container buildx_buildkit_remote-builder                                                                                                                                  1.2s
 => [multi-builder0 internal] booting buildkit                                                                                                                                            3.7s
 => => pulling image moby/buildkit:buildx-stable-1                                                                                                                                        2.4s
 => => creating container buildx_buildkit_multi-builder0                                                                                                                                  1.3s
Name:   multi-builder
Driver: docker-container

Nodes:
Name:      multi-builder0
Endpoint:  unix:///var/run/docker.sock
Status:    running
Buildkit:  v0.10.5
Platforms: linux/amd64*, linux/386

Name:      remote-builder
Endpoint:  tcp://10.10.88.20:2375
Status:    running
Buildkit:  v0.10.5
Platforms: linux/arm64

docker buildx ls 将列出所有可用的 builder 实例和实例中的节点:

$ docker buildx ls
NAME/NODE         DRIVER/ENDPOINT         STATUS   PLATFORMS
multi-builder       docker-container
  multi-builder0    unix:///var/run/docker.sock running v0.10.5  linux/amd64*, linux/386
  remote-builder    tcp://10.10.88.20:2375      running v0.10.5  linux/arm64
default *         docker                           
  default         default                 running  linux/amd64, linux/386

如上就创建了一个支持多平台架构的 builder 实例,执行docker buildx use <builder>将切换到所指定的 builder 实例。

docker buildx inspectdocker buildx stopdocker buildx rm 命令用于管理一个实例的生命周期。

参考:如何使用 docker buildx 构建跨平台 Go 镜像 | Shall We Code? (waynerv.com)

2.3. builder driver

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

docker驱动无法使用一小部分 buildx 的特性,例如不能在一次运行中同时构建多个平台镜像。此外,在镜像的默认输出格式上也有所区别:docker驱动默认将构建结果以 Docker 镜像格式直接输出到 docker 的镜像目录(通常是 /var/lib/overlay2),之后执行 docker images 命令可以列出所输出的镜像。而,docker container则需要通过--output选项指定输出格式为镜像或其他格式。

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

2.4. dockerfile中的环境变量

以下面的dockerfile为例,可以看到对应的env值:

# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log

FROM alpine
COPY --from=build /log /log

build命令:docker buildx build --platform linux/amd64,linux/arm64 -t test:0 -o type=oci,dest=./oci-image .

2.5. output

执行构建命令时,除了指定镜像名称,另外两个重要的选项是指定目标平台输出格式

当使用 docker-container 驱动时,--platform可以接受用逗号分隔的多个值作为输入以同时指定多个目标平台,所有平台的构建结果将合并为一个整体的镜像列表作为输出,因此,无法直接输出为本地的 docker images 镜像。

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

常用的也就两种方式,分别是-o type=registry-o type=docker,dest=./linux-amd64-image
第一种,支持同时build多个镜像,第二种每次只能build一个特定平台的镜像。

docker buildx build --platform linux/amd64,linux/arm64 -t test:0 -o type=registry

# 上面的命令等价与下面两条命令
docker buildx build --platform linux/amd64 -t test:0 -o type=docker,dest=./linux-amd64-image .
docker buildx build --platform linux/arm64 -t test:0 -o type=docker,dest=./linux-arm64-image .
% docker buildx build --platform linux/amd64 -t test:0 -o type=docker,dest=./linux-amd64-image .
[+] Building 7.8s (11/11) FINISHED
 => [internal] load build definition from dockerfile                                                                                                     0.0s
 => => transferring dockerfile: 250B                                                                                                                     0.0s
 => [internal] load .dockerignore                                                                                                                        0.0s
 => => transferring context: 2B                                                                                                                          0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                                                                                         7.6s
 => [internal] load metadata for docker.io/library/golang:alpine                                                                                         7.4s
 => [auth] library/golang:pull token for registry-1.docker.io                                                                                            0.0s
 => [auth] library/alpine:pull token for registry-1.docker.io                                                                                            0.0s
 => [build 1/2] FROM docker.io/library/golang:alpine@sha256:913de96707b0460bcfdfe422796bb6e559fc300f6c53286777805a9a3010a5ea                             0.0s
 => => resolve docker.io/library/golang:alpine@sha256:913de96707b0460bcfdfe422796bb6e559fc300f6c53286777805a9a3010a5ea                                   0.0s
 => [stage-1 1/2] FROM docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126                                  0.0s
 => => resolve docker.io/library/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126                                          0.0s
 => CACHED [build 2/2] RUN echo "I am running on linux/arm64, building for linux/amd64" > /log                                                           0.0s
 => CACHED [stage-1 2/2] COPY --from=build /log /log                                                                                                     0.0s
 => exporting to docker image format                                                                                                                     0.1s
 => => exporting layers                                                                                                                                  0.0s
 => => exporting manifest sha256:5f55c6c58da4636aa38363b1aed04cc2ff1921bfb12c42aa308430a8a20c12ac                                                        0.0s
 => => exporting config sha256:f2ce60afc09e4bb74d905f6b1a58504c140f2090a1420d275e89381a36c9947e                                                          0.0s
 => => sending tarball                                                                                                                                   0.1s
% ll
total 4108
-rw-r--r-- 1 shuzhang staff     211  5  7 16:34 dockerfile
-rw-r--r-- 1 shuzhang staff 3384832  5  7 17:26 linux-amd64-image

% docker load < linux-amd64-image
f1417ff83b31: Loading layer [==================================================>]  3.375MB/3.375MB
c5ddf0bc21cf: Loading layer [==================================================>]     148B/148B
Loaded image: test:0

% docker images | grep test
test                                                                  0                 f2ce60afc09e   50 minutes ago   7.05MB

3. Example

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

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Println("Hello world!")
    fmt.Printf("Running in [%s] architecture.\n", runtime.GOARCH)
}

定义构建过程的 Dockerfile 如下:

FROM --platform=$BUILDPLATFORM golang:1.14 as builder

ARG TARGETOS
ARG TARGETARCH

WORKDIR /app
COPY main.go /app/main.go
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -o output/main main.go

FROM alpine:latest
WORKDIR /root
COPY --from=builder /app/output/main .
CMD /root/main

构建过程分为两个阶段:

4. Setup builder

Docker Desktop provides binfmt_misc multi-architecture support, which means you can run containers for different Linux architectures such as arm, mips, ppc64le, and even s390x.

对于没有Desktop的服务器而言,最新的docker版本默认支持buildx,可能需要安装binfmt,以支持跨平台构建。

# 检查我们当前的buildx可以使用的全部的构建实例/构建节点,我们发现还是使用默认的,没有任何改变
[thinktik@thinkdev ~]$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker                  
  default default         running linux/amd64, linux/386
  
# 安装binfmt,它内置了QEMU能提供跨平台构建功能
[thinktik@thinkdev ~]$ docker run --privileged --rm tonistiigi/binfmt --install all
Unable to find image 'tonistiigi/binfmt:latest' locally
latest: Pulling from tonistiigi/binfmt
2b4d0e08bd75: Pull complete 
c331be51c382: Pull complete 
Digest: sha256:5bf63a53ad6222538112b5ced0f1afb8509132773ea6dd3991a197464962854e
Status: Downloaded newer image for tonistiigi/binfmt:latest
installing: s390x OK
installing: riscv64 OK
installing: mips64le OK
installing: mips64 OK
installing: arm64 OK
installing: arm OK
installing: ppc64le OK
{
  "supported": [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/mips64le",
    "linux/mips64",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "qemu-aarch64",
    "qemu-arm",
    "qemu-mips64",
    "qemu-mips64el",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}

# 创建一个buildx构建器
[thinktik@thinkdev ~]$ docker buildx create --name crossbuilder --driver docker-container
crossbuilder
# 使用新创建的构建器
[thinktik@thinkdev ~]$ docker buildx use crossbuilder
# 再次检查buildx可以使用的构建实例/构建节点,我们看到新加了一个crossbuilder,并且显示支持了一大批CPU架构
[thinktik@thinkdev ~]$ docker buildx ls
NAME/NODE       DRIVER/ENDPOINT             STATUS   PLATFORMS
crossbuilder *  docker-container                     
  crossbuilder0 unix:///var/run/docker.sock inactive 
default         docker                               
  default       default                     running  linux/amd64, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6

5. References

上一篇下一篇

猜你喜欢

热点阅读