使用Docker镜像进行CGO的Golang交叉编译

9

需求: 一个应用程序必须作为Docker镜像进行容器化,并且需要支持arm64amd64架构。

代码库: 这是一个使用git2go库的golang应用程序,必须具有CGO_ENABLED=1才能构建项目。最小可重现示例可以在这里的github链接中找到。

主机机器: 我正在使用arm64 M1 mac和docker桌面版构建应用程序,但在我们的amd64 Jenkins CI构建系统上结果相似。

Dockerfile:

FROM golang:1.17.6-alpine3.15 as builder

WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum

RUN apk add --no-cache libgit2 libgit2-dev git gcc g++ pkgconfig

RUN go mod download

COPY main.go main.go

ARG TARGETARCH TARGETOS

RUN CGO_ENABLED=1 GO111MODULE=on GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags static,system_libgit2 -a -o gitoperations main.go

FROM alpine:3.15 as runner

WORKDIR /
COPY --from=builder /workspace/gitoperations .
ENTRYPOINT ["/gitoperations"]

构建步骤:

docker buildx create --name gitops --use
docker buildx build --platform=linux/amd64,linux/arm64 --pull .

这个设置是有效的,但是在构建不同架构时构建时间太长了。这个特定的构建步骤: RUN CGO_ENABLED=1 GO111MODULE=on GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags static,system_libgit2 -a -o gitoperations main.go 在构建不同架构时的时间差总是比较大:

例如:

  1. 在 arm64 M1 mac(没有rossetta)上:构建 arm64 可执行文件需要约 30 秒,amd64 需要约 300 秒。
  2. 在我们的 amd64 Jenkins CI 系统上:构建 arm64 可执行文件所需的时间比构建 amd64 可执行文件所需的时间长 10 倍。

可以通过查看 docker buildx build 命令输出来查看这些构建时间。
我认为(当然我可能错了),这是因为 docker 在构建与主机机器 CPU 架构不同的 CPU 架构时使用了 qemu 模拟。因此,我想利用 golang 的交叉编译能力来加速构建时间。

我尝试过的方法:我考虑在这个 Dockerfile 中使用单个 builder 阶段来处理 arm 和 amd 架构,尝试使用以下语法:
FROM --platform=$BUILDPLATFORM golang:1.17.6-alpine3.15 as builder。 但是,在对 Dockerfile 进行此更改后使用相同的 docker build 命令会导致构建错误。在 arm64 M1 mac 上运行时,我得到了以下错误信息:
 > [linux/arm64->amd64 builder 9/9] RUN CGO_ENABLED=1 GO111MODULE=on GOOS=linux GOARCH=amd64 go build -tags static,system_libgit2 -a -o gitoperations main.go:
#0 1.219 # runtime/cgo
#0 1.219 gcc: error: unrecognized command-line option '-m64'

阅读了golang CGO文档后,我认为这个错误发生的原因是go没有选择能够为两种架构构建的正确c编译器,所以我需要设置CC环境变量来指示go使用哪个c编译器。

问题:我是否正确地假设qemu导致了构建时间差异,并且可以通过使用golang的本地交叉编译功能来减少?
如何使go build从任何主机机器上编译出amd64和arm64,使用docker桌面,因为我没有使用C代码和gcc的经验,也不确定我应该为CC标志设置什么值,如果我需要支持linux/amd64linux/arm64


你可以尝试使用基础镜像 Golang Ubuntu,我在 Alpine 上构建 CGO 时遇到了错误。 - Tùng Đào Thanh Tùng
5个回答

1
在 arm64 机器或容器上,安装 x86_64-linux-gnu-gcc,我正在使用的是 debian:bookworm。
# apt-get install g++-x86-64-linux-gnu libc6-dev-amd64-cross
# export CC=x86_64-linux-gnu-gcc
# export CXX=x86_64-linux-gnu-g++

也许你需要在arm64平台上安装其他的amd64库。
# dpkg --add-architecture amd64 
# apt-get update
# apt-get install libjpeg-dev:amd64

0
顺便分享一下我的经验,我也尝试使用 GitHub Actions 构建 Go 应用程序 Docker 镜像,并在 Docker 中运行 go build。虽然应用程序不是太大,但我觉得这是一个相当漫长的过程。然后我尝试在 Docker 外部构建二进制文件,这个过程要快得多,特别是如果我们存储了上一次构建过程的缓存。

