本篇博客记录在实施K8S过程中遇到的dns解析慢和不稳定问题。
背景
服务上线K8S后,通过调用链trace发现接口95线响应时间恶化10倍以上,于是开始排查。
明确问题方向
从调用链trace系统,很容易看出接口的哪一个网络请求拖慢了响应时间。
但是发现无论是http调用、mysql、redis的响应时间都严重变慢,所以怀疑是基础层面的问题引起,因此有2个方向:
- 虚拟化网络慢
- DNS解析慢
为了确定到底是哪个原因,最好是通过工具客观分析,拿数据说话。
登录到container内,创建如下的一个文件:
|
1
2
3
4
5
6
7
8
9
10
|
.txt
n
n
n
n
n
n
n
n
|
然后利用curl请求目标域名,就可以得到处理各个阶段的耗时情况:
|
1
|
|
多执行几次,会出现响应时间糟糕的情况,数据如下:
|
1
2
3
4
5
6
7
8
|
0.124686
0.126762
0.000000
0.000000
0.126845
0.146775
--
0.146876
|
curl的展示策略是累计时间,因此可以看出dns查询就占掉了0.124686秒,整个请求的总时间才0.146876,并且connect时间几乎为0。
下面是响应时间正常的情况:
|
1
2
3
4
5
6
7
8
|
0.004197
0.004961
0.000000
0.000000
0.004997
0.006776
--
0.006860
|
因此可以判定就是dns解析慢引起的,而网络因素则可能性很低。
分析具体原因
K8S集群并没有什么压力,请求的域名IP是直接配置在coredns里的,理论上应该几毫秒就返回结果的,那么是什么导致了偶尔的100+毫秒解析时间呢?
所以我在container内开启了tcpdump抓包,监听/etc/resolve.conf中的nameserver地址(其实就是coredns的service ip)的流量,同时通过上述curl命令发起请求,观察耗时长的原因。
透过tcpdump抓包发现,每一次curl请求都发出了2个DNS query,一个是A记录,另外一个是AAAA记录,也就是同时请求了IPV4和IPV6地址。
IPV4很快就返回了结果,而IPV6则经常花费上百毫秒时间,且最终返回NXDomain无IP结果。
这里有2个问题:
- 为什么curl会同时请求IPV4和IPV6呢?实际上我们只有IPV4地址。
- 为什么coredns响应IPV6这么慢呢?
第2个问题很容易回答,因为在coredns里我们只配置了对应的IPV4解析,而IPV6请求会被forward到upstream的DNS(在我这里就是公网DNS),所以IPV6的响应时间就不稳定了。
关于第1个问题,经过谷歌搜索后明确了原因,主要是因为curl走的是glibc的gethostbyname调用来解析域名,而这个函数默认是同时发出ipv4和ipv6请求的:
|
1
2
|
*
;
|
它不像后来linux推出的getaddrinfo函数,可以指定具体IPV4还是IPV4:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int
;
{
/
/
/
/
/
/
/
/
;
|
这些就不具体说明了。
总之,现在问题明确了,就是因为glibc发起了IPV6的请求,而IPV6地址我们没有配置在coredns中所以请求被upstream到外网解析,从而导致了慢查询。
优化方法
一共有3个工作要做,下面依次列出。
下掉ipv6内核模块
glibc之所以发起ipv6查询,其原因是kernel开启了ipv6模块导致的。
因为docker共享的是宿主机的linux kernel,所以我们需要在宿主机上关闭ipv6内核模块,才能彻底禁用ipv6解析的行为。
做法如下:
|
1
2
3
4
5
6
7
8
9
10
|
grub
5
saved
true
.cfg
|
在grud中配置ipv6.disable=1可以达到下线ipv6解析的效果,改后需要重启宿主机。
该效果已得到验证,还有一些其他选项应该不是必须的,大家可以酌情参考:https://blog.csdn.net/cjm712/article/details/87886614。