Go - 跨平臺構(gòu)建Docker鏡像

1. 概述

Docker鏡像支持多平臺,即單個鏡像可支持不同的OS和CPU架構(gòu),例如linux/amd64、linux/arm64等。
在DockerHub,可以查看每個鏡像支持的操作系統(tǒng)和處理器架構(gòu),如下圖,python支持windows和linux兩個操作系統(tǒng),多種CPU架構(gòu)。

python

ubuntu

在不同的操作系統(tǒng)和CPU架構(gòu)下,通過docker pulldocker run,docker daemon會幫助我們自動選擇適合的鏡像,非常方便。
那么,如何build不同CPU架構(gòu)的鏡像呢,本文重點來介紹。

2. Buildx

集成了Buildx的BuildKit,可以實現(xiàn)跨平臺的鏡像構(gòu)建,只需要通過--platform指定對應的平臺即可,例如linux/amd64linux/arm64、darwin/amd64。

下面,先從一些術(shù)語來介紹其功能。

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架構(gòu)下鏡像構(gòu)建。

實例創(chuàng)建之后可以添加新的節(jié)點,通過docker buildx create命令的--append選項可將--node <node>節(jié)點加入到--name <builder>選項指定的 builder 實例。如下將把一個遠程節(jié)點加入 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

剛創(chuàng)建的 builder 處于 inactive 狀態(tài),可以在 create 或 inspect 子命令中添加 --bootstrap 選項立即啟動實例(可驗證節(jié)點是否可用):

$ 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 實例和實例中的節(jié)點:

$ 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

如上就創(chuàng)建了一個支持多平臺架構(gòu)的 builder 實例,執(zhí)行docker buildx use <builder>將切換到所指定的 builder 實例。

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

參考:如何使用 docker buildx 構(gòu)建跨平臺 Go 鏡像 | Shall We Code? (waynerv.com)

2.3. builder driver

buildx 實例通過兩種方式來執(zhí)行構(gòu)建任務,兩種執(zhí)行方式被稱為使用不同的驅(qū)動

  • docker 驅(qū)動:使用 Docker 服務程序中集成的 BuildKit 庫執(zhí)行構(gòu)建
  • docker-container 驅(qū)動:啟動一個包含 BuildKit 的容器并在容器中執(zhí)行構(gòu)建

docker驅(qū)動無法使用一小部分 buildx 的特性,例如不能在一次運行中同時構(gòu)建多個平臺鏡像。此外,在鏡像的默認輸出格式上也有所區(qū)別:docker驅(qū)動默認將構(gòu)建結(jié)果以 Docker 鏡像格式直接輸出到 docker 的鏡像目錄(通常是 /var/lib/overlay2),之后執(zhí)行 docker images 命令可以列出所輸出的鏡像。而,docker container則需要通過--output選項指定輸出格式為鏡像或其他格式。

為了一次性構(gòu)建多個平臺的鏡像,本文使用docker container作為默認的 builder 實例驅(qū)動。

2.4. dockerfile中的環(huán)境變量

  • BUILDPLATFORM,構(gòu)建節(jié)點的平臺信息,例如linux/amd64、linux/arm64
  • BUILDARCH
  • BUILDOS
  • TARGETPLATFORM,目標平臺信息
  • TARGETARCH
  • TARGETOS

以下面的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

執(zhí)行構(gòu)建命令時,除了指定鏡像名稱,另外兩個重要的選項是指定目標平臺輸出格式。

當使用 docker-container 驅(qū)動時,--platform可以接受用逗號分隔的多個值作為輸入以同時指定多個目標平臺,所有平臺的構(gòu)建結(jié)果將合并為一個整體的鏡像列表作為輸出,因此,無法直接輸出為本地的 docker images 鏡像。

docker buildx build支持豐富的輸出行為,通過--output=[PATH,-,type=TYPE[,KEY=VALUE]選項可以指定構(gòu)建結(jié)果的輸出類型和路徑等,常用的輸出類型有以下幾種:

  • local:構(gòu)建結(jié)果將以文件系統(tǒng)格式寫入 dest 指定的本地路徑, 如 --output type=local,dest=./output
  • tar:構(gòu)建結(jié)果將在打包后寫入 dest 指定的本地路徑
  • oci:構(gòu)建結(jié)果以 OCI 標準鏡像格式寫入 dest 指定的本地路徑
  • docker:構(gòu)建結(jié)果以 Docker 標準鏡像格式寫入 dest 指定的本地路徑或加載到 docker 的鏡像庫中。同時指定多個目標平臺時無法使用該選項
  • image:以鏡像或者鏡像列表輸出,并支持 push=true 選項直接推送到遠程倉庫,同時指定多個目標平臺時可使用該選項
  • registry:type=image,push=true 的精簡表示

常用的也就兩種方式,分別是-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 內(nèi)容如下:

package main

import (
    "fmt"
    "runtime"
)

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

定義構(gòu)建過程的 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

構(gòu)建過程分為兩個階段:

  • 在一階段中,我們將拉取一個和當前構(gòu)建節(jié)點相同平臺的 golang 鏡像,并使用 Go 的交叉編譯特性將其編譯為目標架構(gòu)的二進制文件。
  • 然后拉取目標平臺的 alpine 鏡像,并將上一階段的編譯結(jié)果拷貝到鏡像中。

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,以支持跨平臺構(gòu)建。

# 檢查我們當前的buildx可以使用的全部的構(gòu)建實例/構(gòu)建節(jié)點,我們發(fā)現(xiàn)還是使用默認的,沒有任何改變
[thinktik@thinkdev ~]$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker                  
  default default         running linux/amd64, linux/386
  
# 安裝binfmt,它內(nèi)置了QEMU能提供跨平臺構(gòu)建功能
[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"
  ]
}

# 創(chuàng)建一個buildx構(gòu)建器
[thinktik@thinkdev ~]$ docker buildx create --name crossbuilder --driver docker-container
crossbuilder
# 使用新創(chuàng)建的構(gòu)建器
[thinktik@thinkdev ~]$ docker buildx use crossbuilder
# 再次檢查buildx可以使用的構(gòu)建實例/構(gòu)建節(jié)點,我們看到新加了一個crossbuilder,并且顯示支持了一大批CPU架構(gòu)
[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

補充,最新的跨平臺構(gòu)象鏡像,只在docker-desktop中設置一下即可

docker-desktop開啟跨平臺構(gòu)建

5. References

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容