性能文章>一次 APISIX 网关 503 的问题排查(DNS篇)>

一次 APISIX 网关 503 的问题排查(DNS篇)原创

11586512

最近我们内网的 k8s 集群做了一次升级,发现经过 APISIX 网关服务都 503 异常了,于是做了一次分析。我们在内网和线上都采用了 APISIX 来做流量网关,对 APISIX 也贡献了 6 个 PR,所以对它的源码还算比较了解。下面排查过程比较曲折,情感上多次起伏,各位看官耐心看完。

现象

经由 APISIX 接管的所有接口请求都 503 了,如下所示。

网络的拓扑结构也非常的简单,就是 APISIX 将流量转发到后端的 java 服务。

APISIX 错误日志如下。

发现是因为域名解析失败,但是非常奇怪的是,在容器内我们通过 curl 请求直接是可以请求成功的

curl "http://school-performance-http.easicare-test-2:8080/school-performance
/student-archive/schools/30375dee54dc47ef8410b6508cd7aa6a
/archive-bags/0054616e455f4ccc91f64e9cf11e5571/students/
335cb51e8c0343918969e939b1461e8f"
 \
     -H 'accesstoken: masaike'

遇事不决,先抓包

通过 APISIX 直接请求的包如下,IpV4 返回结果正常,IpV6 返回结果为 No such name。我粗浅的以为是因为 lua 那一层认为没有拿到 IP,所以没有后续三次握手发送请求的逻辑,请求在 APISIX 这一层直接终止 503 了。

这个问题可以同步用 nslookup 来确定

$ nslookup -type=A school-performance-http.easicare-test-2
Server:         169.254.20.10
Address:        169.254.20.10#53

Name:   school-performance-http.easicare-test-2.svc.kubernetes.local
Address: 10.96.136.142

$ nslookup -type=AAAA school-performance-http.easicare-test-2
Server:         169.254.20.10
Address:        169.254.20.10#53

** server can't find school-performance-http.easicare-test-2: NXDOMAIN

可以看到 A 记录(IPv4)地址有正确的返回,但是 AAAA(IPv6)的查询返回了 NXDOMAIN,NXDOMAIN 是 DNS 响应码(Rcode=3)表示不存在记录,也就是域名解析结果不存在。

验证是否是因为 IPv6 返回 NXDOMAIN 导致的问题

带着这个疑问,我看了一下最新版的 APISIX 的代码,发现在今年(22 年)的 1 月份,已经增加了这部分的逻辑,允许用户通过 apisix.enable_ipv6 关掉 ipv6 解析,具体的 PR 在这里 https://github.com/apache/apisix/pull/6023 ,这个 PR 改动了两个地方,一个 nginx 的 resolver 配置以及 core/dns/client.lua ,增加对 enable_ipv6 参数的处理。

nginx 配置文件部分的修改

lua 代码的修改

于是我们就重新用最新版的 APISIX 重新打包镜像上传,果然问题解决了。

到这里,我以为找到了根本的原因,于是放下了这个问题。

高兴得太早了

后面我想,一个大版本的升级,带来的改动是非常多的,你怎么能确定就是那个带来的呢?于是我来魔改 2.10.1 版本的 APISIX 的代码,将 IPv6 的解析去掉,如下所示。

diff --git a/apisix/core/dns/client.lua b/apisix/core/dns/client.lua
index a6dbfb37..c5c1b8c3 100644
--- a/apisix/core/dns/client.lua
+++ b/apisix/core/dns/client.lua
@@ -137,7 +137,14 @@ function _M.new(opts)
     -- make sure each client has its separate room
     package_loaded["resty.dns.client"] = nil
     local dns_client_mod = require("resty.dns.client")
+    local table_remove = table.remove
 
+    for i, v in ipairs(opts.order) do
+        if v == "AAAA" then
+            table_remove(opts.order, i)
+            break
+        end
+    end

我以为这样改动,就可以解决问题了,结果发现居然服务还是 503,问题压根就没有解决,而且通过抓包确实没有再次发起 AAAA 记录的查询了,说明我的改动生效了,这样就说明并不是因为 AAAA 记录返回 NXDOMAIN 导致的问题。

开始怀疑人生,抓包显示 A 记录的解析已经成功了,为什么 APISIX 会认为域名还是失败的呢。

既然最新版 2.13.0 版本可以,那就来对比代码,看看 DNS 部分的逻辑到底有什么不一样的。

APISIX 的 dns 解析是通过 lua-resty-dns-client 库来实现的,这个库在 APISIX 的友商 kong 项目下:https://github.com/Kong/lua-resty-dns-client, 2.10.1 版本和2.13.0 依赖对比差异如下。

