Coredns+Nodelocaldns cache解決Coredns域名解析延遲

目前18.6版本和以前的coredns都會出現超時5s的狀況,那麼爲何會出現coredns超時的狀況發生?node

背景

在Kubernetes中,Pod訪問DNS服務器(kube-dns)的最多見方法是經過服務抽象。 所以,在嘗試解釋問題以前,瞭解服務的工做原理以及所以在Linux內核中如何實現目標網絡地址轉換(DNAT)相當重要。linux

服務是如何工做的?

在iptables模式下(默認狀況下),每一個服務的kube-proxy在主機網絡名稱空間的nat表中建立一些iptables規則。
讓咱們考慮在集羣中具備兩個DNS服務器實例的kube-dns服務。 相關規則以下:nginx

(1) -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
<...>
(2) -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
<...>
(3) -A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-LLLB6FGXBLX6PZF7
(4) -A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-LRVEW52VMYCOUSMZ
<...>
(5) -A KUBE-SEP-LLLB6FGXBLX6PZF7 -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.32.0.6:53
<...>
(6) -A KUBE-SEP-LRVEW52VMYCOUSMZ -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.32.0.7:53

在咱們的示例中,每一個Pod的/etc/resolv.conf中都有填充的名稱服務器10.96.0.10條目。 所以,來自Pod的DNS查找請求將發送到10.96.0.10,它是kube-dns服務的ClusterIP(虛擬IP)。
因爲(1),請求進入KUBE-SERVICE鏈,而後匹配規則(2)最後根據(3)隨機值,跳轉到(5)或(6)根據規則( 負載平衡),將請求UDP數據包的目標IPv4地址修改成DNS服務器的「實際」 IPv4地址。 這種修飾是由DNAT完成的。
10.32.0.6和10.32.0.7是Weave Net網絡中Kubernetes DNS服務器容器的IPv4地址。git

Linux內核中的DNAT

如上所示,服務(在iptables模式下)的基礎是DNAT,它由內核執行。github

DNAT的主要職責是同時更改傳出數據包的目的地,答覆數據包的源,並確保對全部後續數據包進行相同的修改。
後者嚴重依賴於鏈接跟蹤機制,也稱爲conntrack,它被實現爲內核模塊。顧名思義,conntrack會跟蹤系統中正在進行的網絡鏈接。
以一種簡化的方式,conntrack中的每一個鏈接都由兩個元組表示-一個元組用於原始請求(IP_CT_DIR_ORIGINAL),另外一個元組用於答覆(IP_CT_DIR_REPLY)。對於UDP,每一個元組都由源IP地址,源端口以及目標IP地址和目標端口組成。答覆元組包含存儲在src字段中的目標的真實地址。
例如,若是IP地址爲10.40.0.17的Pod向kube-dns的ClusterIP發送一個請求,該請求被轉換爲10.32.0.6,則將建立如下元組:web

原始:src = 10.40.0.17 dst = 10.96.0.10 sport = 53378 dport = 53
回覆:src = 10.32.0.6 dst = 10.40.0.17 sport = 53 dport = 53378

經過具備這些條目,內核能夠相應地修改任何相關數據包的目的地和源地址,而無需再次遍歷DNAT規則。此外,它將知道如何修改回覆以及應將回復發送給誰。
建立conntrack條目後,將首先對其進行確認。稍後,若是沒有已確認的conntrack條目具備相同的原始元組或回覆元組,則內核將嘗試確認該條目。
conntrack建立和DNAT的簡化流程以下所示:express

+---------------------------+     
|                                         |      爲一個給定的包建立一個conntrack,若是
|    1. nf_conntrack_in       |     它並不存在;IP_CT_DIR_REPLY是
|                                        |      反向的IP_CT_DIR_ORIGINAL元組,所以
+------------+--------------+    回覆元組的src尚未改變。
             |
             v
+---------------------------+
|                                          |
|     2. ipt_do_table             |     找到一個匹配的DNAT規則。
|                                          |
+------------+--------------+
             |
             v
+---------------------------+
|                                        |      根據DNAT規則更新回覆元組src部分
|    3. get_unique_tuple    |     使其不被任何已經確認的鏈接使用。
|                                        |     
+------------+--------------+
             |
             v
+---------------------------+
|                                        |     
|     4. nf_nat_packet        |      根據應答元組打亂數據包的目的端口和地址。
|                                       |
+------------+--------------+
             |
             v
