1. 程式人生 > >動態連線和DNS查詢超時

動態連線和DNS查詢超時

最近,有許多Kubernetes使用者關於從Pods中查詢DNS的錯誤報告,有時需要5秒甚至更長的時間:編織#3287庫伯內特斯#56903.

在這篇文章中,我將解釋造成這種延遲的根本原因,討論一些緩解措施,並介紹核心修復。

背景

在Kubernetes中,POD訪問DNS伺服器的最常見方式(kube-dns)是通過服務抽象。因此,在試圖解釋這個問題之前,瞭解服務是如何工作的,以及目標網路地址轉換(DNAT)是如何在Linux核心中實現的,這一點非常重要。

NOTE: all examples in this post are based on Kubernetes v1.11.0 and Linux kernel v4.17.

服務如何運作

在……裡面iptables模式,這是一個預設模式,kube-proxy為每個服務建立幾個iptable規則nat主機網路名稱空間的表。

讓我們考慮一下kube-dns具有叢集中兩個DNS伺服器例項的服務。有關規則如下:

(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都有nameserver 10.96.0.10在其/etc/resolv.conf.因此,來自POD的DNS查詢請求將被髮送到10.96.0.10,這是一個叢集IP(虛擬IP)kube-dns服務。

請求輸入KUBE-SERVICE鏈由於(1),然後匹配規則(2),最後,根據隨機值(3)跳轉到(5)或(6)規則(窮人的負載平衡),該規則將請求UDP資料包的目標IPv 4地址修改為DNS伺服器的“真實”IPv 4地址。這種修改是由DNAT完成的。

10.32.0.6和10.32.0.7是編織網中Kubernetes DNS伺服器容器的IPv 4地址。

Linux核心中的DNAT

如上文所示,服務的基礎(在iptables(模式)是由核心執行的DNAT。

DNAT的主要職責是更改傳送資料包的目的地、回覆資料包的來源,同時確保對所有後續資料包進行相同的修改。

後者在很大程度上依賴於連線跟蹤機制(也稱為conntrack它是作為核心模組實現的。顧名思義,conntrack跟蹤系統中正在進行的網路連線。

以一種簡化的方式,conntrack用兩個元組表示-一個用於原始請求(IP_CT_DIR_ORIGINAL)及一份作答覆(IP_CT_DIR_REPLY)對於UDP,每個元組由源IP地址、源埠以及目標IP地址和目標埠組成。回覆元組包含儲存在src場。

例如,如果IP地址為10.40.0.17的Pod向叢集IP傳送請求kube-dns如果將其翻譯為10.32.0.6,將建立以下元組:

  • 原件: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建立和指定國家主管部門的情況如下:

+---------------------------+      Create a conntrack for a given packet if
|                           |      it does not exist; IP_CT_DIR_REPLY is
|    1. nf_conntrack_in     |      an invert of IP_CT_DIR_ORIGINAL tuple, so
|                           |      src of the reply tuple is not changed yet.
+------------+--------------+
             |
             v
+---------------------------+
|                           |
|     2. ipt_do_table       |      Find a matching DNAT rule.
|                           |
+------------+--------------+
             |
             v
+---------------------------+
|                           |      Update the reply tuples src part according
|    3. get_unique_tuple    |      to the DNAT rule in a way that it is not used
|                           |      by any already confirmed conntrack.
+------------+--------------+
             |
             v
+---------------------------+
|                           |      Mangle the packet destination port and address
|     4. nf_nat_packet      |      according to the reply tuple.
|                           |
+------------+--------------+
             |
             v
+----------------------------+
|                            |     Confirm the conntrack if there is no confirmed
|  5. __nf_conntrack_confirm |     conntrack with either the same original or
|                            |     a reply tuple; increment insert_failed counter
+----------------------------+     and drop the packet if it exists.

問題

當兩個UDP資料包同時從不同的執行緒通過同一個套接字傳送時,會發生問題。

Udp是一個無連線的協議,因此不傳送任何資料包。連線(2)SysCall(與TCP相反),因此,沒有conntrack已在呼叫後建立條目。

只有在傳送資料包時才建立條目。這導致以下可能的種族:

  1. 兩個資料包都沒有找到已確認的conntrack在.。1. nf_conntrack_in step。兩個包conntrack建立具有相同元組的條目。

  2. 與上述情況相同,但conntrack其中一個數據包的輸入是在另一個數據包呼叫之前確認的。3. get_unique_tuple。另一個數據包得到一個不同的回覆元組,通常是隨著源埠的更改。

  3. 與第一種情況相同,但步驟中選擇了兩個具有不同端點的不同規則。2. ipt_do_table.

競賽的結果是相同的-其中一個數據包被丟棄在步驟中。5. __nf_conntrack_confirm.

這正是在DNS情況下發生的情況。GNU C庫和MUSL libc並行執行A和AAAA DNS查詢。其中一個UDP資料包可能會因為競爭而被核心丟棄,因此客戶端將嘗試在超時(通常是5秒)之後重新發送它。

值得一提的是,這個問題不僅針對Kubernetes-任何Linux多執行緒程序並行傳送UDP資料包都很容易出現這種競爭情況。

而且,即使沒有任何DNAT規則,第二場比賽也可能發生-這足以載入nf_nat核心模組來啟用對get_unique_tuple.

這,這個,那,那個insert_failed計數器,可以用conntrack -S是一個很好的指標,你是否正在經歷這個問題。

緩解

建議

提出了許多解決方案:禁用並行查詢、禁用IPv 6以避免AAAA查詢、使用TCP進行查詢、在POD的解析器配置檔案中設定DNS伺服器的真實IP地址等。有關詳細資訊,請參閱帖子開頭的連結問題。不幸的是,由於通常使用的容器基礎映像AlpinLinux所使用的MUSLlibc中的限制,其中許多都無法工作。

對於編織網路使用者來說,似乎可靠的方法是將dns資料包延遲到TC。看見昆廷·馬丘(Quentin Machu)筆錄關於這件事。

另外,您可能想知道Kube-代理是否在ipvs模式可以繞過問題。答案是否定的,因為conntrack也在此模式下啟用。同時,當使用rr排程器,第三次競賽可以很容易地在一個低DNS流量的叢集中再現。

核修正

不管解決方法是什麼,我決定修復核心中的根本原因。

結果是以下核心補丁:

  1. “網路過濾器:NF_CONATH:解決匹配連線軌跡的衝突”修正第一場比賽(接受)。

  2. “netfilter:nf_nat:返回匹配CTs的同一個應答元組”修正第二場比賽(等待評審)。

這兩個補丁修復了只執行一個DNS伺服器例項的叢集的問題,同時降低了其他伺服器的超時命中率。

為了完全消除所有情況下的問題,第三場比賽需要解決。一個可能的解決方法是合併衝突conntrack步驟中來自同一套接字的具有不同目的地的條目。5. __nf_conntrack_confirm。但是,這將使先前IptabLes規則遍歷的結果失效,該資料包的目的地在該步驟中被更改。

另一種可能的解決方案是在每個節點上執行dns伺服器例項,並按照我的同事的建議建立一個pod來查詢執行在本地節點上的dns伺服器。這裡.

結論

首先,我展示了“dns查詢需要5秒”問題的基本細節,並揭示了罪魁禍首linux。conntrack核心模組,本質上是動態的。看見這篇文章用於模組中其他可能的比賽。

接下來,我介紹了核心修復,它消除了模組中三個相關種族中的兩個。

最後,我強調,在編寫本報告時,根本原因並不完全固定,在某些情況下需要使用者的變通。