问题背景
某平台的组件,在创建流水线时,一直报错,日志显示请求一个地址连接失败。
Lark20201223221441
在平台流水线创建有关的组件日志里面,发现相同的错误。
image-20201130215353485
排查
从上面日志输出,很明显是程序去请求了10.74.223.115这个地址的443端口,但是连接失败。初始判断是程序本身配置错误引起,于是检查该组件的configmap信息,grep过滤这个IP地址,一无所获。在检查组件所在的k8s集群的service的IP信息,endpoint信息,secret信息等,都没有发现任何信息和这个IP地址关联。
根据日志记录,exec到此组件的pod里面,curlhttps://localhost:443/apis/,也是正常有返回结果。产研反馈创建流水线的代码逻辑,就是请求localhost的443地址。让该组件的研发,在源代码层面查找这个IP信息,也是没有结果,排查一时没有头绪了。
没有思路情况下,因为是个内网地址,k8s集群层面没有地址记录,于是反查这个IP是不是客户环境的。通过在本地,发现了一些端倪。如下图,10.74.223.115,是有域名解析的,解析地址为localhost.cloud.xxx.com的地址,域名也有个localhost,貌似有点关联性。
于是到组件pod检查 /etc/resolv.conf文件和/etc/hosts文件。在容器里面,ping localhost是有正常响应的,在/etc/hosts文件里面,也有localhost的记录。在/etc/resolv.conf里面的search域里面,确实有cloud.xxx.com的地址。同时pod里面ping localhost.cloud.xxx.com,是能ping通的,地址解析IP正是10.74.223.115这个地址。
image-20201223223914101
image-20201223224229951
排查pod里面的search域有cloud.xxx.com地址,排查为集群的coredns开启了foward选项,宿主机的 /etc/resolv.conf是有这个search域的,因此属于正常现象。结合组件日志报错,问题变成为何组件请求localhost地址时,没有请求127.0.0.1,而是请求localhost.cloud.xxx.com地址,/etc/hosts文件配置了localhost解析,组件为何不优先使用此记录,这是linux系统默认行为。
image-20201223224841054
linux系统,/etc/nsswitch.conf 可以控制dns和hosts文件解析顺序。然后观察到有问题的 Pod 用的 alpine 镜像,试试其它镜像后发现只有基于 alpine 的镜像才会有这个问题。
再一搜发现:musl libc 并不会使用 /etc/nsswitch.conf ,也就是说 alpine 镜像并没有实现用这个文件控制域名查找优先顺序,瞥了一眼 musl libc 的 gethostbyname 和 getaddrinfo 的实现,看起来也没有读这个文件来控制查找顺序,写死了先查 hosts,没找到再查 DNS。
这么说,那还是该先查 hosts 再查 DNS 呀,为什么这里抓包看到是先查的 DNS?(如果是先查 hosts 就能命中查询,不会再发起 DNS 请求)
另外这个异常的组件用 Go 写的,会不会是 Go 程序解析域名时压根没调底层 c 库的 gethostbyname 或 getaddrinfo?
搜一下发现果然是这样:go runtime 用 Go 实现了 Glibc 的 getaddrinfo 的行为来解析域名,减少了 c 库调用(应该是考虑到减少 cgo 调用带来的的性能损耗)。issue:net: replicate DNS resolution behaviour of getaddrinfo(glibc) in the go dns resolver所以虽然 alpine 用的 musl libc 不是 Glibc,但 Go 程序解析域名还是一样走的 Glibc 的逻辑,而 alpine 没有 /etc/nsswitch.conf 文件,所以DNS解析请求高于hosts文件。
Lark20201223223222
搜索到翻源码验证下:
Unix 系的 OS 下,除了 openbsd, go runtime 会读取 /etc/nsswitch.conf (net/conf.go):
img
hostLookupOrder 函数决定域名解析顺序的策略,Linux 下,如果没有 nsswitch.conf 文件就 DNS 比 hosts 文件优先(net/conf.go):
img
可以看到 hostLookupDNSFiles 的意思是 dns first(net/dnsclient_unix.go):
img
解决
重新做镜像,在Dockerfile中增加如下命令
参考
https://github.com/golang/go/issues/22846
https://www.jianshu.com/p/c47a08b3f22e
领取专属 10元无门槛券
私享最新 技术干货