容器云平臺和容器網絡緊密結合,共同構建了容器化應用程序的網絡基礎設施,實現了容器之間的通信、隔離和安全性。文中容器云平臺采用的容器網絡組件是calico,這個是業界普遍采用的一種方案,性能及安全性在同類產品中都是比較好的。本篇就筆者在近期項目上所涉及的幾個容器網絡問題,依據真實案例,分享如何通過網絡抓包進行問題的解析與處理。
實戰一、因基礎網絡層MTU不一致引發的應用訪問偶發性超時
MTU,即Maximum Transmission Unit(最大傳輸單元),此值設定TCP/IP協議傳輸數據報時的最大傳輸單元,單位字節。傳統的網絡模式下,如果此值設置得太小,網絡性能會受影響;如果設置得太大可能觸發“部分網站打不開”等問題。
在容器環境下,容器網絡借助于IAAS層網絡傳輸來達到分布式服務間的網絡通訊。容器環境下的典型特征是:服務都是多實例的、分布式的、跨網段的、跨多網絡區域、甚至跨VPC的。再加上容器網絡本身也是一層虛擬化,特別是在overlay模式下,會額外嵌入一層協議,更增加了容器網絡問題分析的復雜性。特別是當IAAS較復雜的組網模式時,如果MTU不合理,可能導致以下問題:
大包阻塞(Jumbo Frames):如果不同網段的設備或路由器的MTU設置不一致,可能會導致大于某個網絡段MTU的數據包無法通過。這可能導致大包阻塞,因為在路徑上的某個點上,數據包將被分片或被丟棄。
丟包:當數據包在跨越網絡段時被分片,而某個網絡段無法容納整個分片時,會導致分片的丟失。這可能會引起通信中的丟包情況,降低網絡性能。
性能下降:分片和重組數據包會增加網絡設備的負擔,可能導致性能下降。特別是在高負載情況下,這可能會對容器間的通信產生顯著的影響。
某項目前端頁面上出現個別菜單偶發性超時,但是如果重新點擊又正常了。這種現象出現多次,且近期出現的次數更加明顯。
應用的通訊架構可以抽象為如下:

前端頁面上出現個別菜單偶發性超時

在前端瀏覽器上F12調試-504