这个回答完全没有用:它没有展示发帖者是如何做到的。 - TheDiveO

0

是的,您的想法是正确的,qemu导致了构建时间差异,使用golang的本地交叉编译功能可以帮助减少构建时间。

为了从任何主机机器使用Docker Desktop编译amd64和arm64架构,您可以在go build命令中设置CC环境变量以指定Go应使用的编译器。

例如,要进行交叉编译arm64,您可以设置CC=aarch64-linux-gnu-gcc。对于amd64,您可以设置CC=gcc。您可以将这些值作为参数设置在docker buildx build命令中。

请注意,您需要在主机机器上安装适当的交叉编译工具才能使其正常工作,例如aarch64-linux-gnu-gcc用于arm64


当构建过程在docker中进行时,为什么我需要在主机上安装这些交叉编译工具呢?我的问题是,我需要在docker内安装哪些工具才能使其正常工作? - ravi kumar
这个答案非常误导人,可能是错误的;请给出一个例子;从当前的措辞中,我不明白ravi指出的这个东西应该如何工作。你好ChatGPT,是你吗...? - TheDiveO

0
要在Go上编译C代码,您需要将CC变量设置为ARM交叉编译器。您可以通过go env命令查看您的CC变量。您遇到的错误与您使用的主机系统中的本地编译器有关。您应该在dockerfile中添加apk add gcc-arm-none-eabi。在下载必要的交叉编译工具之后,您需要通过我提到的命令将gcc命令链接到您已下载的编译器。然后,您应该能够为arm64编译您的应用程序。
您能否也分享一下go env的输出?您可能还需要编辑GOGCCFLAGS变量。

您似乎忽略了问题中明确指出“FROM golang:1.17.6-alpine3.15”,因此该问题涉及Alpine。在基于Alpine的镜像中,"sudo apt-get install"没有意义,在任何基于Debian/Ubuntu的镜像中,sudo部分也是错误的。请修正您的答案。 - TheDiveO
你似乎忽略了问题明确指出“FROM golang:1.17.6-alpine3.15”,所以问题是关于Alpine的。在基于Alpine的镜像中,"sudo apt-get install" 是没有意义的,而且在任何基于Debian/Ubuntu的镜像中,sudo部分也是错误的。请修正你的回答。 - TheDiveO
你似乎忽略了问题明确指出了“FROM golang:1.17.6-alpine3.15”,所以问题是关于Alpine的。在基于Alpine的镜像中,"sudo apt-get install" 是没有意义的,而且在任何基于Debian/Ubuntu的镜像中,sudo部分也是错误的。请修正你的回答。 - undefined
@TheDiveO 谢谢,我漏掉了那一部分。 - Berkant Ay
@TheDiveO 谢谢,我漏掉了那部分。 - Berkant Ay
@TheDiveO 谢谢,我漏掉了那部分。 - undefined

0

实际上,已经有一个干净整洁的解决方案,即@tonistiigi/xxxx . Dockerfile交叉编译助手。当我在研究Docker的多架构构建相关的Github操作时,我最初完全忽视了它的重要性。

文档中有一个专门介绍Go / Cgo的部分,详细解释了如何在Alpine上使用CGO进行交叉编译:

FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx

FROM --platform=$BUILDPLATFORM golang:alpine
RUN apk add clang lld
COPY --from=xx / /
ARG TARGETPLATFORM
RUN xx-apk add musl-dev gcc
ENV CGO_ENABLED=1
RUN xx-go build -o hello ./hello.go && \
    xx-verify hello

xx-apk 安装 目标 平台特定的软件包,而 apk 安装 构建 平台特定的软件包,因为我们使用了 FROM --platform=$BUILDPLATFORM ...

xx-go 方便地封装了 go 命令,在幕后提供所需的目标特定设置。最后的 xx-verify 是一个很好的检查方式,以确保生成的二进制文件实际上是针对 目标 平台,而不是意外地针对构建平台(如果它们不同)。

对于我的 @thediveo/lxkns 服务镜像,在免费的 Github action runner 上,linux/amd64+arm64 的构建时间从25分钟缩短到7分钟,大约快了3.5倍。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接