可以看到 2.10.1 版本的 APISIX 用的 lua-resty-dns-client 版本的是 5.2.0,2.13.0 版本的 APISIX 用的版本是 6.0.2,这下就好办了,来一个移花接木,把最新版本的 lua-resty-dns-client 的代码覆盖到旧版的 APISIX 中

cp -rf lua-resty-dns-client-5.2.3/src/resty/dns/* /usr/local/apisix/deps/share/lua/5.1/resty/dns/

重新启动 APISIX,发现问题解决了,跟 IPv4、IPv6 没有任何关系。其实想想也是这样,如果 IPv4 域名解析成功、IPv6 失败的情况下,造成 APISIX 域名解析失败,这个错误也太低级了,不应该发生才对。

到这里问题就已经局限到这个库到底有啥问题了,采用二分的方式,好在这个库的版本不多,从 6.0.25.2.0 版本二分覆盖测试。

很快就发现,5.2.2 版本是 OK 的,再低的版本就会出问题,于是对比 5.2.2 和 5.2.1 到底改了什么。

这里的逻辑就是处理了域名末尾带点号的问题。容器内的 /etc/resolv.conf 的配置如下:

cat /etc/resolv.conf 
nameserver 169.254.20.10
search imdach-dev-dev.svc.kubernetes.local. svc.kubernetes.local. kubernetes.local. gz.cvte.cn

比如我们查询的 app-537b340e61adc0ec7ccd840bdcd8a59989cb6598-3.imdach-dev-dev 域名的时候,就会依次查询 search,如下所示。

可以看到,DNS 的查询和返回,都没有带上最后的点号,比如查询

app-537b340e61adc0ec7ccd840bdcd8a59989cb6598-3
.imdach-dev-dev.svc.kubernetes.local.

请求和响应的域名都是

app-537b340e61adc0ec7ccd840bdcd8a59989cb6598-3
.imdach-dev-dev.svc.kubernetes.local

DNS 查询和响应都没有最后的点号。

但是 lua 中需要进行字符串的匹配,qname 是带有点号的,DNS 返回结果虽然查询到了 IP 但是域名没有点号,这样 lua 中就匹配不上,表现出来就是域名解析失败未找到对应 IP。

到这里原因基本上清楚了,那为什么最近才出问题呢?于是问了一下 k8s 运维的同学,得到了肯定的答复。

为了 100% 验证这个问题,我自己手动改了一下 /etc/resolv.conf,将 search 中的点号去掉,然后 APISIX 回滚到最初出问题的版本,问题同样也解决了,访问正常了。

cat /etc/resolv.conf 
nameserver 169.254.20.10
search imdach-dev-dev.svc.kubernetes.local svc.kubernetes.local kubernetes.local gz.cvte.cn

域名到底要不要以点号结尾

其实标准的 DNS 域名就是需要以点号结尾的,但是大家在用域名的过程中往往省略了最后的点,. 是根域名,访问所有域名本质都是要从根域名开始解析,比如 care.seewo.com. 理论要先问根域名服务器 .com 在哪。

这里专门有一篇文章讲这个问题,感兴趣的同学可以深入研究

http://www.dns-sd.org/trailingdotsindomainnames.html

小结

因为内网 K8S 的升级,导致 /etc/resolv.conf 中的 search 末尾多了一个点号,导致低版本的 APISIX(APISIX 2.12 版本以下)的域名解析会失败,与 IPV6 返回 NXDOMAIN 无关。

后记

分析问题一定得静下心,仔细去探究问题的根源,不要急于求成。

今天看到一句话,觉得挺好的,分享给大家,「经验用来对待特殊场景,方**用来处理通用场景,没有经验可能会慢一些,没有方**可能寸步难行」

如果上面的分析过程能给你带来一些启发,那就很好了。

 

点赞收藏
挖坑的张师傅

机械工业出版社《深入理解 JVM 字节码》作者,掘金小册作者《JVM 字节码从入门到精通》、《深入理解TCP 协议》作者,Vim 死忠粉、Kotlin&Go 爱好者、能抓一手好包、喜欢底层技术和分享。微信公众号:张师傅的博客(shifuzhang01)

请先登录,查看5条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

Redis stream 用做消息队列完美吗?

Redis stream 用做消息队列完美吗?

Netty源码解析:writeAndFlush

Netty源码解析:writeAndFlush

12
5

机械工业出版社《深入理解 JVM 字节码》作者,掘金小册作者《JVM 字节码从入门到精通》、《深入理解TCP 协议》作者,Vim 死忠粉、Kotlin&Go 爱好者、能抓一手好包、喜欢底层技术和分享。微信公众号:张师傅的博客(shifuzhang01)