如何使用 docker buildx 构建跨平台 Go 镜像
gudong366 2025-05-18 13:35 35 浏览
为了在不同操作系统和处理器架构上运行应用,为不同平台单独构建程序版本是很常见的场景。当开发应用的平台与部署的目标平台不同时,实现这一目标并不容易。例如在 x86 架构上开发一个应用程序并将其部署到 ARM 平台的机器上,通常需要准备 ARM 平台的基础设施用于开发和编译。
一次构建多处部署的镜像分发大幅提高了应用的交付效率,对于需要跨平台部署应用但基础设施不够充分的场景,利用 docker buildx 构建跨平台的镜像是一种快捷高效的解决方案。
前提
大部分镜像托管平台支持多平台镜像,这意味着镜像仓库中单个标签可以包含不同平台的多个镜像,以 docker hub 的 python 镜像仓库为例,3.9.6 这个标签就包含了 10 个不同系统和架构的镜像(平台 = 系统 + 架构)。
通过 docker pull 或 docker run 拉取一个支持跨平台的镜像时,docker 会自动选择与当前运行平台相匹配的镜像。由于该特性的存在,在进行镜像的跨平台分发时,镜像的消费端是无感知,我们只需要关心镜像的生产,即如何构建跨平台的镜像。
docker buildx
默认的 docker build 命令无法完成跨平台构建任务,需要为 docker 命令行工具安装 buildx 插件扩展其功能。buildx 能够使用由 Moby BuildKit 提供的构建镜像额外特性,它能够创建多个 builder 实例,在多个节点并行地执行构建任务,以及跨平台构建。
启用 Buildx
macOS 或 Windows 系统的 Docker Desktop,以及 Linux 发行版通过 deb 或者 rpm 包所安装的 docker 内置了 buildx,不需要另行安装。
如果你的 docker 没有 buildx 命令,可以下载二进制包进行安装:
- 首先从 Docker buildx 项目的 release 页面找到适合自己平台的二进制文件。
- 下载二进制文件到本地并重命名为 docker-buildx,移动到 docker 的插件目录 ~/.docker/cli-plugins。
- 向二进制文件授予可执行权限。
如果本地的 docker 版本高于 19.03,可以通过以下命令直接在本地构建并安装,这种方式更为方便:
$ 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
使用 buildx 进行构建的方法如下:
docker buildx build .
buildx 和 docker build 命令的使用体验基本一致,还支持 build 常用的选项如 -t、-f 等。
builder 实例
docker buildx 通过 builder 实例对象来管理构建配置和节点,命令行将构建任务发送至 builder 实例,再由 builder 指派给符合条件的节点执行。我们可以基于同一个 docker 服务程序创建多个 builder 实例,提供给不同的项目使用以隔离各个项目的配置,也可以为一组远程 docker 节点创建一个 builder 实例组成构建阵列,并在不同阵列之间快速切换。
使用 docker buildx create 命令可以创建 builder 实例,这将以当前使用的 docker 服务为节点创建一个新的 builder 实例。要使用一个远程节点,可以在创建示例时通过 DOCKER_HOST 环境变量指定远程端口或提前切换到远程节点的 docker context。下面以远程节点创建一个新的 builder 实例并通过命令行选项指定其驱动、目标平台和实例名称:
$ export DOCKER_HOST=tcp://10.10.150.66:2375
$ docker buildx create --driver docker-container --platform linux/amd64,linux/arm64 --name remote-builder
remote-builder
docker buildx ls 将列出所有可用的 builder 实例和实例中的节点:
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
remote-builder docker-container
remote-builder0 tcp://10.10.150.66:2375 inactive linux/amd64*, linux/arm64*
default * docker
default default running linux/amd64, linux/386
实例创建之后可以继续向它添加新的节点,通过 docker buildx create 命令的 --append <node> 选项可将节点加入到 --name <builder> 选项指定的 builder 实例:
$ docker buildx create --name default --append remote-builder0
docker buildx inspect、docker buildx stop 和 docker buildx rm 命令用于管理一个实例的生命周期。
docker buildx use <builder> 将切换到所指定的 builder 实例。
构建驱动
buildx 实例通过两种方式来执行构建任务,两种执行方式被称为使用不同的「驱动」:
- docker 驱动:使用 Docker 服务程序中集成的 BuildKit 库执行构建。
- docker-container 驱动:启动一个包含 BuildKit 的容器并在容器中执行构建。
docker 驱动无法使用一小部分 buildx 的特性(如在一次运行中同时构建多个平台镜像),此外在镜像的默认输出格式上也有所区别:docker 驱动默认将构建结果以 Docker 镜像格式直接输出到 docker 的镜像目录(通常是 /var/lib/overlay2),之后执行 docker images 命令可以列出所输出的镜像;而 docker container 则需要通过 --output 选项指定输出格式为镜像或其他格式。
为了一次性构建多个平台的镜像,下文将使用 docker container 驱动的 builder 实例。
buildx 的跨平台构建策略
根据构建节点和目标程序语言不同,buildx 支持以下三种跨平台构建策略:
- 通过 QEMU 的用户态模式创建轻量级的虚拟机,在虚拟机系统中构建镜像。
- 在一个 builder 实例中加入多个不同目标平台的节点,通过原生节点构建对应平台镜像。
- 分阶段构建并且交叉编译到不同的目标架构。
QEMU 通常用于模拟完整的操作系统,它还可以通过用户态模式运行:以 binfmt_misc 在宿主机系统中注册一个二进制转换处理程序,并在程序运行时动态翻译二进制文件,根据需要将系统调用从目标 CPU 架构转换为当前系统的 CPU 架构。最终的效果就像在一个虚拟机中运行目标 CPU 架构的二进制文件。Docker Desktop 内置了 QEMU 支持,其他满足运行要求的平台可通过以下方式安装:
$ docker run --privileged --rm tonistiigi/binfmt --install all
这种方式不需要对已有的 Dockerfile 做任何修改,实现的成本很低,但显而易见效率并不高。
将不同系统架构的原生节点添加到 builder 实例中可以为跨平台编译带来更好的支持,而且效率更高,但需要有足够的基础设施支持。
如果构建项目所使用的程序语言支持交叉编译(如 C 和 Go),可以利用 Dockerfile 提供的分阶段构建特性:首先在和构建节点相同的架构中编译出目标架构的二进制文件,再将这些二进制文件复制到目标架构的另一镜像中。下文会使用 Go 实现一个具体的示例。这种方式不需要额外的硬件,也能得到较好的性能,但只有特定编程语言能够实现。
一次构建多个架构 Go 镜像实践
源代码和 Dockerfile
下面将以一个简单的 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 TARGETARCH
WORKDIR /app
COPY main.go /app/main.go
RUN GOOS=linux GOARCH=$TARGETARCH go build -a -o output/main main.go
FROM alpine:latest
WORKDIR /root
COPY --from=builder /app/output/main .
CMD /root/main
构建过程分为两个阶段:
- 在一阶段中,我们将拉取一个和当前构建节点相同平台的 golang 镜像,并使用 Go 的交叉编译特性将其编译为目标架构的二进制文件。
- 然后拉取目标平台的 alpine 镜像,并将上一阶段的编译结果拷贝到镜像中。
执行跨平台构建
执行构建命令时,除了指定镜像名称,另外两个重要的选项是指定目标平台和输出格式。
docker buildx build 通过 --platform 选项指定构建的目标平台。Dockerfile 中的 FROM 指令如果没有设置 --platform 标志,就会以目标平台拉取基础镜像,最终生成的镜像也将属于目标平台。当使用 docker-container 驱动时,这个选项可以接受用逗号分隔的多个值作为输入以同时指定多个目标平台,所有平台的构建结果将合并为一个整体的镜像列表作为输出,因此无法直接输出为本地的 docker images 镜像。此外 Dockerfile 中可通过 BUILDPLATFORM、TARGETPLATFORM、BUILDARCH 和 TARGETARCH 等参数使用该选项的值。
docker buildx build 支持丰富的输出行为,通过--output=[PATH,-,type=TYPE[,KEY=VALUE] 选项可以指定构建结果的输出类型和路径等,常用的输出类型有以下几种:
- local:构建结果将以文件系统格式写入 dest 指定的本地路径, 如 --output type=local,dest=./output。
- tar:构建结果将在打包后写入 dest 指定的本地路径。
- oci:构建结果以 OCI 标准镜像格式写入 dest 指定的本地路径。
- docker:构建结果以 Docker 标准镜像格式写入 dest 指定的本地路径或加载到 docker 的镜像库中。同时指定多个目标平台时无法使用该选项。
- image:以镜像或者镜像列表输出,并支持 push=true 选项直接推送到远程仓库,同时指定多个目标平台时可使用该选项。
- registry:type=image,push=true 的精简表示。
对本示例我们执行如下 docker buildx build 命令:
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo -o type=registry .
该命令将在当前目录同时构建 linux/amd64、 linux/arm64 和 linux/arm 三种平台的镜像,并将输出结果直接推送到远程的阿里云私人镜像仓库中。
构建过程可拆解如下:
- docker 将构建上下文传输给 builder 实例。
- builder 为命令行 --platform 选项指定的每一个目标平台构建镜像,包括拉取基础镜像和执行构建步骤。
- 导出构建结果,镜像文件层被推送到远程仓库。
- 生成一个清单 JSON 文件,并将其作为镜像标签推送给远程仓库。
验证构建结果
运行结束后可以通过 docker buildx imagetools 探查已推送到远程仓库的镜像:
$ docker buildx imagetools inspect registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest
Name: registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:e2c3c5b330c19ac9d09f8aaccc40224f8673e12b88ff59cb68971c36b76e95ca
Manifests:
Name: registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:cb6a7614ee3db03c8858e3680b1585f32a6fe3de9b371e37e25cf42a83f6e0ba
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
Name: registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:034aa0077a452a6c2585f8b4969c7c85d5d2bf65f801fcc803a00d0879ce900e
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64
Name: registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:db0ee3a876fb789d2e733471385eef0a056f64ee12d9e7ef94e411469d054eb5
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm/v7
最后在不同的平台以 latest 标签拉取并运行镜像,验证构建结果是否正确。
使用 Docker Desktop 时,其本身集成的虚拟化功能可以运行不同平台的镜像,可以直接以 sha256 值拉取镜像:
$ docker run --rm registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:cb6a7614ee3db03c8858e3680b1585f32a6fe3de9b371e37e25cf42a83f6e0ba
Hello world!
Running in [amd64] architecture.
$ docker run --rm registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:034aa0077a452a6c2585f8b4969c7c85d5d2bf65f801fcc803a00d0879ce900e
Hello world!
Running in [arm64] architecture.
如何交叉编译 Golang 的 CGO 项目
支持交叉编译到常见的操作系统和 CPU 架构是 Golang 的一大优势,但以上示例中的解决方案只适用于纯 Go 代码,如果项目中通过 cgo 调用了 C 代码,情况会变得更加复杂。
准备交叉编译环境和依赖
为了能够顺利编译 C 代码到目标平台,首先需要在编译环境中安装目标平台的 C 交叉编译器(通常基于 gcc),常用的 Linux 发行版会提供大部分平台的交叉编译器安装包,可以直接通过包管理器安装。
其次还需要安装目标平台的 C 标准库(通常标准库会作为交叉编译器的安装依赖,不需要单独安装),另外取决于你所调用的 C 代码的依赖关系,可能还需要安装一些额外的 C 依赖库(如 libopus-dev 之类)。
我们将使用 amd64 架构的 golang:1.14 官方镜像作为基础镜像执行编译,其使用的 Linux 发行版为 Debian。假设交叉编译的目标平台是 linux/arm64,则需要准备的交叉编译器为 gcc-aarch64-linux-gnu,C 标准库为 libc6-dev-arm64-cross,安装方式为:
$ apt-get update
$ apt-get install gcc-aarch64-linux-gnu
libc6-dev-arm64-cross 会同时被安装。
得益于 Debian 包管理器 dpkg 提供的多架构安装能力,假如我们的代码依赖 libopus-dev 等非标准库,可通过 <library>:<architecture> 的方式安装其 arm64 架构的安装包:
$ dpkg --add-architecture arm64
$ apt-get update
$ apt-get install -y libopus-dev:arm64
交叉编译 CGO 示例
假设有如下 cgo 的示例代码:
package main
/*
#include <stdlib.h>
*/
import "C"
import "fmt"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
func main() {
rand := Random()
fmt.Printf("Hello %d\n", rand)
}
将使用的 Dockerfile 如下:
FROM --platform=$BUILDPLATFORM golang:1.14 as builder
ARG TARGETARCH
RUN apt-get update && apt-get install -y gcc-aarch64-linux-gnu
WORKDIR /app
COPY . /app/
RUN if [ "$TARGETARCH" = "arm64" ]; then CC=aarch64-linux-gnu-gcc && CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
CGO_ENABLED=1 GOOS=linux GOARCH=$TARGETARCH CC=$CC CC_FOR_TARGET=$CC_FOR_TARGET go build -a -ldflags '-extldflags "-static"' -o /main main.go
Dockerfile 中通过 apt-get 安装了 gcc-aarch64-linux-gnu 作为交叉编译器,示例程序较为简单因此不需要额外的依赖库。在执行 go build 进行编译时,需要通过 CC 和 CC_FOR_TARGET 环境变量指定所使用的交叉编译器。
为了基于同一份 Dockerfile 执行多个目标平台的编译(假设目标架构只有 amd64/arm64),最下方的 RUN 指令使用了一个小技巧,通过 Bash 的条件判断语法来执行不同的编译命令:
- 假如构建任务的目标平台是 arm64,则指定 CC 和 CC_FOR_TARGET 环境变量为已安装的交叉编译器(注意它们的值有所不同)。
- 假如构建任务的目标平台是 amd64,则不指定交叉编译器相关的变量,此时将使用默认的 gcc 作为编译器。
最后使用 buildx 执行构建的命令如下:
$ docker buildx build --platform linux/amd64,linux/arm64 -t registry.cn-hangzhou.aliyuncs.com/waynerv/cgo-demo -o type=registry .
总结
有了 Buildx 插件的帮助,在缺少基础设施的情况下,我们也能使用 docker 方便地构建跨平台的应用镜像。
但默认通过 QEMU 虚拟化目标平台指令的方式有明显地性能瓶颈,如果编写应用的语言支持交叉编译,我们可以通过结合 buildx 和交叉编译获得更高的效率。
本文最后介绍了一种进阶场景的解决方案:如何对使用了 CGO 的 Golang 项目进行交叉编译,并给出了编译到 linux/arm64 平台的示例。
相关推荐
- 使用再生龙工具远程克隆Linux服务器
-
大家好,之前给大家介绍一个一款可以用来备份还原、远程克隆、P2V、V2V的工具--再生龙,今天就来给大家演示如何用该工具来远程克隆一台linux服务器。使用此方法,可以将一台物理服务器远程克隆到虚拟...
- Linux 下用 SSH 登录远程服务器后把远程服务器文件传本地电脑
-
在Linux下,使用SSH命令登录远程服务器后,可以使用scp命令将远程服务器上的文件复制到本地电脑。以下是scp命令的基本用法:scp[用户名]@[远程服务器地址]:[远程文件路径][本地存放路...
- 一文掌握怎么利用Shell+Python实现Linux系统数据异地备份程序
-
简介:在当今的信息化时代,数据安全已成为企业和个人运维的重中之重。无论是服务器宕机、硬盘损坏,还是遭遇勒索病毒,数据丢失都可能带来巨大损失。为了最大程度保障数据安全,异地备份成为了最佳实践之一。本文将...
- 如何在Linux上搭建本地Docker Registry并实现远程连接
-
在Linux上搭建本地DockerRegistry并实现远程连接,可以按照以下步骤操作:一、安装Docker确保Linux系统上已经安装了Docker。如果尚未安装,可以使用以下命令进行安装(以Ub...
- 服务器连接方法教程(服务器地址怎么连接)
-
连接服务器的方式多种多样,具体取决于服务器的类型、操作系统以及你的使用需求。以下是几种常见的服务器连接方法,包含详细步骤和注意事项:一、远程桌面连接(适用于Windows服务器)适用场景:需要图形...
- 自动化测试学习:使用python库Paramiko实现远程服务器上传和下载
-
前言测试过程中经常会遇到需要将本地的文件上传到远程服务器上,或者需要将服务器上的文件拉到本地进行操作,以前安静经常会用到xftp工具。今天安静介绍一种python库Paramiko,可以帮助我们通过代...
- 手把手教你安装、远程连接Ubuntu 22.04
-
Ubuntu分为桌面版和服务器版本,我们选择服务器版本1下载Ubuntu22.04Ubuntu22.04下载地址:https://releases.ubuntu.com/22.04/ubuntu...
- Windows服务器怎么连接?远程连接服务器命令
-
服务器操作系统可以实现对计算机硬件与软件的直接控制和管理协调,任何计算机的运行离不开操作系统,服务器也一样,服务器操作系统主要分为四大流派:WindowsServer、Netware、Unix和Li...
- 如何使用JuiceSSH实现手机端远程连接Linux服务器
-
在当今数字化时代,远程连接到服务器成为了许多人工作和生活中的必需品。JuiceSSH是一款比较强大的Android应用程序,它可以让您在手机上轻松地远程连接到Linux服务器。下面简单的向您介绍如何使...
- 本地电脑如何远程连接服务器(电脑如何远程桌面连接服务器)
-
下面就来说说如何远程登录服务器。服务器一般有两大类系统,一种是windows系统,一种是Linux系统。下面以Windows系统为例1、Windows系统有自带的登录系统,点击“运行”(或者windo...
- 如何用CHAT配置linux的远程连接?(chattr linux)
-
问CHAT:配置linux的远程连接1.下载ssh2.启动ssh服务3.查看ssh服务状态4.设置ssh服务开机自启动5.设置windows的cmd下ssh6.通过cmd的ssh命令远程到...
- 服务器怎么远程连接控制(服务器远程桌面连接设置方法)
-
我是艾西,还是有很多小白同学问我服务器怎么远程连接。那么今天我们重点来教教大家如何用电脑远程服务器配上图文教程,让不懂的新手小白一看就会,分分钟上手教程远程服务器需要一台电脑俗称“PC”就是我们自己平...
- 如何远程管理Linux服务器(linux远程登录管理)
-
在当今数字化的时代,Linux服务器凭借其稳定性和高效性,成为众多企业和开发者的首选。然而,很多时候我们无法直接在服务器前操作,这就需要掌握远程管理Linux服务器的技巧啦。别担心,今天就来给大家分享...
- Linux系统无法启动?别慌!这可能是全网最全的故障排查攻略
-
当Linux系统罢工时,盲目重装只会浪费时间!本文整理8种常见故障的解决方案,涵盖从引导修复到硬件检测全流程,建议收藏备用。一、引导阶段故障排查1.GRUB引导丢失现象:黑屏显示"grub&...
- Linux进程管理(linux进程管理实验报告)
-
原作者:Linux教程,原文「链接」:https://mp.weixin.qq.com/s/39rQMl3V2Egot9cZ14NCLg【获得原作者转载授权】每个计算机系统都包含一个核心软件集合,即操...
- 一周热门
- 最近发表
- 标签列表
-
- linux一键安装 (31)
- linux运行java (33)
- ln linux (27)
- linux 磁盘管理 (31)
- linux 内核升级 (30)
- linux 运行python (28)
- linux 备份文件 (30)
- linux 网络测试 (30)
- linux 网关配置 (31)
- linux jre (32)
- linux 杀毒软件 (32)
- linux语法 (33)
- linux博客 (33)
- linux 压缩目录 (37)
- linux 查看任务 (32)
- 制作linux启动u盘 (35)
- linux 查看存储 (29)
- linux乌班图 (31)
- linux挂载镜像 (31)
- linux 软件源 (28)
- linux题目 (30)
- linux 定时脚本 (30)
- linux 网站搭建 (28)
- linux 远程控制 (34)
- linux bind (31)