從調用鏈測發現調用有失敗
平臺的調用鏈引擎是pinpoint,業界很多廠商也是基于此技術棧實現分布式應用的調用拓撲、調用性能的分析。我們從調用鏈上可以看到前端與后端的請求中,有少數請求是超時的,可以明確看到Socket("message:"Read timeout..")的網絡接收數據超時。從這里可以初步感受到問題是出在網絡通訊上。

調用與被調用的容器間的ping包都是正常的;做一些簡單的curl也是正常的。
由于從調用鏈上能夠得到的信息是出在網絡上,而在容器環境下業務間的通訊是容器網絡,于是首先從最基本的檢查做起,理清前端、后端間的相關容器的節點分布情況,寫腳本探測容器之間的網絡穩定性,看看是不是會出現網絡抖動或ping丟包。
最基本檢查可以通過ping腳本來評估網絡的穩定性,選取不同的節點上調度的容器,分別在前、后端容器內執行長時間的ping探測,并收集ping結果進行分析,腳本如下:
#長ping腳本,帶時間戳,可以直接塞入相關被探測的容器中 #!/bin/bash for i in 100.80.95.XX 100.80.174.YY 100.80.90.ZZ 100.80.76.MM ... ;do ping $i | awk '{ print $0"\t" strftime("%Y-%m-%d %H:%M:%S",systime()); fflush()}' >> ./PingpodTopod.log & done
經過幾個小時的探測(確保這個期間業務有偶發性超時問題發生),從ping的結果上來分析,所有ping的req包都有回包,沒有丟包,且延時都非常低,從這里至少可以說明網絡鏈路層是沒有問題的。
那進一步做tcp層通訊的基本檢查,從業務側獲取一些涉及tcp通訊且不會對生產環境產生任何影響的接口調用,在前端容器內對后端容器實行實施多次curl調用檢查,從多對容器以及多次的抓取中可以看到所有的curl都是能夠快速應答,且沒有超時發生。
從最基本的鏈路層檢測及tcp通訊的檢測上來看,容器網絡沒有明顯的問題。那么問題會出現在哪里?接下來對相關容器實施網絡抓包,從網絡包上看看能不能發現端倪。
限于篇幅,長言短敘,隨便挑一對發生問題的異常容器,然后實施抓包分析。服務端抓包可以采取最常見的基于ip與port的過濾器來抓網絡包。特別注意要分別在服務端與客戶端進行抓包,好處是可以基于時間點及sequence來相互印證,找出有問題的包,然后對比分析,重點查看發出去的包有沒有到達對端,對端的回包自己有沒有收到、有沒有重傳等等,從異常中找出問題的線索。
在下圖中:服務端容器100.80.95.126 、客戶端容器100.80.174.53, 可以很明顯地看到在問題發生的時間點有多次重傳(TCP Retransmission), 進一步分析發現屬于典型的丟包現象。具體分析細節見圖注說明。

客戶端抓包分析:

從客戶端與服務端抓包的多個包的對比中,得出以下結論
未收到的回包屬于大包,大包(長度 >=6000 或者7000的包,服務端發了,但是客戶端收不到)
再加上客戶端容器、服務端容器主要在跨宿主機網段的的機器上,這些特點符合MTU的問題,因此,下一步調整MTU。一般來說,應該要檢查一下IAAS層的網絡設備的MTU的配置,但是IAAS資源都是局方的,而且也分屬于不同部門,協調起來比較困難。因此,考慮在容器側調整MTU,在經過客戶的允許后,在夜晚調小了線上環境的容器網絡的MTU,問題解決。
再次抓包,可以看到大的數據包也都可以正常傳輸了,見下圖:

從調用鏈上反饋也是正常的啦。

通過分析收發包的特點,找出異常數據包的發送情況,主要特征是:多發生在跨網段間的主機上,核心是小包能正常通訊,大包通訊上有問題,大包無法到達對端,典型的MTU配置不當的表現。
知道問題的原因了,那么解決問題的辦法就很簡單了,可以直接調小一點容器層面的MTU,從而可以規避此問題。
在容器場景下,不僅要在容器平面進行MESH方式的通訊,常常容器還需要與外部通訊,甚至與第3方的廠家進行業務交互,困難點在于你無法清晰知道第3方服務的邏輯、也無法登錄第3方環境,無法知道網絡架構、網絡安全配置等。這樣的背景下,當業務出現異常且排除自己業務代碼的問題層面之后,那么可借助網絡抓包來進行分析,通過解析數據的的交互同時比對正常情況下的TCP流與異常情況下的TCP流的差異、同時基于業務采用具體業務協議下本來應該是個什么樣的,從而來發現問題。
這種問題本質上不是網絡問題,網絡層面的通訊都是正常的,是業務邏輯上有潛在的問題,比如業務代碼條件判斷不夠精確,誤判走了不同的流程,回傳了不需要的數據包。
某項目上線期間發現API發起VC充值業務( CGMMLClient調用第三方的的MML接口 QUERY VC INFO)偶發性異常。相關業務日志
APPLICATION|ERROR|192.168.12.243|20|2022-12-15 12:39:38:971||70802003|ConnectorCacheHandler.java:86|postHandle|7083012|245|||Message is timeout, and handled. message [beginTime=2022-12-15 12:39:23.969,timeout=15000,interval=15002 : server channel=[1], channel=[/192.168.12.243:58034], channel=[/10.16.76.5:8124] adapter=[SocketClient-MML],sequence=[SocketClient-00018915], status=[REMOTING_TIMEOUT], lastHandlingObject=[com.ztesoft.zsmart.cg.service.binder.ServiceBindingRuleIntrp@2c881e0], command=[QUERY VC INFO] field list: {"OPTYPE":"1","transId":"000024f3","totalLen":{"_endPos_":8,"_value_":"0068","_startPos_":4},"sessionId":"00000000","terminal":"CCB00000","serviceName":"PPSPHS","version":"1.00","msgBeginFlag":"`SC`","transRemain":"FFFF","checkSum":"AA8A9495","COMMAND":"QUERY VC INFO","transControl":"TXBEG ","Body":{"_endPos_":112,"_startPos_":8},"sessionControl":"DLGCON","CARDNO":"516683732197360540","sessionRemain":"FFFF"} variable list: {"protocol":"MML","reomoteHost":"10.16.76.5","cg":"SocketClient-MML","resultCode":"REMOTING_TIMEOUT","__cost__":0,"command":"QUERY VC INFO","status":"REMOTING_TIMEOUT"} param list: {"arg0":{"opType":1,"class":"com.ztesoft.zsmart.bss.payment.orange.smartfren.bc.service.mml.QueryVcCardRequest","cardNumber":"516683732197360540"}} buffer=[。。。。。。] ]
抓包分析-通過對比正常號碼充值與異常號碼充值的網絡包
既然是偶發性異常,那么一定是存在正常的業務流程與異常的業務流程;接下來大的思路就是對比正常業務流程下的業務收/發包與異常流程下收/發包,然后比較差異。
首先重點從業務日志中找出關鍵的數據包中必傳輸的關鍵字信息,比如本例中充值號碼:"cardNumber":"516683732197360540"。
容器內基于IP與port端口進行抓包,首先針對異常號碼充值包分析,在業務容器內進行抓包,同時從業務側獲取問題發生期間的充值異常的號碼,例如:CARDNO":"516683732197360540 ,基于此號碼分析網絡包。
現在的wireshark功能比較強大,可以通過match添加過濾條件,從TCP包中找出異常號碼"cardNumber":"516683732197360540"對應的通訊鏈路,然后再逐一分析這個TCP流。


另外,取正常號碼充值包分析 ,比如正常的號碼 CRD 160588272651221

從比對中發現問題-第3方的服務端
包的主要特點:
1) 長鏈接;
2) 整個網絡流比較正常,第一次抓取的包,是在一個容器內做的;10萬個包中真正發生重傳的次數也就10來次,比率相當低的;且重傳后都能得到及時響應,不存在網絡丟包;
3) 問題觸發時,并不發生在包的重傳上;
4) 整個單純的TCP層面的通訊上沒有明顯問題;
5) 根據apig發包以及服務端的回應來看,收到的不是預想的包-這是重點。
從比對中發現,每次發生異常時,apig 正常發送了vc query請求包,沒有收到vc qeury的應答包;而正常的充值的號碼都是收到了應答包。因此服務端存在偶發性的回包問題,而服務端是局方管理的第3方公司提供的;之后經由與局方面對面溝通,同時比照務端業務日志的查看,問題確實在第3方公司的服務端。后經第3方公司核查完自己的服務端代碼,發現一處條件的判斷邏輯有問題,后經處理完服務端,該問題得到解決。
[要點] 分別找出正常充值時的TCP數據流與異常充值時的數據流,通過對比正常充值與異常充值的數據包,從而發現差異、介定異常。
實戰三、websocket協議斷鏈-大量斷鏈包中發現規律+源碼求證
WebSocket是一種在單個TCP連接上提供全雙工通信的協議,它允許在客戶端和服務器之間進行實時、雙向的數據傳輸。WebSocket通訊的優點很多,以下重點提2個:
全雙工通信:WebSocket允許雙向通信,客戶端和服務器可以同時發送和接收數據。這使得實時性應用程序的開發更為簡便,例如在線聊天、實時協作和實時更新的Web應用程序。
持久連接:WebSocket連接是持久的,一旦建立連接,它可以保持打開狀態,允許任何時候雙方都能發送數據。這降低了頻繁建立和斷開連接相關的開銷。
簡單地說,基于websocket協議通訊的業務,都傾向于長時通訊,一般情況下不會去主動斷開;如果項目上碰到websocket通訊有頻發斷鏈的情況,要引起注意,請明確這屬于邏輯上設置的主動斷鏈,還是異常斷鏈;特別是在不了解背后實現邏輯的情況下,可以通過抓包,重點抓取3次握手與4次握手,來發現斷鏈的規律、由誰發起的、屬于主動斷鏈、還是丟包導致的鍛煉,有條件的要將抓包與源碼進行比對分析,從而解決問題。
業務網關與k8s APIServer之間websocket有多次的斷鏈,按以往經驗來看,之前沒有發生過斷鏈,都是長鏈接。項目上關于多次斷鏈有點擔心,怕存在隱患。
相關日志
00:54:34:463|||WatcherWebSocketListener.java:66|onClose||43||||WebSocket close received. code: 1000, reason: k8s.log:APPLICATION|DEBUG|||2023-05-19 00:54:35:468|||WatchConnectionManager.java:60|closeWebSocket||475||||Closingwebsocket io.fabric8.kubernetes.client.okhttp.OkHttpWebSocketImpl@46de5f80 k8s.log:APPLICATION|DEBUG|||2023-05-19 00:54:35:468|||WatchConnectionManager.java:63|closeWebSocket||475||||Websocket already closed io.fabric8.kubernetes.client.okhttp.OkHttpWebSocketImpl@46de5f80 k8s.log:APPLICATION|DEBUG|||2023-05-19 01:52:47:876|||WatcherWebSocketListener.java:66|onClose||43||||WebSocket close received. code: 1000, reason: k8s.log:APPLICATION|DEBUG|||2023-05-19 01:52:48:880|||WatchConnectionManager.java:60|closeWebSocket||487||||Closingwebsocket io.fabric8.kubernetes.client.okhttp.OkHttpWebSocketImpl@7de2a21a k8s.log:APPLICATION|DEBUG|||2023-05-19 01:52:48:880|||WatchConnectionManager.java:63|closeWebSocket||487||||Websocket already closed ...(略) ...
由于是線上環境,首先要考慮的是對線上業務的影響。從日志中評估出每小時發生的概率不高,因此,可以實施抓包。
- 初步抓握手包-觀察斷鏈及規律
特別強調一下,為了最大程度地不影響線上環境,只抓少量的包,且只抓建鏈、斷鏈的包,即3次握手、4次握手。此次抓包不同于前面的直接基于IP與端口抓包,會加一些稍顯復雜的過濾條件,盡可能減少線上環境的影響,只抓1000個包,只在網關節點上抓包
sudo nohuptcpdump -i eth0 host XXX and port 6443 and \('tcp[tcpflags] == tcp-rst' or'tcp[tcpflags] == tcp-syn' or 'tcp[tcpflags] == tcp-fin' or 'tcp[13] & 1 != 0'\)-c 1000 -nn -vv -w ./host195-2-socket-esta-or-close-01.pcap &
基于抓包文件分析-取片段:

