Go 编译的二进制文件不会在 Ubuntu 主机上的 alpine docker 容器中运行

Posted

技术标签:

【中文标题】Go 编译的二进制文件不会在 Ubuntu 主机上的 alpine docker 容器中运行【英文标题】:Go-compiled binary won't run in an alpine docker container on Ubuntu host 【发布时间】:2016-07-16 17:03:18 【问题描述】:

给定一个二进制文件,使用 Go 编译,使用 GOOS=linuxGOARCH=amd64,部署到基于 alpine:3.3docker 容器,如果 docker 引擎主机是 Ubuntu (15.10),则二进制文件将不会运行:

sh: /bin/artisan: not found

如果 docker 引擎主机是 busyboxalpine 的基础)部署在 VirtualBox 虚拟机中,那么这个相同的二进制文件(为相同的操作系统和架构编译)将运行良好在 Mac OS X 上。

如果容器基于 Ubuntu 映像之一,同样的二进制文件也可以正常运行。

知道这个二进制文件缺少什么吗?

这是我为重现所做的(未显示在 OS X 上的 VirtualBox/busybox 中成功运行):

构建(使用标志显式构建,即使拱门匹配):

➜  artisan git:(master) ✗ GOOS=linux GOARCH=amd64 go build

检查它是否可以在主机上运行:

➜  artisan git:(master) ✗ ./artisan 
10:14:04.925 [ERROR] artisan: need a command, one of server, provision or build 

复制到 docker 目录,构建,运行:

➜  artisan git:(master) ✗ cp artisan docker/build/bin/        
➜  artisan git:(master) ✗ cd docker 
➜  docker git:(master) ✗ cat Dockerfile 
FROM docker:1.10
COPY build/ /
➜  docker git:(master) ✗ docker build -t artisan .
Sending build context to Docker daemon 10.15 MB
Step 1 : FROM docker:1.10
...
➜  docker git:(master) ✗ docker run -it artisan sh
/ # /bin/artisan 
sh: /bin/artisan: not found

现在将图像库更改为phusion/baseimage

➜  docker git:(master) ✗ cat Dockerfile 
#FROM docker:1.10
FROM phusion/baseimage
COPY build/ /
➜  docker git:(master) ✗ docker build -t artisan .
Sending build context to Docker daemon 10.15 MB
Step 1 : FROM phusion/baseimage
...
➜  docker git:(master) ✗ docker run -it artisan sh
# /bin/artisan
08:16:39.424 [ERROR] artisan: need a command, one of server, provision or build 

【问题讨论】:

添加 CGO_ENABLED=0 有帮助吗? 魔法,确实如此。您能否详细说明答案,我会接受。 你能试试go build -tags netgo -a -v std CGO_ENABLED=1 吗?我认为这可能是 net 包的问题,​​导致动态链接。 正如您所建议的,这对CGO_ENABLED=1 go build -tags netgo -a -v 很有帮助。如果我理解正确,与 CGO_ENABLED=0 相比,这也将保留 net 包中的 TLS 功能,否则会丢失,对吗?我能否以某种方式查看静态链接到二进制文件中的内容以及动态链接的内容? 默认情况下 CGO 可用于 net 包 - 使用上述标签或 CGO_ENABLED=0 强制使用 Go std 实现进行查找 - 你可以做的是:ldd output.bin 在每个构建变体以查看它们是否真正静态编译,或者是否正在进行任何动态链接。 【参考方案1】:

我在使用 go 二进制文件时遇到了同样的问题,在将它添加到我的 docker 文件后我让它工作了:

RUN apk add --no-cache libc6-compat 

【讨论】:

帮我在我的 Alpine 镜像中运行 CGO 节省了很多时间。 +1 不幸的是,这对我不起作用,并给error relocating ...: fprintf chk: symbol not found 您需要在相同的环境中构建和运行。例如,如果您在本地机器上构建二进制文件并在 docker 中运行它,可能会导致问题 我正在一个 Alpine docker 容器中运行别人的 go 二进制文件,我不想自己重新编译它。我宁愿使用公开可用的二进制文件。这个解决方案解决了这个问题,让我在 Alpine docker 容器中运行 go 二进制文件【参考方案2】:

我的应用需要CGO_ENABLED=1

我在 debian-slim 容器中运行编译后的 go 二进制文件的解决方法是使用 RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o goapp 构建二进制文件

并在 debian slim 中运行以下命令

RUN apt-get update && apt-get install -y musl-dev
RUN ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1

让我之后能够运行 goapp

提示:ldd goapp 表明容器中缺少 libc.musl-x86_64。

【讨论】:

【参考方案3】:

在 debian docker 容器中执行 go 二进制文件时,遇到了以下问题: /bin/bash: line 10: /my/go/binary: No such file or directory

二进制文件是使用 docker-in-docker (dind) 从高山容器中使用命令构建的: GOOS=linux GOARCH=amd64 go build

在构建二进制文件时使用以下环境修复它: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

【讨论】:

【参考方案4】:

对我来说,诀窍是在链接器选项中启用静态链接:

$ go build -ldflags '-linkmode external -w -extldflags "-static"'

-linkmode 选项告诉 Go 使用外部链接器,-extldflags 选项设置传递给链接器的选项,-w 标志禁用 DWARF 调试信息以提高二进制大小。

请参阅go tool link 和Statically compiled Go programs, always, even with cgo, using musl 了解更多详情

【讨论】:

这可行,但值得指出的是它也会发出警告。 => warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking 我想如果是 alpine 基础图像,它还可以。【参考方案5】:

您的构建机器中的 Go 编译器可能会将您的二进制文件与位于不同于 Alpine 的位置的库链接。在我的情况下,它是使用 /lib64 下的依赖项编译的,但 Alpine 不使用该文件夹。

FROM alpine:edge AS build
RUN apk update
RUN apk upgrade
RUN apk add --update go=1.8.3-r0 gcc=6.3.0-r4 g++=6.3.0-r4
WORKDIR /app
ENV GOPATH /app
ADD src /app/src
RUN go get server # server is name of our application
RUN CGO_ENABLED=1 GOOS=linux go install -a server

FROM alpine:edge
WORKDIR /app
RUN cd /app
COPY --from=build /app/bin/server /app/bin/server
CMD ["bin/server"]

我正在撰写有关此问题的文章。你可以在这里http://kefblog.com/2017-07-04/Golang-ang-docker找到这个解决方案的草稿。

【讨论】:

【参考方案6】:

默认情况下,如果使用net 包,构建可能会生成带有一些动态链接的二进制文件,例如到 libc。您可以通过查看ldd output.bin 的结果来检查动态链接和静态链接

我遇到过两种解决方案:

禁用 CGO,通过CGO_ENABLED=0 强制使用net依赖的Go实现,netgo via go build -tags netgo -a -v,这是针对某些平台实现的

来自https://golang.org/doc/go1.2:

net 包默认需要 cgo,因为主机操作系统通常必须调解网络调用设置。但是,在某些系统上,可以在没有 cgo 的情况下使用网络,这样做很有用,例如避免动态链接。新的构建标签 netgo(默认关闭)允许在可能的情况下在纯 Go 中构建 net 包。

以上假设唯一的 CGO 依赖是标准库的 net 包。

【讨论】:

CGO_ENABLED=0 也解决了我的问题:尝试在非高山 docker 上运行高山构建的 go 程序。我的错误只是说docker: Error response from daemon: Container command not found or does not exist.. 对于任何使用 Bazel 的人,以上都可以通过 --features=static --features=pure 标志来完成。 感谢您的回答,花了很多谷歌搜索。 我认为另一种可选的解决方案是将源代码复制到图像中并 exec go build in image。 CGO_ENABLED=0 应该可以解决问题。这个条件也很重要,GOARCH=amd64

以上是关于Go 编译的二进制文件不会在 Ubuntu 主机上的 alpine docker 容器中运行的主要内容,如果未能解决你的问题,请参考以下文章

Go 编译的二进制文件会在 Windows NT 上运行,包括使用以太网端口吗?

Go1.18 新特性:编译后的二进制文件,将包含更多信息

golang 在mac 下go install成二进制文件,上传到centos 无法执行

以太坊上发行ERC20代币

Linux系统移植 ------搭建交叉编译环境

在 Ubuntu 开启 GO 程序编译之旅