Golang中执行go run main正常但go build -o main.go指定GOOS和GOARCH失败的问题

Golang中执行go run main正常但go build -o main.go指定GOOS和GOARCH失败的问题 大家好。

当我在我的MBP M1上执行 go run main.go 时,它可以正常运行。 当我执行 go build . 时,它可以成功构建。 但是,当我尝试使用下面的导出变量来构建,或者执行Makefile时,就会失败 - 错误信息如下。

<备注:我已在AWS上基于Amazon Linux的EC2实例(x86-64)上尝试了相同的命令/步骤,因为我打算部署到Lambda>

BINARY_NAME=main
export GOOS=linux
export GOARCH=amd64
export CGO_ENABLED=0

.DEFAULT_GOAL := deploy

deploy:
	
	go build -o ${BINARY_NAME} .
	zip -r function.zip main
	aws lambda update-function-code --function-name "S3JSONDecomposer-Golang" --zip-file fileb://function.zip --region="af-south-1" | jq .    

run:
	go run ${BINARY_NAME}.go
./main.go:130:14: undefined: kafka.ConfigMap
./main.go:142:25: undefined: kafka.NewProducer

请帮忙。

G


更多关于Golang中执行go run main正常但go build -o main.go指定GOOS和GOARCH失败的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

21 回复

谢谢,我会去看看是否能做… 会反馈

G

更多关于Golang中执行go run main正常但go build -o main.go指定GOOS和GOARCH失败的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


func main() {
    fmt.Println("hello world")
}

基础容器不包含这些库

你是否可以在某个构建步骤中复制这些库呢?

(我想到的是多阶段 Dockerfile。)

考虑执行Go和C的静态链接。这篇文章详细解释了执行此操作的过程和注意事项。Matt Turner - 2022年静态链接Go 祝你好运

使用 Lambda 时,您只需提供代码,无法访问任何外部操作系统级别的变量。

Lambda 基于/部署在 Amazon Linux 操作系统构建上。 感谢提供的 StackOverflow 线索,我会去看看。

G

太棒了,正如所说,感谢指点。

安装了 gcc 设置了 CGO=1 重新运行了 Makefile,它编译成功了… 谢谢,现在快速测试一下应用。

G

georgelza:

这是在 CGO=0 的情况下……因为我认为它与我的目标架构相同。

每当 Go 包含 C 代码时,都必须设置 CGO 环境变量。这与目标架构无关。

这个回答在我看来是你在分享的那个Stack Overflow讨论串中一个不错的解决方案。

简单来说:在一个模拟实际Lambda运行环境的容器内构建二进制文件,这样二进制文件就能链接到正确的库。

可能需要对这部分进行修改,你可以设置环境变量…… 但为了符合这个理念,你可能不希望安装操作系统级别的软件包,因为其核心思想是:你提供代码,代码会被动态推送到一个Docker容器中并执行。

仍然卡住了。

G

我曾尝试(使用Python)通过Docker进行编译,但遗憾的是总是遇到问题。对于Python,我最终构建了这些EC2镜像,它们具有目标操作系统/架构……

如前所述,我也尝试在EC2实例上进行编译,结果遇到了相同的错误。我会很快再试一次,谁知道呢 😉

我会告知进展,感谢你的见解。

G

通过在基于 Amazon Linux 的 EC2 上构建我的二进制文件,我实现了完全相同的结果。

我也尝试过在容器内构建,但目前遇到了同样的问题。基础容器不包含这些库,因为看起来即使我遵循静态链接方法,仍有一些组件是动态链接的,而这些组件在 Lambda 初始化函数时使用的目标容器中并不存在。

我目前正在与一个问题作斗争,对 Lambda 或 Go 都缺乏足够的知识/经验。

:smile:

G

你好 @georgelza

我猜你使用的是 confluent-kaftka-go。这个库基于 C 库 librdkafka,因此编译时需要 CGO。

只有纯 Go 代码才能轻松进行交叉编译。我可能会在 Mac 上使用虚拟机(Docker Desktop、Podman Desktop、OrbStack、Colima 等)来搭建一个 Linux 构建环境。

