在 Kubernetes 集群會碰到這個間歇性 5 延遲的問題,Weave works 發布了一篇博客 Racy conntrack and DNS lookup timeouts 詳細介紹了問題的原因。
簡單來說,由於 UDP 是無連接的,內核 netfilter 模塊在處理同一個 socket 上的並發 UDP 包時就可能會有三個競爭問題。以下面的 conntrack 和 DNAT 工作流程為例: 由於 UDP 的 connect 系統調用不會立即創建 conntrack 記錄,而是在 UDP 包發送之后才去創建,這就可能會導致下面三種問題: 兩個 UDP 包在第一步 nf*conntrack*in 中都沒有找到 conntrack 記錄,所以兩個不同的包就會去創建相同的 conntrack 記錄(注意五元組是相同的)。 一個 UDP 包還沒有調用 get*unique*tuple 時 conntrack 記錄就已經被另一個 UDP 包確認了。 兩個 UDP 包在 ipt*do*table 中選擇了兩個不同端點的 DNAT 規則。 所有這三種場景都會導致最后一步 _*nf*conntrack_confirm 失敗,從而一個 UDP 包被丟棄。由於 GNU C 庫和 musl libc 庫在查詢 DNS 時,都會同時發出 A 和 AAAA DNS 查詢,由於上述的內核競爭問題,就可能會發生其中一個包被丟掉的問題。丟棄之后客戶端會超時重試,超時時間通常是 5 秒。
簡介: NodeLocal DNSCache在集群的上運行一個dnsCache daemonset來提高clusterDNS性能和可靠性。在ACK集群上的一些測試表明:相比於純coredns方案,nodelocaldns + coredns方案能夠大幅降低DNS查詢timeout的頻次,提升服務穩定性,能夠扛住1倍多的QPS。
NodeLocal DNSCache通過在集群上運行一個dnsCache daemonset來提高clusterDNS性能和可靠性。在ACK集群上的一些測試表明:相比於純coredns方案,nodelocaldns + coredns方案能夠大幅降低DNS查詢timeout的頻次,提升服務穩定性。
本文將介紹如何在ACK集群上部署node local dns。
部署nodelocaldns
nodelocaldns通過添加iptables規則能夠接收節點上所有發往169.254.20.10
的dns查詢請求,把針對集群內部域名查詢請求路由到coredns;把集群外部域名請求直接通過host網絡發往集群外部dns服務器。
# 下載部署腳本 $ curl https://node-local-dns.oss-cn-hangzhou.aliyuncs.com/install-nodelocaldns.sh # 部署。確保kubectl能夠連接集群 $ bash install-nodelocaldns.sh
定制業務容器dnsConfig
為了使業務容器能夠使用nodelocaldns,需要將nameserver配置為169.254.20.10
,而不是ClusterDNS。定制dnsConfig有以下幾點需要注意到:
- dnsPolicy: None。不使用ClusterDNS。
- 配置searches,保證集群內部域名能夠被正常解析。
- 適當降低ndots值。當前ACK集群ndots值默認為5,降低ndots值有利於加速集群外部域名訪問。如果業務容器沒有使用帶多個dots的集群內部域名,建議將值設為2。
apiVersion: v1 kind: Pod metadata: name: alpine namespace: default spec: containers: - image: alpine command: - sleep - "10000" imagePullPolicy: Always name: alpine dnsPolicy: None dnsConfig: nameservers: ["169.254.20.10"] searches: - default.svc.cluster.local - svc.cluster.local - cluster.local options: - name: ndots value: "2"
如何避免這個問題
要避免 DNS 延遲的問題,就要設法繞開上述三個問題,所以就有下面幾種方法:
①. 禁止並發 DNS 查詢,比如在 Pod 配置中開啟 single-request-reopen
選項強制 A 查詢和 AAAA 查詢使用相同的 socket:
dnsConfig:
options:
- name: single-request-reopen
②. 禁用 IPv6 從而避免 AAAA 查詢,比如可以給 Grub 配置 ipv6.disable=1
來禁止 ipv6(需要重啟節點才可以生效)。
通過大神寫go 測試dns解析時間腳本進行了測試
package main import ( "context" "flag" "fmt" "net" "sync/atomic" "time" ) var host string var connections int var duration int64 var limit int64 var timeoutCount int64 func main() { // os.Args = append(os.Args, "-host", "www.baidu.com", "-c", "200", "-d", "30", "-l", "5000") flag.StringVar(&host, "host", "", "Resolve host") flag.IntVar(&connections, "c", 100, "Connections") flag.Int64Var(&duration, "d", 0, "Duration(s)") flag.Int64Var(&limit, "l", 0, "Limit(ms)") flag.Parse() var count int64 = 0 var errCount int64 = 0 pool := make(chan interface{}, connections) exit := make(chan bool) var ( min int64 = 0 max int64 = 0 sum int64 = 0 ) go func() { time.Sleep(time.Second * time.Duration(duration)) exit <- true }() endD: for { select { case pool <- nil: go func() { defer func() { <-pool }() resolver := &net.Resolver{} now := time.Now() _, err := resolver.LookupIPAddr(context.Background(), host) use := time.Since(now).Nanoseconds() / int64(time.Millisecond) if min == 0 || use < min { min = use } if use > max { max = use } sum += use if limit > 0 && use >= limit { timeoutCount++ } atomic.AddInt64(&count, 1) if err != nil { fmt.Println(err.Error()) atomic.AddInt64(&errCount, 1) } }() case <-exit: break endD } } fmt.Printf("request count:%d\nerror count:%d\n", count, errCount) fmt.Printf("request time:min(%dms) max(%dms) avg(%dms) timeout(%dn)\n", min, max, sum/count, timeoutCount) }
使用方法為:
./dns -host {service}.{namespace} -c 200 -d 30 比如 ./dns -host redis.senyint -c 200 -d 30
阿里雲的方式內部解析還是5秒左右,沒用, 使用①+nodelocaldns方式解析時間控制在800ms左右
參考摘自:
https://developer.aliyun.com/article/709471
https://blog.csdn.net/qq_23435961/article/details/106659069
https://zhuanlan.zhihu.com/p/145127061