+----------------------------+
|                                                |  若是沒有與相同的原始元組或應答元組確認的連
|  5. __nf_conntrack_confirm | 則確認鏈接道;
|                                                |     
+----------------------------+     遞增insert_failed計數器並刪除數據包(若是在)。

問題

當從不一樣線程經過同一套接字同時發送兩個UDP數據包時,會出現問題。
UDP是無鏈接協議,所以connect(2)syscall(與TCP相反)不會發送任何數據包,所以,在調用以後沒有建立conntrack條目。
該條目僅在發送數據包時建立。這致使如下可能:apache

一、兩個包都沒有在1中找到一個確認的conntrack。nf_conntrack_in一步。爲兩個包建立具備相同元組的兩個conntrack條目。
二、與上面的狀況相同,但一個包的conntrack條目在另外一個包調用3以前被確認。get_unique_tuple。另外一個包一般在源端口更改後獲得一個不一樣的應答元組。
三、與第一種狀況相同,可是在步驟2中選擇了具備不一樣端點的兩個不一樣規則。ipt_do_table。api

競爭的結果是相同的—其中一個包在步驟5中被丟棄。__nf_conntrack_confirm。緩存

這正是在DNS狀況下發生的狀況。 GNU C庫和musl libc都並行執行A和AAAA DNS查找。因爲競爭,內核可能會丟棄其中一個UDP數據包,所以客戶端一般會在5秒的超時後嘗試從新發送它。

值得一提的是,這個問題不只是針對Kubernetes的-任何並行發送UDP數據包的Linux多線程進程都容易出現這種競爭狀況。

另外,即便您沒有任何DNAT規則,第二場競爭也可能發生-加載nf_nat內核模塊足以啓用對get_unique_tuple的調用就足夠了。

可使用conntrack -S得到的insert_failed計數器能夠很好地指示您是否遇到此問題。

緩解措施

意見建議

建議採起多種解決方法:禁用並行查找,禁用IPv6以免AAAA查找,使用TCP進行查找,改成在Pod的解析器配置文件中設置DNS服務器的真實IP地址,等等。不幸的是,因爲經常使用的容器基礎映像Alpine Linux使用musl libc的限制,它們中的許多不起做用。
對於Weave Net用戶來講彷佛可靠的方法是使用tc延遲DNS數據包。

另外,您可能想知道在ipvs模式下的kube-proxy是否能夠繞過這個問題。答案是否認的,由於conntrack也是在這種模式下啓用的。此外,在使用rr調度程序時,能夠在DNS流量較高的集羣中輕鬆重現第3次競爭。

內核修復

不管採用哪一種解決方法,都決定在內核中修復根本緣由。
結果是如下內核補丁:

一、 「 netfilter:nf_conntrack:解決衝突以匹配conntracks」修復了第一場比賽(被接受)。
二、 「 netfilter:nf_nat:返回相同的答覆元組以匹配CT」修復了第二場比賽(等待複審)。

這兩個補丁解決了僅運行一個DNS服務器實例的羣集的問題,同時下降了其餘實例的超時命中率。
爲了在全部狀況下徹底消除問題,須要解決第三場競爭。一種可能的解決方法是在步驟5中將衝突的conntrack條目與來自同一套接字的不一樣目的地合併。__nf_conntrack_confirm。可是,這會使在該步驟中更改了目的地的數據包的先前iptables規則遍歷的結果無效。
另外一種可能的解決方案是在每一個節點上運行DNS服務器實例,並按照個人同事的建議,經過Pod查詢運行在本地節點上的DNS服務器。
結論
首先,我展現了「 DNS查找須要5秒」問題的基本細節,並揭示了罪魁禍首-Linux conntrack內核模塊,它本質上是不受歡迎的。有關模塊中也存在其餘可能的問題

解決方案以下:

方案(一):使用 TCP 協議發送 DNS 請求
經過resolv.conf的use-vc選項來開啓 TCP 協議
測試
一、修改/etc/resolv.conf文件,在最後加入一行文本:
options use-vc
二、此壓測可根據下面測試的go文件進行測試,編譯好後放進一個pod中,進行壓測:
#200個併發,持續30秒,記錄超過5s的請求個數 ./dns -host {service}.{namespace} -c 200 -d 30 -l 5000

方案(二):避免相同五元組 DNS 請求的併發
經過resolv.conf的single-request-reopen和single-request選項來避免:
single-request-reopen (glibc>=2.9) 發送 A 類型請求和 AAAA 類型請求使用不一樣的源端口。這樣兩個請求在 conntrack 表中不佔用同一個表項,從而避免衝突。
single-request (glibc>=2.10) 避免併發,改成串行發送 A 類型和 AAAA 類型請求,沒有了併發,從而也避免了衝突。

測試 single-request-reopen
修改/etc/resolv.conf文件,在最後加入一行文本:
options single-request-reopen
此壓測可根據下面測試的go文件進行測試,編譯好後放進一個pod中,進行壓測:
#200個併發,持續30秒,記錄超過5s的請求個數 ./dns -host {service}.{namespace} -c 200 -d 30 -l 5000

測試 single-request
修改/etc/resolv.conf文件,在最後加入一行文本:
options single-request
此壓測可根據下面測試的go文件進行測試,編譯好後放進一個pod中,進行壓測:
#200個併發,持續30秒,記錄超過5s的請求個數 ./dns -host {service}.{namespace} -c 200 -d 30 -l 5000

最後結果,若是你測試過,相信coredns的測試若是仍是增長使用 TCP 協議發送 DNS 請求,仍是避免相同五元組 DNS 請求的併發,都沒有顯著的解決coredns延遲的結果

那麼其實 k8s 官方也意識到了這個問題比較常見,因此也給出了 coredns 以 cache 模式做爲 daemonset 部署的解決方案

在 Kubernetes 集羣中使用 NodeLocal DNSCache

https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/dns/nodelocaldns

NodeLocal DNSCache 經過在集羣節點上做爲 DaemonSet 運行 dns 緩存代理來提升集羣 DNS 性能。 在當今的體系結構中,處於 ClusterFirst DNS 模式的 Pod 能夠鏈接到 kube-dns serviceIP 進行 DNS 查詢。 經過 kube-proxy 添加的 iptables 規則將其轉換爲 kube-dns/CoreDNS 端點。 藉助這種新架構,Pods 將能夠訪問在同一節點上運行的 dns 緩存代理,從而避免了 iptables DNAT 規則和鏈接跟蹤。 本地緩存代理將查詢 kube-dns 服務以獲取集羣主機名的緩存缺失(默認爲 cluster.local 後綴),並有效解決5秒延遲問題

在集羣中運行 NodeLocal DNSCache 有以下幾個好處:

若是本地沒有 CoreDNS 實例,則具備最高 DNS QPS 的 Pod 可能必須到另外一個節點進行解析,使用 NodeLocal DNSCache 後,擁有本地緩存將有助於改善延遲
跳過 iptables DNAT 和鏈接跟蹤將有助於減小 conntrack 競爭並避免 UDP DNS 條目填滿 conntrack 表。(常見的5s超時問題就是這個緣由形成的)
從本地緩存代理到 kube-dns 服務的鏈接能夠升級到 TCP,TCP conntrack 條目將在鏈接關閉時被刪除,而 UDP 條目必須超時(默認 nf_conntrack_udp_timeout 是 30 秒)
將 DNS 查詢從 UDP 升級到 TCP 將減小歸因於丟棄的 UDP 數據包和 DNS 超時的尾部等待時間,一般長達 30 秒(3 次重試+ 10 秒超時)。
能夠從新啓用負緩存,從而減小對 kube-dns 服務的查詢數量。

架構圖

啓用 NodeLocal DNSCache 以後,這是 DNS 查詢所遵循的路徑:
Coredns+Nodelocaldns cache解決Coredns域名解析延遲

環境檢查

該資源清單文件中包含幾個變量,其中:
PILLARDNSSERVER :表示 kube-dns 這個 Service 的 ClusterIP,能夠經過命令 kubectl get svc -n A | grep kube-dns | awk '{ print $4 }' 獲取
PILLARLOCALDNS:表示 DNSCache 本地的 IP,默認爲 169.254.20.10
PILLARDNSDOMAIN:表示集羣域,默認就是 cluster.local

另外還有兩個參數 PILLARCLUSTERDNSPILLARUPSTREAMSERVERS,這兩個參數會經過鏡像 1.15.6 版本以上的去進行配置,對應的值來源於 kube-dns 的 ConfigMap 和定製的 Upstream Server 配置。直接執行以下所示的命令便可安裝:

運行nodelocaldns須要進行替換如下操做,若是下載過慢,能夠直接使用下面的yaml來使用,須要替換的話,只有10.96.0.10,這個是kube-dns service的clusterIP

開始部署

wget -O nodelocaldns.yaml "https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml" && \
sed -i 's/k8s.gcr.io/zhaocheng172/g' nodelocaldns.yaml && \
sed -i 's/__PILLAR__DNS__SERVER__/10.96.0.10/g' nodelocaldns.yaml && \
sed -i 's/__PILLAR__LOCAL__DNS__/169.254.20.10/g' nodelocaldns.yaml && \
sed -i 's/__PILLAR__DNS__DOMAIN__/cluster.local/g' nodelocaldns.yaml

最終替換結果

#Copyright 2018 The Kubernetes Authors.
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#http://www.apache.org/licenses/LICENSE-2.0
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns-upstream
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "KubeDNSUpstream"
spec:
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  selector:
    k8s-app: kube-dns
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  Corefile: |
    cluster.local:53 {
        errors
        cache {
                success 9984 30
                denial 9984 5
        }
        reload
        loop
        bind 169.254.20.10 10.96.0.10
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        health 169.254.20.10:8080
        }
    in-addr.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 10.96.0.10
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        }
    ip6.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 10.96.0.10
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        }
    .:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 10.96.0.10
        forward . __PILLAR__UPSTREAM__SERVERS__ {
                force_tcp
        }
        prometheus :9253
        }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    k8s-app: node-local-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 10%
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
      annotations:
        prometheus.io/port: "9253"
        prometheus.io/scrape: "true"
    spec:
      priorityClassName: system-node-critical
      serviceAccountName: node-local-dns
      hostNetwork: true
      dnsPolicy: Default  # Don't use cluster DNS.
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
      - effect: "NoExecute"
        operator: "Exists"
      - effect: "NoSchedule"
        operator: "Exists"
      containers:
      - name: node-cache
        image: zhaocheng172/k8s-dns-node-cache:1.15.13
        resources:
          requests:
            cpu: 25m
            memory: 5Mi
        args: [ "-localip", "169.254.20.10,10.96.0.10", "-conf", "/etc/Corefile", "-upstreamsvc", "kube-dns-upstream" ]
        securityContext:
          privileged: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9253
          name: metrics
          protocol: TCP
        livenessProbe:
          httpGet:
            host: 169.254.20.10
            path: /health
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
        volumeMounts:
        - mountPath: /run/xtables.lock
          name: xtables-lock
          readOnly: false
        - name: config-volume
          mountPath: /etc/coredns
        - name: kube-dns-config
          mountPath: /etc/kube-dns
      volumes:
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
      - name: kube-dns-config
        configMap:
          name: kube-dns
          optional: true
      - name: config-volume
        configMap:
          name: node-local-dns
          items:
            - key: Corefile
              path: Corefile.base

能夠經過以下命令來查看對應的 Pod 是否已經啓動成功:

Coredns+Nodelocaldns cache解決Coredns域名解析延遲
須要注意的是這裏使用 DaemonSet 部署 node-local-dns 使用了 hostNetwork=true,會佔用宿主機的 8080 端口,因此須要保證該端口未被佔用。

另外咱們還須要修改 kubelet 的 --cluster-dns 參數,將其指向 169.254.20.10,Daemonset 會在每一個節點建立一個網卡來綁這個 IP,Pod 向本節點這個 IP 發 DNS 請求,緩存沒有命中的時候纔會再代理到上游集羣 DNS 進行查詢。

兩種方案測試nodelocaldns實效性

第一種就是定製一個pod,Kubernetes Pod dnsPolicy 能夠針對每一個Pod設置DNS的策略,經過PodSpec下的dnsPolicy字段能夠指定相應的策略
這種方式能夠直接啓動一個pod,Pods將直接能夠訪問在同一節點上運行的 dns 緩存代理,從而避免了 iptables DNAT 規則和鏈接跟蹤,可是這種對於總體集羣來說並不適合,只提升了當前pod的DNScache的命中率,這種適合定製一些dns策略

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web
    spec:
      containers:
      - image: nginx
        name: nginx
      dnsConfig:
        nameservers:
          - 169.254.20.10
        searches:
          - public.svc.cluster.local
          - svc.cluster.local
          - cluster.local
        options:
          - name: ndots
            value: "5"
      dnsPolicy: None