在實際的分析中是取了多個片段進行分析的,總體發現一個規律
1)服務端發起的斷鏈
2)每個鏈接保持時長都在 (30分鐘 60分鐘)這個區間內,這一點很重要,這為我們后面在規定時間內抓取數據包提供很大的價值。
- 進一步抓數據包
為了不影響生產環境,在同版本的測試環境上進行同樣方式抓包,同樣具有此特點,后續就直接轉到測試環境上進行分析...
按前面的規律,半小時至1小時內必然發生斷鏈,因此,考慮抓取數據包。由于websocket是基于TLS的通訊,為了探索出到底是什么數據帶來的影響,更進一步的措施做TLS數據包的解析。方便解包,仿照網關邏輯,寫了一個golang的websocket測試程序,在測試環境上進行多輪測試。從抓包及websocket TLS解包中來看,是服務端正常的斷開,服務發送了一個TLS Aller_message 21,解包中得出是 Close_notify,由此可以明確知道問題該從服務端查起。

如果不解析包,會是什么情況?可以看到全部是加密的數據,不是太好分析,舉例

- 從k8s master源碼分析
從k8s master源碼分析,發現k8s master有個配置項,RequestTimeout,基本上從來沒有用。限于篇幅,略去中間的摸索過程,見代碼如下

- 定位原因-版本差異
通過辛苦的版本比對及調試驗證,最后找到是k8s高版本做了超時的修復,具體來說是k8s 1.15.0-alpha1,直接上個圖:

由此可以看出,從1.15.0-alpha1版本開始,都是這樣子的。后經與研發一起討論,認為這個斷鏈是正常的,不會有實質性的影響。
【要點】
從抓包中確認有websocket 斷鏈發生,且是服務端發起的抓取足夠量的建鏈、斷鏈,發現規律每(30分鐘、60分鐘)發生一次斷鏈通過以上2點確認排查方向是從k8s master服務端做起結合TLS解包,確認這個斷鏈是TLS 21 消息,屬于正常的斷鏈包,排除了網絡層面的影響結合k8s master的啟動項及源碼發現高版本的源碼中就是這樣的設計邏輯源代碼層面,通過比對低版本與高版本的websocket處的實現,發現從1.15.0-alpha1版本開始,代碼實現上加入了超時斷開的功能經過反復測試,斷鏈的問題清楚,不用擔心,可以忽略,屬于正常機制