使用node local dns來提升ClusterDNS服務質量


在 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有以下幾點需要注意到:

  1. dnsPolicy: None。不使用ClusterDNS。
  2. 配置searches,保證集群內部域名能夠被正常解析。
  3. 適當降低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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM