10 Feb 2022
调用路径:service --> public-proxy(nginx) --> public world
service通过nginx代理访问外网API A域名,A域名解析的IP有两个(RS1 和 RS2),且都有防火墙白名单限制。A域名的防火墙已经给nginx代理服务器的IP加白。
遇到的问题是,nginx访问A域名时,有报错说connection timeout,进一步查看,发现A域名解析出来的IP并不是RS1 和RS2。
通过排查,发现服务器使用dnsmasq做了DNS缓存。/etc/resolv.conf中配置了dns server为本机的dnsmasq,而dnsmasq通过resolv-file=***指向特定的resolv配置文件,配置使用了四个DNS上游服务器IP地址。使用dig指定DNS服务器地址逐个解析排查,将解析结果和RS1 RS2比对,发现是其中两个DNS服务器返回的解析结果和RS1 RS2不匹配。
找到了问题的直接原因(关于这两台DNS服务器返回的解析结果为什么突然不一致的问题这里先不做讨论),最快的解决方式是修改/etc/hosts来固定域名的解析地址。为了尽快恢复故障,于是采用了这个方案。
但是,写了/etc/hosts后,业务反馈错误量并未下降,并且使用nslookup解析出来的结果并不是/etc/hosts里面本地DNS记录。
基于对nslookup结果的信任,这里判断/etc/hosts中的DNS记录并未生效。
于是产生以下几个疑问
dnsmasq在这个过程中扮演了什么角色,影响了dns解析中的哪些因素?大概看了一下dnsmasq,简短一点介绍,它是一个dns缓存器。
启动的时候加载了/etc/resolv.conf(获取dns upstream server)和/etc/hosts(获取本地dns记录,其实应该叫主机名解析记录,不过也可以用作dns劫持用,而且这里讨论的是dns解析问题,所以这里就以dns记录来表述)。
了解到这里,就意识到,/etc/hosts是否是热加载的呢?会不会是因为不是热加载导致了/etc/hosts的修改并没有在dnsmasq中生效?
于是查看了一下文档,发现确实dnsmasq对/etc/hosts默认不是热加载,如果需要热加载,需要增加启动选项--hostsdir指向一个单独的hosts文件所在的目录。并且dnsmasq的no-hosts选项可以控制dnsmasq是否加载hosts文件。
看到这里就去nginx服务器上确认了一下,发现并没有禁止hosts文件加载,并且也没有指向其他的hosts热加载目录
分析到这里,按照逻辑来讲,可能就是上面说的原因。
但是,还有一个问题。前面的假设是所有的dns解析请求是通过dnsmasq,因为/etc/resolv.conf中指向了本机的dnsmasq,那么按照上面的原因推断的话,这台服务器上所有的程序(不仅仅是nginx)访问A域名都会是同样的解析现象。但是实际情况是,使用ping命令来测试A域名的解析时,就是/etc/hosts中的结果。
那么现在就到了第一个问题了,linux中的进程在进行dns解析时,dnsmasq、/etc/hosts和/etc/resolv.conf在里面到底是按照什么逻辑来生效和互相影响的?
详细的内容见linux中的dns解析是如何工作的。
简短的介绍就是,使用C库中gethostbyname和其相关functions来做DNS解析时,是按照NSS(/etc/nsswitch.conf)中配置的hosts: files dns来确定本地DNS解析记录(/etc/hosts)和DNS服务器(/etc/resolv.conf和dnsmasq)的生效顺序,且只要优先度高的解析方式获取到了记录,剩下的解析方式就不会执行。
这就是为什么
ping可以解析/etc/hosts的DNS记录,但是nslookup无法解析/etc/hosts的DNS记录的原因,也是因为这个原因,导致了上面的误判(通过nslookup来检测域名解析,推断/etc/hosts的修改未生效)。其实不止nslookup,host和dig都是直接向DNS服务器发请求,而不会解析/etc/hosts中的记录。
那么问题就很明了了,nginx服务器上的NSS配置是hosts: files dns myhostname,那么理论上当使用gethostbyname和其他相关functions的程序做DNS解析时候,/etc/hosts是第一顺位的解析方式。
如果以上的推导和求证没问题的话,到这里就出现了矛盾:nginx的DNS解析,理论上/etc/hosts应该生效,但是实际上没有生效
此时就有两个可能:
nslookup类似,无法解析/etc/hosts中的记录(但这个可能性太小,以前做过很多hosts解析,如果这个假设成立,那么我早就发现这个问题了)于是先排查第一个可能,仔细检查了nginx的配置,发现了这样的一个配置
resolver 127.0.0.1 ipv6=off valid=300s;
破案了,之所以修改了/etc/hosts的方案未生效(service通过nginx代理访问A域名依旧有问题),是因为nginx的这个配置跳过了/etc/hosts而直接使用了DNS服务器(dnsmasq)来做dns解析。
三个需要注意的点:
nslookup、dig、hosts无法解析/etc/hosts中的本地DNS记录,可以用ping或者getent hostsdnsmasq的基本配置