一个由于 Nginx 静态 DNS 解析缓存导致的奇怪问题

该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2023/12/29/problem-led-by-nginx-static-dns-cache/

公司网站的一个接口近期突然出现故障。若仅是一般性的系统内部异常,一篇文章或许并不必要。然而,问题的关键在于该接口返回的故障明显不是我们应用程序返回的,而是另一个未知系统的返回。更为奇异的是,这个接口的返回内容并非固定的,而是在两种错误返回中随机切换。

下午,运维同事反馈,有一个接口自 18 号以来一直故障。由于它不是业务接口,对终端用户没有任何影响,大家也没有过多关注,直到今天才开始处理。我通过浏览器直接调用这个接口,发现返回的错误一直在两种结果里反复横跳,一会儿是 “404 No such service”、一会是 “404 Page not found”。这两种错误都不是 Nginx 的错误响应,更不是我们的应用系统返回的。

这就奇怪了,我们的网站也没有挂前置 CDN 啊,只有一个 ELB 和 WAF,我查了下 WAF 也没有拦截过这个接口,怎么会出现我们系统之外的响应呢? 这时我查了下 nginx 的配置,发现这个接口在 nginx 的配置里是一个转发到另一个服务的反代,配置大概如下:

1location /api/a {
2    proxy_pass https://service-b.example.com/api/b;
3}

起初我怀疑是这台机器上 DNS 的问题,于是我尝试在主机和 Nginx 容器里分别用 curl https://service-b.example.com/api/b 调用,结果响应的内容都是正常的,没有出现 404 错误。但如果执行 curl http://127.0.0.1/api/a,就是错误的响应。更奇怪了,为什么只有 nginx 里的这个接口会出现问题呢?

这时我同事已经从 ELB 拆下了一台工作机器,并在上面执行了 nginx -s reload,我通过 curl http://127.0.0.1/api/a 是正常的。 而我在另一台仍然在 ELB 上挂着的工作机器上执行 curl http://127.0.0.1/api/a 则,仍然是错误的响应。

百思不得其解,无奈之下先让运维同事逐台机器重启 Nginx 暂时缓解

也正在这时,我突然想起来,Nginx 对 proxy_pass 里用到的 upstream 节点的 hostname 解析结果是静态的!!!Nginx 并不会动态解析 upstreamhostname 的 IP 地址。而 service-b.example.com 恰好也是挂在 ELB 后面的——每个 AWS ELB 有两个公网 IP 地址,这两个 IP 不是固定的,会定期更换。

也就是说,上面配置的 Nginx 配置,只会在启动和 reload 的时候解析一次 service-b.example.com 的 IP 地址,然后就会一直使用这个 IP 地址,直到下次 reload。再加上 ELB 动态更换 IP 地址,随着时间的变化,Nginx 里的 upstream 节点的 IP 地址终将不在和 service-b ELB 的 IP 地址保持一致,从而导致请求全都发到了此前的 ELB IP 地址上,这时候此前的 ELB IP 早已经被 AWS 回收用于其他服务了,其中 404 no such service 就是 AWS Cloudfront 的一种异常响应。

问题找到了,解决方案,倒是有两个:

  • 由于主站和 service-b 其实在同一个 VPC,那么大可以直接使用内网转发,此处使用了公网转发确实不妥,不仅效率低,还引发了这个问题。

  • 改用 Nginx 的变量 + resolver 指令,详见 StackExchange 上的一篇回答

comments powered by Disqus