本篇博客記錄在實施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。