第二種若是對於集羣來說,須要所有生效
須要替換每一個節點的clusterDNS的地址

clusterDNS:
- 10.96.0.10

替換的話能夠直接使用sed直接替換,另外須要全部節點替換並重啓kubelet

sed -i 's/10.96.0.10/169.254.20.10/g' /var/lib/kubelet/config.yaml
systemctl daemon-reload
systemctl restart kubelet

待 node-local-dns 安裝配置完成後,咱們能夠部署一個新的 Pod 來驗證下:(test-node-local-dns.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: test-node-local-dns
spec:
  containers:
  - name: local-dns
    image: busybox
    command: ["/bin/sh", "-c", "sleep 60m"]

直接部署:

$ kubectl apply -f test-node-local-dns.yaml
$ kubectl exec -it test-node-local-dns /bin/sh
/ # cat /etc/resolv.conf
nameserver 169.254.20.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

須要注意
咱們如今已經能夠看到 nameserver 已經變成 169.254.20.10 了,固然對於以前的歷史 Pod 要想使用 node-local-dns 則須要重建全部的pod

一、linux 中glibc的 resolver 的缺省超時時間是 5s,而致使超時的緣由是內核conntrack模塊的 bug。
二、DNS client (glibc 或 musl libc) 會併發請求 A 和 AAAA 記錄,跟 DNS Server 通訊天然會先 connect (創建 fd),後面請求報文使用這個 fd 來發送,因爲 UDP 是無狀態協議, connect 時並不會發包,也就不會建立 conntrack 表項, 而併發請求的 A 和 AAAA 記錄默認使用同一個 fd 發包,send 時各自發的包它們源 Port 相同(由於用的同一個 socket 發送),當併發發包時,兩個包都尚未被插入 conntrack 表項,因此 netfilter 會爲它們分別建立 conntrack 表項,而集羣內請求 kube-dns 或 coredns 都是訪問的 CLUSTER-IP,報文最終會被 DNAT 成一個 endpoint 的 POD IP,當兩個包剛好又被 DNAT 成同一個 POD IP 時,它們的五元組就相同了,在最終插入的時候後面那個包就會被丟掉,若是 dns 的 pod 副本只有一個實例的狀況就很容易發生(始終被 DNAT 成同一個 POD IP),現象就是 dns 請求超時,client 默認策略是等待 5s 自動重試,若是重試成功,咱們看到的現象就是 dns 請求有 5s 的延時。

另外若是要想去跟蹤 DNS 的解析過程的話能夠去經過抓包來觀察具體超時的最大時間。

測試coredns+nodelocaldns的效果
咱們能夠經過Go的一個測試用例來測試DNS的解析是否獲得提高

首先安裝一個Go的環境

#wget   https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz
#tar -zxf go1.12.5.linux-amd64.tar.gz -C /usr/local
#export PATH=$PATH:/usr/local/go/bin
#go version

主要是要測試 dns 服務的性能,至關於壓測工具只作域名解析的耗時時間

cat dns-test.go

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)
}

直接go build,會有一個dns-test的二進制文件

將此文件放到pod裏面
#kubectl cp /root/dns-test web-7f5df76d5f-r76xx:/root -n kube-system
下面來進行測試

進行壓測
200個併發,持續30秒,記錄超過5s的請求個數
/dns -host {service}.{namespace} -c 200 -d 30 -l 5000
結果以下:
1.14.3版本原生集羣不加參數測試默認使用iptables性能方面可能不是那麼好,不過已經沒有5s延遲狀況發生,最高耗時2.9s
Coredns+Nodelocaldns cache解決Coredns域名解析延遲

1.18.6原生集羣不加參數測試
結論沒有延遲操做最大耗時爲0.5s,默認採用ipvs,效率很是高
Coredns+Nodelocaldns cache解決Coredns域名解析延遲

最後總結:經過測試結果獲得如下結論1.14.3集羣使用coredns+nodelocaldns配合使用避免相同五元組 DNS 請求的併發,增長options single-request-reopen,最大耗時下降到2.25s左右,不會出現5s超時狀況,效果最好1.18.6集羣使用coredns+nodelocaldns不加參數測試最大耗時下降到0.53s左右,效率明顯提高,效果最好

相關文章
相關標籤/搜索