在Ubuntu主机上,使用alpine docker容器无法运行Go编译的二进制文件

108

如果一个二进制文件是使用 GOOS=linux 以及 GOARCH=amd64 编译的,在基于 alpine:3.3docker 容器中运行,但如果 docker 引擎主机是 Ubuntu (15.10),该二进制文件将无法运行:

sh: /bin/artisan: not found

同样的二进制文件(针对相同的操作系统和架构编译)如果Docker引擎主机是在Mac OS X上部署的VirtualBox VM中的busybox(这是alpine的基础),则可以正常运行。如果容器基于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 

5
加上 CGO_ENABLED=0 有帮助吗? - Martin Gallagher
神奇的事情发生了。你能否详细解释一下,我会接受的。 - Oleg Sklyar
请尝试使用CGO_ENABLED=1运行go build -tags netgo -a -v std,我认为这可能与net包有关,导致动态链接问题。 - Martin Gallagher
2
默认情况下,CGO可用于net包 - 使用上述标签或CGO_ENABLED = 0会强制使用Go std实现进行查找 - 您可以执行ldd output.bin以查看每个构建变体是否真正静态编译,或者是否存在任何动态链接。 - Martin Gallagher
太棒了,非常感谢你的帮助和解释! - Oleg Sklyar
显示剩余5条评论
7个回答

120

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

我遇到过两种解决方案:

  • 通过CGO_ENABLED = 0禁用CGO
  • 通过在某些平台上使用go build -tags netgo -a -v强制使用Go实现的网络依赖项netgo

https://golang.org/doc/go1.2得知:

默认情况下,net包要求cgo,因为在一般情况下,主机操作系统必须调停网络调用设置。但在某些系统上,可以不使用cgo使用网络,并且有用的做法是避免动态链接。新的构建标记netgo(默认关闭)允许在这些可能性的系统上纯粹使用go构建net包。

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


6
CGO_ENABLED=0 也解决了我的问题:在非Alpine Docker上尝试运行基于Alpine构建的Go程序时遇到问题。在我的情况下,错误信息只是说 docker: Error response from daemon: Container command not found or does not exist. - Vlad A. Ionescu
对于使用Bazel的任何人来说,可以通过--features=static --features=pure标志来实现上述操作。 - Ben Elgar
感谢您的回答,我花了很多时间在谷歌上搜索才找到。 - Kyle Gibbons
我认为另一个可选的解决方案是将源代码复制到镜像中,并在镜像中执行go build。 - g10guang
CGO_ENABLED=0 应该可以解决问题。另外,这个条件也很重要:GOARCH=amd64。 - Naren Yellavula
1
如果这个答案能够包含两种解决方案可能引起的问题,那就太好了。它们中有哪一个基本上是安全的?或者在程序试图执行一些相关代码时,它们会导致其他运行时问题吗?我正在将某人的项目构建为我的代码组件,因此我没有太多了解可能依赖 CGO 的相关代码。 - Stephen Smith

106

我使用 Go 二进制文件时遇到了相同的问题,将以下内容添加到我的 Docker 文件后问题解决:

RUN apk add --no-cache libc6-compat 

1
帮我在我的Alpine镜像中运行CGO。 - xsor
2
节省了很多时间。+1 - Manwal
3
抱歉,这对我来说不起作用,会出现“错误重新定位...:fprintf chk:找不到符号”的提示。 - TheDiveO
1
经过三个小时的奋斗,终于成功了!!!谢谢。 - Ashwani
1
仅供记录 - 如果您在Dockerfile中使用多阶段构建,则此命令必须在二进制文件运行的阶段中使用,而不是构建阶段。否则,它将无法找到所需的动态库。 - Beolap
显示剩余4条评论

11

在构建机器上的 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"]

我正在撰写一篇关于这个问题的文章。您可以在此处找到包含此解决方案的草稿 https://kefblog.com/Post/2017-07-04_golang-in-docker-without-disabling-cgo-on-alpine


“RUN cd /app” 是多余的,因为 “WORKDIR” 指令会自动将当前目录设置为工作目录。 - Piotr Ostrowski

11

对我有效的是在链接器选项中启用静态链接:

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

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

有关详细信息,请参见 go tool link静态编译的Go程序,即使使用cgo也始终如一,使用musl


1
那个可以工作,但是值得指出的是它也会抛出一个警告。=> 警告:在静态链接应用程序中使用“getaddrinfo”需要在运行时使用与链接所使用的glibc版本相对应的共享库 我想在alpine基础镜像的情况下这没问题。 - The Fool

3

我有一个应用程序需要使用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

这让我之后能够运行go应用程序。

提示:使用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)从Alpine容器构建的,命令如下:

GOOS=linux GOARCH=amd64 go build

通过在构建二进制文件时使用以下环境变量,解决了该问题:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

0
这个问题的原因是alpine镜像使用musl libc,而由cgo编译的二进制程序依赖于gnu libc,因此会出现兼容性问题。您可以使用ldd命令查看二进制文件所依赖的动态库。
root# ldd main 
 !10807         
  linux-vdso.so.1 (0x00007ffea7fab000)
  libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f7b1f818000)
  libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7b1f5f9000)
  libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7b1f208000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f7b1fa32000)

有方法可以解决这个问题,去构建集合并将CGO_ENABLED设置为0。

env CGO_ENABLED=0 go build -o main main.go

此外,如果您坚持使用cgo编译程序,您可以使用frolvlad/alpine-glibc作为基础镜像来解决这个问题。
FROM frolvlad/alpine-glibc
WORKDIR /hello
COPY . .
CMD ["./main"]

希望它能帮到你。


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