最近我们内网的 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 在这里 github.com/apache/apis… ,这个 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 项目下: github.com/Kong/lua-re… 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.2
到 5.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 在哪。
这里专门有一篇文章讲这个问题,感兴趣的同学可以深入研究
小结
因为内网 K8S 的升级,导致 /etc/resolv.conf
中的 search 末尾多了一个点号,导致低版本的 APISIX(APISIX 2.12 版本以下)的域名解析会失败,与 IPV6 返回 NXDOMAIN 无关。
后记
分析问题一定得静下心,仔细去探究问题的根源,不要基于求成。
今天看到一句话,觉得挺好的,分享给大家,「经验用来对待特殊场景,方法论用来处理通用场景,没有经验可能会慢一些,没有方法论可能寸步难行」
如果上面的分析过程能给你带来一些启发,那就很好了。