英文:
Go-compiled binary won't run in an alpine docker container on Ubuntu host
问题
给定一个使用GOOS=linux
和GOARCH=amd64
编译的Go二进制文件,部署到基于alpine:3.3
的docker
容器中,如果docker引擎主机是Ubuntu(15.10),该二进制文件将无法运行:
sh: /bin/artisan: not found
如果docker引擎主机是在Mac OS X上部署的基于busybox
(即alpine
的基础)的VirtualBox虚拟机中,相同的二进制文件(针对相同的操作系统和架构编译)将可以正常运行。
如果容器基于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
英文:
Given a binary, compiled with Go using GOOS=linux
and GOARCH=amd64
, deployed to a docker
container based on alpine:3.3
, the binary will not run if the docker engine host is Ubuntu (15.10):
sh: /bin/artisan: not found
This same binary (compiled for the same OS and arch) will run just fine if the docker engine host is busybox
(which is the base for alpine
) deployed within a VirtualBox VM on Mac OS X.
This same binary will also run perfectly fine if the container is based on one of Ubuntu images.
Any idea what this binary is missing?
This is what I've done to reproduce (successful run in VirtualBox/busybox on OS X not shown):
Build (building explicitly with flags even though the arch matches):
➜ artisan git:(master) ✗ GOOS=linux GOARCH=amd64 go build
Check it can run on the host:
➜ artisan git:(master) ✗ ./artisan
10:14:04.925 [ERROR] artisan: need a command, one of server, provision or build
Copy to docker dir, build, run:
➜ 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
Now changing the image base to 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
答案1
得分: 119
默认情况下,如果使用net
包进行构建,生成的二进制文件可能会包含一些动态链接,例如与libc的链接。您可以通过查看ldd output.bin
的结果来检查动态链接和静态链接。
我找到了两个解决方案:
- 通过
CGO_ENABLED=0
禁用CGO。 - 强制使用Go实现的net依赖项,通过
go build -tags netgo -a -v
,这适用于某些平台。
来自https://golang.org/doc/go1.2:
默认情况下,net包需要使用cgo,因为主机操作系统通常需要介入网络调用设置。但在某些系统上,可以在没有cgo的情况下使用网络,并且这样做是有用的,例如避免动态链接。新的构建标签netgo(默认情况下关闭)允许在可能的系统上使用纯Go构建net包。
上述假设的是唯一的CGO依赖是标准库的net
包。
英文:
By default, if using the net
package a build will likely produce a binary with some dynamic linking, e.g. to libc. You can inspect dynamically vs. statically link by viewing the result of ldd output.bin
There are two solutions I've come across:
- Disable CGO, via
CGO_ENABLED=0
- Force the use of the Go implementation of net dependencies, netgo via
go build -tags netgo -a -v
, this is implemented for a certain platforms
From https://golang.org/doc/go1.2:
> The net package requires cgo by default because the host operating system must in general mediate network call setup. On some systems, though, it is possible to use the network without cgo, and useful to do so, for instance to avoid dynamic linking. The new build tag netgo (off by default) allows the construction of a net package in pure Go on those systems where it is possible.
The above assumes that the only CGO dependency is the standard library's net
package.
答案2
得分: 105
我在使用一个Go二进制文件时遇到了同样的问题,后来在我的Docker文件中添加了以下内容后问题得到解决:
RUN apk add --no-cache libc6-compat
英文:
I had the same issue with a go binary, and I got it to work after adding this to my docker file:
RUN apk add --no-cache libc6-compat
答案3
得分: 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是我们应用程序的名称
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。
英文:
Go compiler from your build machine probably links your binary with libraries on different location than in Alpine. In my case it was compiled with dependencies under /lib64 but Alpine does not use that folder.
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"]
I'm working on article about this issue. You can find draft with this solution here https://kefblog.com/Post/2017-07-04_golang-in-docker-without-disabling-cgo-on-alpine .
答案4
得分: 11
对我起作用的是在链接器选项中启用静态链接:
$ 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。
英文:
What did the trick for me was enabling static linking in the linker options:
$ go build -ldflags '-linkmode external -w -extldflags "-static"'
The -linkmode
option tells Go to use the external linker, the -extldflags
option sets options to pass to the linker and the -w
flag disables DWARF debug info to improve binary size.
See go tool link
and Statically compiled Go programs, always, even with cgo, using musl
for more details
答案5
得分: 3
在Debian的Docker容器中执行Go二进制文件时,遇到了以下问题:
/bin/bash: line 10: /my/go/binary: No such file or directory
该二进制文件是在Alpine容器中使用docker-in-docker(dind)命令构建的:
GOOS=linux GOARCH=amd64 go build
通过在构建二进制文件时使用以下环境变量进行修复:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
英文:
While executing a go binary inside a debian docker container, faced this issue:
/bin/bash: line 10: /my/go/binary: No such file or directory
The binary was built by using docker-in-docker (dind) from an alpine container using command:
GOOS=linux GOARCH=amd64 go build
Fixed it by using following env while building the binary:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
答案6
得分: 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
这样我就能够运行 goapp 了。
提示:ldd goapp
显示容器中缺少 libc.musl-x86_64。
英文:
I had an app that required CGO_ENABLED=1
.
The fix for me to run the compiled go binary in a debian-slim container was to build the binary using RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o goapp
And run the following commands in the 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
Made me able to run the goapp afterwards
TIP: ldd goapp
showed that libc.musl-x86_64 was missing in the container.
答案7
得分: 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)
有几种方法可以解决这个问题,go build命令可以将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"]
希望能对你有所帮助。
英文:
The reason for this problem is that the alpine image uses musl libc, and the binary program compiled by cgo relies on gnu libc, so there will be compatibility problems. You can use the ldd command to view the dynamic libraries that a binary depends on.
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)
There are ways around this, go build sets CGO_ENABLED to 0.
env CGO_ENABLED=0 go build -o main main.go
In addition, if you insist on using cgo to compile the program, you can use frolvlad/alpine-glibc as the base image to solve this problem.
FROM frolvlad/alpine-glibc
WORKDIR /hello
COPY . .
CMD ["./main"]
hope it can help you.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论