K8S – 优化dns解析时间
本篇博客记录在实施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。
阅读后续内容?
你必须付费加入我的知识星球,为有效知识付费是对作者最好的回报。
二维码见下方 或者 右侧边栏。

1
1