ulb4是ucloud自主研發的基於dpdk的高可用四層負載均衡產品,**能力接近線速;dpdk則是乙個高效能的開源資料面開發套件。ulb4作為使用者應用的全域性入口,在大流量多元化場景下保證使用者業務的持續穩定至關重要,這也是ucloud網路產品團隊的技術使命。尤其現網單個ulb集群承載頻寬已達10g,包量83萬pps,執行環境複雜,即使面臨突發因素(比如觸發未知bug),我們也要設法保證產品正常工作,避免產生嚴重影響。
近期,我們在ulb4的線上環境中,發現了乙個dpdk的發包異常現象,由於整個ulb產品為集群架構,該異常並未導致使用者服務不可用。但為了任何時刻都能保證使用者服務的足夠穩定,團隊通過gdb、報文匯出工具、生產環境流量映象等手段,從現網gb級流量中捕獲異常報文,再結合dpdk原始碼分析,定位到原因出自dpdk本身的bug並修復解決。期間未對使用者業務造成影響,進一步保證了ucloud數萬ulb例項的穩定執行。
問題背景
在12月初一向穩定的ulb4集群中突然出現了容災,某台ulb4伺服器工作異常被自動移出了集群。當時的現象是:
**面服務監控到網絡卡接收方向流量正常,但是傳送方向流量為0,重啟**面服務後又可以正常收發,同時集群其他機器也會不定期出現異常情況。對使用者業務而言,會出現少量連線輕微抖動,隨後迅速恢復。
下面是整個問題的處理過程,我們在此過程中做出種種嘗試,最終結合dpdk原始碼完成分析和解決,後續也準備將自研的報文匯出工具開源共享。
問題定位與分析
ulb4集群一直很穩定地工作,突然陸續在集群的不同機器上出現同樣的問題,並且機器恢復加入集群後,過了一段時間又再次出現同樣的問題。根據我們的運營經驗,初步猜測是某種異常報文觸發了程式bug。但是,面對gb級流量如何捕獲到異常報文?又如何在不影響業務情況下找出問題呢?
一、gdb除錯報文,發現疑點
想要知道整個程式為什麼不發包,最好的辦法就是能夠進入到程式中去看看具體的執行過程。對於dpdk使用者態程式來說,gdb顯然是乙個好用的工具。我們在發包程式邏輯中設定斷點,並通過disassemble命令檢視該函式的執行邏輯,反彙編之後足足有七百多行。(該函式中呼叫的很多函式都使用了inline修飾,導致該函式在彙編之後指令特別多)
結合對應dpdk版本的原始碼,單條指令一步步執行。在多次嘗試之後,發現每次都會在下圖所示的地方直接返回。
大致流程是i40e_xmit_pkts()在傳送的時候,發現傳送佇列滿了就會去呼叫i40e_xmit_cleanup()清理佇列。dpdk中網絡卡在傳送完資料報後會去回寫特定字段,表明該報文已經傳送,而驅動程式去檢視該字段就可以知道這個報文是否已經被發過。此處的問題就是驅動程式認為該佇列中的報文始終未被網絡卡傳送出去,後續來的報文將無法加入到佇列而被直接丟棄。
至此,直接原因已經找到,就是網絡卡因為某種原因不發包或者沒能正確回寫特定字段,導致驅動程式認為傳送佇列始終處於佇列滿的狀態,而無法將後續的報文加入傳送佇列。
那麼為什麼出現佇列滿?異常包是否相關呢?帶著這個疑問,我們做了第二個嘗試。
二、一鍵還原網絡卡報文
佇列滿,而且後面的報文一直加不進去,說明此時佇列裡面的報文一直卡在那。既然我們猜測可能是存在異常報文,那麼有沒有可能異常報文還在佇列裡面呢?如果可以把當前佇列裡面的報文全部匯出來,那就可以進一步驗證我們的猜測了。
基於對dpdk的深入研究,我們根據以下步驟匯出報文。
我們看i40e_xmit_pkts()函式,會發現第乙個引數就是傳送佇列,所以我們可以獲取到佇列的資訊。
如下圖所示,在剛進入斷點的時候,檢視暫存器資訊,以此來獲得該函式對應的引數。
當我們列印該佇列的訊息時,卻發現沒有符號資訊,此時我們可以如下圖所示去載入編譯時候生成的 i40e_rxtx.o 來獲取對應符號資訊。
在得到佇列資訊後,我們使用gdb的dump命令將整個佇列中所有的報文全部按佇列中的順序匯出,對每個報文按序號命名。
此時匯出的報文還是原始的報文,我們無法使用wireshark方便地檢視報文資訊。為此如下圖所示,我們使用libpcap庫寫了個簡單的小工具轉換成wireshark可以解析的pcap檔案。
果然,如下圖所示,在匯出的所有報文中包含了乙個長度為26位元組,但內容為全0的報文。這個報文看上去十分異常,似乎初步驗證了我們的猜測:
為了提高在排查問題時匯出報文的速度,我們寫了乙個報文一鍵匯出工具,可以在異常時一鍵匯出所有的報文並轉成pcap格式。
三、流量映象,確認異常包
第二步結論讓整個排查前進了一大步,但是佇列包是經過一系列程式處理的,並不是真正的原始業務報文。不達目的不罷休,關鍵時刻還是要上映象抓包,於是當晚緊急聯絡網路運維同事在交換機上配置port-mirroring(埠映象),將發往ulb4集群的流量映象到乙個空閒伺服器上進行映象抓包。當然,映象伺服器還需要做特殊配置,如下:
設定網絡卡混雜模式,用於收取映象流量(ifconfig net2 promisc)。 關閉gro功能(ethtool -k net2 gro off),用於收取最原始的報文,防止linux的gro功能提前將報文進行組裝。 根據異常ip的地域特性,我們針對性抓取了部分源ip段的流量。
參考命令:nohup tcpdump -i net2 -s0 -w %y%m%d_%h-%m-%s.pcap -g 1800 「proto gre and (((ip[54:4]&0x11223000)==0x11223000) or ((ip[58:4]&0x11223000)==0x11223000))」 &
經過多次嘗試後,功夫不負有心人,故障出現了,經過層層剝離篩選,找到了如下報文:
這是ip分片報文,但是奇怪的是ip分片的第二片只有ip頭。經過仔細比對,這兩個報文合在一起就是匯出佇列報文中的那兩個連在一起的報文。後26位元組和全0報文完全吻合。
我們知道在tcp/ip協議中,如果傳送時乙個ip報文長度超過了mtu,將會觸發ip分片,會被拆成多個小的分片報文進行傳送。正常情況下,所有的分片肯定都是攜帶有資料的。但是這乙個分片報文就很異常,報文的總長度是20,也就是說只有乙個ip頭,後面不再攜帶任何資訊,這樣的報文是沒有任何意義的。這個報文還因為長度太短在經過交換機後被填充了26位元組的0。
至此,我們最終找到了這個異常報文,也基本驗證了我們的猜測。但是還需要去實際驗證是否為這種異常報文導致。(從整個報文的互動來看,這一片報文本來是設定了不可分片的tcp報文,但是在經過某個公網閘道器後被強制設定了允許分片,並且分片出了這種異常的形式。)
四、解決方案
如果確實是這個異常報文導致的,那麼只要在收包時對這種異常報文進行檢查然後丟棄就可以了。於是,我們修改dpdk程式,丟棄這類報文。作為驗證,先發布了一台線上伺服器,經過1天執行再也沒有出現異常容災情況。既然問題根因已經找到,正是這種異常報文導致了dpdk工作異常,後續就可以按灰度全網發布了。
五、dpdk社群反饋
本著對開源社群負責任的態度,我們準備將bug向dpdk社群同步。對比最新的commit後,找到11月6日提交的乙個commit,情況如出一轍,如下:
ip_frag: check fragment length of incoming packet
dpdk 18.11最新發布的版本中,已對此進行了修復,和我們處理邏輯一致,也是丟棄該異常報文。
覆盤和總結
處理完所有問題後,我們開始做整體覆盤。
一、ulb無法發包的成因總結
ulb4無法發包的整個產生過程如下:
dpdk收到分片報文中的第一片,將其快取下來等待後續分片; 第二片只有ip頭的異常分片到來,dpdk按照正常的報文處理邏輯進行處理,並沒有進行檢查丟棄,於是兩片報文的rte_mbuf結構被鏈在一起,組成了乙個鏈式報文返回給ulb4; 這樣的報文被ulb4接收後,因為整個報文的總長度並沒有達到需要分片的長度,所以ulb4直接呼叫dpdk的傳送介面傳送出去; dpdk沒有對這種異常報文進行檢查,而是直接呼叫相應的使用者態網絡卡驅動直接將報文傳送出去; 使用者態網絡卡驅動在傳送這樣的異常報文時觸發了網絡卡tx hang; 觸發tx hang後,網絡卡不再工作,驅動佇列中報文對應的傳送描述符不再被網絡卡正確設定傳送完成標記; 後續的報文持續到來,開始在傳送佇列中積壓,最終將整個佇列佔滿,再有報文到來時將被直接丟棄。
二、為什麼異常報文會觸發網絡卡tx hang
首先我們看下dpdk中跟網絡卡傳送報文相關的**。
如下圖所示,通常網絡卡對應的datasheet中會對相應字段進行相關描述,網絡卡驅動中一般都會有相應的資料結構與其對應。
在有了基本了解後,我們猜想如果直接在程式中手動構造這種類似的異常報文,是否也會導致網絡卡異常不發包?
答案是肯定的。
如下圖所示,我們使用這樣的**片段構成異常報文,然後呼叫dpdk介面直接傳送,很快網絡卡就會tx hang。
三、對直接操作硬體的思考
直接操作硬體是一件需要非常謹慎的事情,在傳統的linux系統中,驅動程式一般處於核心態由核心去管理,而且驅動程式**中可能進行了各種異常處理,因此很少會發生使用者程式操作導致硬體不工作的情況。而dpdk因為其自身使用使用者態驅動的特點,使得可以在使用者態直接操作硬體,同時為了提公升效能可能進行了非常多的優化,如果使用者自身程式處理出問題就有可能會導致網絡卡tx hang這樣的異常情況發生。
四、工具的價值
我們編寫了一鍵匯出dpdk驅動佇列報文的工具,這樣就可以在每次出現問題時,快速匯出網絡卡驅動傳送佇列中的所有報文,大大提高了排查效率。這個工具再優化下後,準備在ucloud github上開源,希望對dpdk開發者有所幫助。
寫在最後
dpdk作為開源套件,通常情況下穩定性和可靠性不存在什麼問題,但是實際的應用場景千變萬化,一些特殊情況可能導致dpdk工作異常。雖然發生概率很小,但是dpdk通常在關鍵的閘道器位置,一旦出現了問題,哪怕是很少見的問題也將會產生嚴重影響。
因此技術團隊理解其工作原理並對其原始碼進行分析,同時能夠結合具體現象一步步定位出dpdk存在的問題,對提高整個dpdk程式的服務可靠性具有重要意義。值得一提的是,ulb4的高可用集群架構在本次問題的處理過程中發揮了重要作用,在一台不可用的時候,集群中其他機器也可以繼續為使用者提供可靠服務,有效提公升了使用者業務的可靠性。
抽絲剝繭,在實踐中深入學習QTP
前言 只要您仔細地閱讀本書並加以實踐,在工作中結合測試專案的特點,還有qtp自帶的幫助手冊,基本上就可以滿足您自動化測試的全部需求。而且本書詳細地描述了所包含例子的具體操作步驟,這些例子都是作者在本機上測試通過的。本書也沒有對自動化測試管理進行擴充套件,畢竟真正做自動化回歸測試的人可能不是測試經理,...
抽絲剝繭解開CSS3中的Border屬性的外衣
這幾天使用border raduis畫了各種形狀,順帶歸納了一下css中的border屬性,本篇筆記中主要涉及的知識點有 1.border屬性 2.css3新增的3個border屬性 border color border radius border image border屬性 語法 border...
生產環境下的負載均衡配置
一 簡介 首先考慮到的是將網上的連線通過負載均衡的方式分散來減輕伺服器的壓力,這方面可以使用nginx 來實現 其次需要解決的問題是session,對比了幾種方案發現nginx內建的ip hash策略可以解決該問題,最終網路的架構變成了下圖所示,在該方案中增加了4臺伺服器,其中一台nginx負載 另...