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请求) 来获得解析结果;
2) 使用 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
库。