……所以如果我设置了 CGO_ENABLED=0

然后执行

go build -ldflags "-linkmode 'external'"
# jsondecom
./main.go:139:14: undefined: kafka.ConfigMap
./main.go:151:25: undefined: kafka.NewProducer
./main.go:241:23: undefined: kafka.Message
./main.go:242:28: undefined: kafka.TopicPartition
./main.go:242:80: undefined: kafka.PartitionAny

引用 georgelza:

本以为这会是一个完整/自包含的二进制文件…

只有纯 Go 代码才能做到。任何包含的 C 代码都会有 C 依赖。

我对 LambdaOS 一无所知,但我找到了这个 Stackoverflow 帖子,它表明你可能需要设置 LD_LIBRARY_PATH,并且很可能还需要以某种方式提供所需的 glibc。

找到

stackoverflow.com

加载 libssl.so.1.0.0 时提示没有这样的文件或目录

标签: amazon-web-services, go, apache-kafka, aws-lambda

提问者: maathor 提问时间: 10:01AM - 24 Aug 18 UTC

G

将我的代码复制到基于Amazon Linux… x86-64…的EC2实例上后,我又回到了这个问题。

这是在设置 CGO=0 的情况下… 因为我假设它与我的目标架构相同,并且是x86-64,所以我猜它与源架构相同。 如果我设置 CGO=1,那么它会抱怨缺少C编译器。必须看看要安装什么C编译器,以前从未这样做过。

make deploy
go build -o main .
# jsondecom
./main.go:130:14: undefined: kafka.ConfigMap
./main.go:142:25: undefined: kafka.NewProducer
make: *** [Makefile:11: deploy] Error 1

有点卡住了,看着说明,我已经根据说明构建了一个 Dockerfile… 当然,当我运行并调用 docker 容器时,我遇到了已知的问题。 所以回到那个链接,它提到使用那个 .configure 来编译那些库文件… 那么,这个 .configure 是从哪里来的,它将要编译的源文件又在哪里? 然后它把它们复制到 {$PWD}/lib/,但这是哪个目录,是在容器内部。 有时候真希望写这些“如何解决问题”的人能退一步,考虑到执行这些操作的人可能不具备和他们相同的基础知识。 当然,一旦我让这个本地运行/调用工作起来,还得看看如何使用 AWS ECR 进行部署… 在某个地方看到过关于它的说明。 唉,看起来 Go 语言对 Lambda 不太友好。 G

我理解你的想法,但不太确定……我知道你给它一个包含主程序的 zip 文件,但不确定这个 zip 文件是否可以包含依赖项,以及它们是否可以被复制。

对我来说……这也没有意义……可执行文件应该是完整自包含的,否则如果你把它从一台主机复制到另一台主机……你最终会陷入部署的噩梦,而这正是单个 Go 二进制文件试图解决的问题。

我在某个视频/笔记中看到,你可以构建用于部署应用程序的 Docker 镜像,我在想这是否可行。那样的话,Lambda 就不是将我的二进制文件推送到镜像中并调用它,而是部署我提供的镜像(其中包含了所有必需的内容)。但转念一想,如果这样做,我为什么不直接在我们自己的 K8S / EKS 集群上部署呢?对我来说,到了那个阶段,Lambda 部署的优势就被削弱/减少了。

G

设置 CGO_ENABLED=1(在 MAC 上,让我在我的 EC2 实例上尝试一下)

go build -o main .
# runtime/cgo
linux_syscall.c:67:13: error: call to undeclared function 'setresgid'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
linux_syscall.c:67:13: note: did you mean 'setregid'?
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/unistd.h:593:6: note: 'setregid' declared here
linux_syscall.c:73:13: error: call to undeclared function 'setresuid'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
linux_syscall.c:73:13: note: did you mean 'setreuid'?
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/unistd.h:595:6: note: 'setreuid' declared here
make: *** [deploy] Error 1

唉……

