Go 语言镜像精简
为了精简镜像,一般会加载两个镜像,第一层是编译环境,来根据源码编译出可执行的二进制文件。然后拷贝可执行文件到 scratch 镜像中,作为最终的镜像:
1 | FROM xxx:lastest AS builder |
二进制文件的动态链接和静态链接导致的问题
Golang 语言程序编译时会将所有必须的依赖编译到二进制文件中,但也不完全使用的是静态链接,因为 Golang 的某些包是依赖系统标准库的,例如使用到 DNS 解析的包。只要代码中导入了这些包,编译的二进制文件就需要调用到某些系统库,Golang 中使用 CGO,以允许 Golang 调用 C 代码,这样编译好的二进制文件就可以调用系统库。
也就是说,如果 Go 程序使用了 net 包,就会生成一个动态的二进制文件,没有那些动态链接库,镜像就不能够正常工作。
会出现以下的错误:
1 | exec user rocess caused "no such file or directory" |
解决
如果想让镜像能够正常工作,有几种方法:
禁用
CGO:过设置环境变量CGO_ENABLED=0来禁用CGO,此时程序会使用内置的实现来替代系统库(例如使用内置的DNS解析器)。这种情况下生成的二进制文件是不依赖外部动态链接库的,可以通过ldd命令查看。静态链接:通过添加编译选项
-ldflags "-extldflags '-static'"实现编译时,把需要的链接库都打包,这样生成的二进制文件就是静态链接的,可以直接在scratch中执行。必须将需要的库文件复制到镜像中;
直接使用
busybox:glibc镜像;
Golang 域名解析
Golang 的 DNS 解析对协程支持很好, 即 DNS 解析时不会阻塞执行线程,只会阻塞当前协程。
根据官方文档中关于域名解析的描述:
域名解析会间接调用 Dial 函数或者直接使用 LokupHost 和 LookupAddr 函数。不同操作系统有不同的实现方式。在 Unix 类系统中有两种方法进行域名解析:
1) 纯 Golang 语言实现的域名解析:从 /etc/resolv.conf 中取出本地 dns server 列表,然后发送 UDP 报文(DNS请求) 来获得解析结果;
- 使用
CGO实现,调用到C标准库的getaddrinfo或getnameinfo函数(不建议使用,因为对协程不友好)
关于 cgo dns 解析的坑 参照以下链接:
https://jira.mongodb.org/browse/MGO-41
https://github.com/golang/go/issues/8602#issuecomment-66098142
CGO 编译导致的问题
默认会使用纯 Golang 实现的域名解析。但是会有一种情况,程序必须打开 CGO 编译且使用静态链接 (通过 -ldflags "-extldflags '-static'" 来实现静态链接)。这时候就会出现程序静态链接了 CGO 的 DNS 解析库,打包进 scratch 之类的镜像之后,就会出现解析域名服务失败的问题。
解决
编译的时候,指定标签 -tags='netgo',来使用指定使用内置纯 Golang 实现的 netgo 库。

