本篇博客记录在实施K8S过程中遇到的dns解析慢和不稳定问题。
背景
服务上线K8S后,通过调用链trace发现接口95线响应时间恶化10倍以上,于是开始排查。
明确问题方向
从调用链trace系统,很容易看出接口的哪一个网络请求拖慢了响应时间。
但是发现无论是http调用、mysql、redis的响应时间都严重变慢,所以怀疑是基础层面的问题引起,因此有2个方向:
- 虚拟化网络慢
- DNS解析慢
为了确定到底是哪个原因,最好是通过工具客观分析,拿数据说话。
登录到container内,创建如下的一个文件:
1
2
3
4
5
6
7
8
9
10
|
vim curl-format.txt
time_namelookup: %{time_namelookup}\n
time_connect: %{time_connect}\n
time_appconnect: %{time_appconnect}\n
time_redirect: %{time_redirect}\n
time_pretransfer: %{time_pretransfer}\n
time_starttransfer: %{time_starttransfer}\n
----------\n
time_total: %{time_total}\n
|
然后利用curl请求目标域名,就可以得到处理各个阶段的耗时情况:
1
|
curl -w "@curl-format.txt" -o /dev/null -s -L "http://smzdm_probation_db_mysql_s01"
|
多执行几次,会出现响应时间糟糕的情况,数据如下:
1
2
3
4
5
6
7
8
|
time_namelookup: 0.124686
time_connect: 0.126762
time_appconnect: 0.000000
time_redirect: 0.000000
time_pretransfer: 0.126845
time_starttransfer: 0.146775
----------
time_total: 0.146876
|
curl的展示策略是累计时间,因此可以看出dns查询就占掉了0.124686秒,整个请求的总时间才0.146876,并且connect时间几乎为0。
下面是响应时间正常的情况:
1
2
3
4
5
6
7
8
|
time_namelookup: 0.004197
time_connect: 0.004961
time_appconnect: 0.000000
time_redirect: 0.000000
time_pretransfer: 0.004997
time_starttransfer: 0.006776
----------
time_total: 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
|
struct hostent *
gethostbyname(const char *name);
|
它不像后来linux推出的getaddrinfo函数,可以指定具体IPV4还是IPV4:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int
getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
struct addrinfo {
int ai_flags; /* input flags */
int ai_family; /* protocol family for socket */
int ai_socktype; /* socket type */
int ai_protocol; /* protocol for socket */
socklen_t ai_addrlen; /* length of socket-address */
struct sockaddr *ai_addr; /* socket-address for socket */
char *ai_canonname; /* canonical name for service location */
struct addrinfo *ai_next; /* pointer to next in list */
};
|
这些就不具体说明了。
总之,现在问题明确了,就是因为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
|
1、cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="ipv6.disable=1 crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
grub2-mkconfig -o /boot/grub2/grub.cfg
|
在grud中配置ipv6.disable=1可以达到下线ipv6解析的效果,改后需要重启宿主机。
该效果已得到验证,还有一些其他选项应该不是必须的,大家可以酌情参考:https://blog.csdn.net/cjm712/article/details/87886614。