看起来,为了在我的EC2镜像上编译程序而安装的必需glib库,在Lambda的操作系统上默认并不存在。这有点奇怪,可能是我知识不足,但我原本以为这会是一个完整/自包含的二进制文件……

/var/task/main: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /var/task/main)
/var/task/main: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by /var/task/main)
/var/task/main: /lib64/libc.so.6: version `GLIBC_2.32' not found (required by /var/task/main)
/var/task/main: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by /var/task/main)
2023/07/02 07:47:37 exit status 1
/var/task/main: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /var/task/main)
/var/task/main: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by /var/task/main)
/var/task/main: /lib64/libc.so.6: version `GLIBC_2.32' not found (required by /var/task/main)
/var/task/main: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by /var/task/main)
2023/07/02 07:47:37 exit status 1

这是一个典型的交叉编译问题,特别是当涉及到C依赖时。go run main.gogo build . 在本地工作是因为它们使用默认的本地环境(GOOS=darwin, GOARCH=arm64),并且能够找到本地的C库。当你设置 GOOS=linuxGOARCH=amd64 进行交叉编译时,CGO被禁用(CGO_ENABLED=0),但你的代码似乎依赖了C绑定。

从错误信息看,你使用了 confluent-kafka-go 库,这个库在底层依赖了 librdkafka 的C库。当 CGO_ENABLED=0 时,纯Go模式无法使用这些C绑定,导致 kafka.ConfigMapkafka.NewProducer 等符号未定义。

解决方案是使用支持交叉编译的纯Go Kafka客户端,或者为交叉编译正确配置CGO。对于Lambda部署,推荐以下两种方法:

方法1:使用Docker进行构建(推荐)

创建Dockerfile,在Linux环境中构建:

FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# 在Linux容器内构建
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main .

# 使用scratch或alpine作为最终镜像
FROM alpine:latest
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]

方法2:使用纯Go的Kafka客户端

替换 confluent-kafka-go 为纯Go实现的库,如 segmentio/kafka-go

// 替换前
import "github.com/confluentinc/confluent-kafka-go/kafka"

// 替换后
import "github.com/segmentio/kafka-go"

// 使用示例
func produceToKafka() {
    writer := &kafka.Writer{
        Addr:     kafka.TCP("localhost:9092"),
        Topic:    "your-topic",
        Balancer: &kafka.LeastBytes{},
    }
    
    err := writer.WriteMessages(context.Background(),
        kafka.Message{
            Key:   []byte("key"),
            Value: []byte("value"),
        },
    )
}

方法3:修改Makefile,在本地使用CGO交叉编译

需要安装交叉编译工具链:

BINARY_NAME=main
export GOOS=linux
export GOARCH=amd64

# 对于confluent-kafka-go,需要CGO
export CGO_ENABLED=1

# 设置交叉编译工具链(macOS上)
export CC=x86_64-linux-gnu-gcc
export CXX=x86_64-linux-gnu-g++

deploy:
    # 需要先安装交叉编译工具:brew install FiloSottile/musl-cross/musl-cross
    CC=x86_64-linux-musl-gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "-linkmode external -extldflags -static" -o ${BINARY_NAME} .
    zip -r function.zip main
    aws lambda update-function-code --function-name "S3JSONDecomposer-Golang" --zip-file fileb://function.zip --region="af-south-1" | jq .

方法4:使用Lambda容器镜像

如果必须使用 confluent-kafka-go,考虑使用Lambda容器镜像,在构建时安装依赖:

FROM public.ecr.aws/lambda/provided:al2023 AS builder

# 安装构建依赖
RUN yum install -y gcc gcc-c++ make cmake

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# 构建
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o main .

# 最终镜像
FROM public.ecr.aws/lambda/provided:al2023
COPY --from=builder /app/main ${LAMBDA_TASK_ROOT}
CMD ["main"]

对于AWS Lambda部署,方法1(Docker构建)或方法2(使用纯Go库)是最简单可靠的。如果Kafka功能不是核心需求,强烈建议使用方法2切换到纯Go的Kafka客户端。

回到顶部