動態連線和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
已在呼叫後建立條目。
只有在傳送資料包時才建立條目。這導致以下可能的種族:
兩個資料包都沒有找到已確認的
conntrack
在.。1. nf_conntrack_in step
。兩個包conntrack
建立具有相同元組的條目。與上述情況相同,但
conntrack
其中一個數據包的輸入是在另一個數據包呼叫之前確認的。3. get_unique_tuple
。另一個數據包得到一個不同的回覆元組,通常是隨著源埠的更改。與第一種情況相同,但步驟中選擇了兩個具有不同端點的不同規則。
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流量的叢集中再現。
核修正
不管解決方法是什麼,我決定修復核心中的根本原因。
結果是以下核心補丁:
“網路過濾器:NF_CONATH:解決匹配連線軌跡的衝突”修正第一場比賽(接受)。
“netfilter:nf_nat:返回匹配CTs的同一個應答元組”修正第二場比賽(等待評審)。
這兩個補丁修復了只執行一個DNS伺服器例項的叢集的問題,同時降低了其他伺服器的超時命中率。
為了完全消除所有情況下的問題,第三場比賽需要解決。一個可能的解決方法是合併衝突conntrack
步驟中來自同一套接字的具有不同目的地的條目。5. __nf_conntrack_confirm
。但是,這將使先前IptabLes規則遍歷的結果失效,該資料包的目的地在該步驟中被更改。
另一種可能的解決方案是在每個節點上執行dns伺服器例項,並按照我的同事的建議建立一個pod來查詢執行在本地節點上的dns伺服器。這裡.
結論
首先,我展示了“dns查詢需要5秒”問題的基本細節,並揭示了罪魁禍首linux。conntrack
核心模組,本質上是動態的。看見這篇文章用於模組中其他可能的比賽。
接下來,我介紹了核心修復,它消除了模組中三個相關種族中的兩個。
最後,我強調,在編寫本報告時,根本原因並不完全固定,在某些情況下需要使用者的變通。