Golang与CGO内部docker图像交叉编译



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

Codebase:它是一个golang应用程序,需要使用git2go库,并且必须具有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总是长10倍:

示例:

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

可以通过查看docker buildx build命令输出来查看此构建时间
我相信(我肯定错了)这是因为docker在为与主机的cpu架构不同的cpu架构构建时使用了qemu仿真。因此,我想利用golang交叉编译功能来加快构建时间。

我尝试过的:我想通过尝试以下语法在这个arm和amd arch的dockerfile中有一个单独的builder阶段:
FROM --platform=$BUILDPLATFORM golang:1.17.6-alpine3.15 as builder。但是在对dockerfile进行更改后使用相同的docker构建命令会产生构建错误,这就是我在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编译器,并且我需要设置CCenv变量来指示go使用哪个c编译器。

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

为了能够在运行中编译C代码,您需要将CC变量设置为arm交叉编译器。您可以通过go env查看您的CC变量。您遇到的错误与您使用的主机系统中的本机编译器有关。你应该在你的案卷中apk add gcc-arm-none-eabi。在您下载了必要的交叉编译工具之后。您需要将您的gcc命令链接到通过我提到的命令下载的编译器。然后,您应该能够为arm64编译您的应用程序。

你也可以分享你的go env输出吗。您可能还需要编辑GOGCCFLAGS变量。

实际上已经有一个干净整洁的解决方案,形式是@tonistiigi/xx的xx。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安装target特定于平台的软件包,而apk安装构建专用于平台的程序包,因为我们使用FROM --platform=$BUILDPLATFORM ...

xx-go方便地封装了go命令,在引擎盖下提供所需的目标特定设置。最后的xx-verify是一个很好的触摸,可以检查生成的二进制文件实际上是针对目标平台的,如果这些不同,则不会意外地针对构建平台。

对于我的@thediveo/lxkns服务映像,在免费的Github操作运行程序上linux/amd64+arm64的构建时间从25分钟下降到了7分钟,所以速度大约是原来的3.5倍。

在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

最新更新