最近有一个用户反馈, 他使用 golang:1.13.1-alpine3.10
这个镜像来编译的可执行程序无法在云函数的环境运行, 报错信息如下:
fork/exec /var/user/main: no such file or directory
在 macOS
下编译则没有这个问题
问题定位
还未来得及定位问题, 用户便反馈说换了一个镜像就没问题了, 于是没能获得更多信息
过了几天, 有一个同事在群里贴出了 Go 程序链接出错的信息, 看起来也是在 Alpine Linux 下编译的, 有人回复道 Alpine Linux 使用的不是 glibc
啊哈, 终于有线索了, 写代码验证一下
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
在 CentOS
上编译后, 使用 ldd
查看一下程序依赖哪些 .so
(也可以使用 readelf -d
)
$ ldd main
not a dynamic executable
程序太简单了, 没有依赖动态库
搜索了一下, 发现 Go 的仓库有一个 issue #33019, 和我们的问题很类似
package main
import (
"net"
"fmt"
"os"
)
func main() {
ips, err := net.LookupIP("localhost")
if err != nil {
fmt.Fprintf(os.Stderr, "Could not get IPs: %v\n", err)
os.Exit(1)
}
for _, ip := range ips {
fmt.Printf("localhost. IN A %s\n", ip.String())
}
}
编译这段代码, 再次使用 ldd
查看一下程序依赖哪些 .so
$ ldd main
linux-vdso.so.1 => (0x00007ffca89c9000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f6c4b4bd000)
libc.so.6 => /lib64/libc.so.6 (0x00007f6c4b0f9000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f6c4aef5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6c4b6d9000)
终于和 glibc
扯上关系了
使用 golang:1.13.1-alpine3.10
这个镜像重新编译一下这段代码, 看看结果有什么不同
docker run -v PWD:/go/src/test -w /go/src/test golang:1.13.1-alpine3.10 go build -o main-alpine
$ ldd main-alpine
linux-vdso.so.1 => (0x00007ffe0055e000)
libc.musl-x86_64.so.1 => not found
libdl.so.2 => /lib64/libdl.so.2 (0x00007f2512754000)
libc.so.6 => /lib64/libc.so.6 (0x00007f2512390000)
/lib/ld-musl-x86_64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x00007f2512958000)
可以看到, 缺失了 libc.musl-x86_64.so.1
, 运行一下程序
$ ./main-alpine
-bash: ./main-alpine: /lib/ld-musl-x86_64.so.1: bad ELF interpreter: No such file or directory
No such file or directory
正是本文一开始提到的出错信息
(完整的出错信息可通过使用 Go 的 os/exec
包启动 main-alpine
获得)
解决方案
问题的原因在于云函数的运行环境(CentOS)提供的是 glibc
, 而 Alpine Linux
却是 musl libc
. 因而使用 golang:1.13.1-alpine3.10
这个镜像编译出来的程序如果依赖于 musl libc
, 则会在程序加载的时候找不到所需的动态库
解决问题的方法很简单, 只需将镜像换成 golang:<version>
的版本(如golang:1.12)即可