一個bug引發的血案--Linux內核bug引起Mesos, Kubernetes, Docker containers的TCP/IP數據包失效。
【編者的話:最近發現linux內核bug,會造成使用veth設備進行路由的容器(例如Docker on IPv6,k8s,google container Engine和Mesos)不檢查TCP校驗碼(checksum),這會造成應用在某些場合下,例如壞的網絡設備,接收錯誤數據。這個bug可以在到三年前任何一個測試過的內核版本中發現。補丁已經被整合進核心代碼,正在回遷入3.14之前的多個發行版中(例如SuSE,Canonical)。如果在自己環境中使用容器,強烈建議打上此補丁,或者等發布后,部署已經打上補丁的核心版本。】
【原文地址參見: https://tech.vijayp.ca/linux-k ... othmv 】
【注:Docker默認NAT網絡并不受影響,而實際上,Google Container Engine也通過自己的虛擬網絡防止硬件錯誤。】
【編者:Jake Bower指出這個bug跟一段時間前發現的另外一個bug很相似。有趣!】
起因
十一月的某個周末,一組推ter負責服務的工程師收到值班通知,每個受影響的應用都報出“impossible”錯誤,看起來像奇怪的字符出現在字符串中,或者丟失了必須的字段。這些錯誤之間的聯系并不很明確指向推ter分布式架構。問題加劇表現為:任何分布式系統,數據,一旦出問題,將會引起更長的字段出問題(他們都存在緩存中,寫入日志磁盤中,等等...)。經過一整天應用層的排錯,團隊可以將問題定位到某幾個機柜內的設備。團隊繼續深入調查,發現在第一次影響開始前,扇入的TCP糾錯碼錯誤大幅度升高;這個調查結果將應用從問題中摘了出來,因為應用只可能引起網絡擁塞而不會引起底層包問題。
【編者:用“團隊”這個詞可能費解,是不是很多人來解決這個問題。公司內部很多工程師參與排錯,很難列出每個人名字,但是主要貢獻者包括:Brian Martin, David Robinson, Ken Kawamoto, Mahak Patidar, Manuel Cabalquinto, Sandy Strong, Zach Kiehl, Will Campbell, Ramin Khatibi, Yao Yue, Berk Demir, David Barr, Gopal Rajpurohit, Joseph Smith, Rohith Menon, Alex Lambert, and Ian Downes, Cong Wang.】
一旦機柜被移走,應用失效問題就解決了。當然,很多因素可以造成網絡層失效,例如各種奇怪的硬件故障,線纜問題,電源問題,等....;TCP/IP糾錯碼就是為保護這些錯誤而設計的,而且實際上,從這些設備的統計證據表明錯誤都可以檢測到---那么為什么這些應用也開始失效呢?
隔離特定交換機后,嘗試減少這些錯誤(大部分復雜的工作是由SRE Brain Martin完成的)。通過發送大量數據到這些機柜可以很容易復現失效數據被接收。在某些交換機,大約~10%的包失效。然而,失效總是由于核心的TCP糾錯碼造成的(通過netstat -a返回的TcpInCsumError參數報告),而并不發送給應用。(在Linux中,IPv4 UDP包可以通過設置隱含參數SO_NO_CHECK,以禁止糾錯碼方式發送;用這種方式,我們可以發現數據失效)。
Evan Jones(@epcjones) 有一個理論,說的是假如兩個bit剛好各自翻轉(例如0->1和1->0)失效數據有可能有有效的糾錯碼,對于16位序字節,TCP糾錯碼會剛好抵消各自的錯誤(TCP糾錯碼是逐位求和)。當失效數據一直在消息體固定的位置(對32位求模),事實是附著碼(0->1)消除了這種可能性。因為糾錯碼在存儲之前就無效了,一個也糾錯碼bit翻轉外加一個數據bit翻轉,也會抵消各自的錯誤。然而,我們發現出問題的bit并不在TCP糾錯碼內,因此這種解釋不可能發生。
很快,團隊意識到測試是在正常linux系統上進行的,許多推ter服務是運行在Mesos上,使用Linux容器隔離不同應用。特別的,推ter的配置創建了veth(virtual ethernet)設備,然后將應用的包轉發到設備中。可以很確定,當把測試應用跑在Mesos容器內后,立即發現不管TCP糾錯碼是否有效(通過TcpInCsumErrors增多來確認),TCP鏈接都會有很多失效數據。有人建議激活 veth以太設備上的“checksum offloading” 配置,通過這種方法解決了問題,失效數據被正確的丟棄了。
到這兒,有了一個臨時解決辦法,推ter Mesos團隊很快就將解決辦法作為fix推給了Mesos項目組,將新配置部署到所有Twiter的生產容器中。
排錯
當Evan和我討論這個bug時,我們決定由于TCP/IP是在OS層出現問題,不可能是Mesos不正確配置造成的,一定應該是核心內部網絡棧未被發現bug的問題。
為了繼續查找bug,我們設計了最簡單的測試流程:
單客戶端打開一個socket,每秒發送一個簡單且長的報文 。
單服務端(使用處于偵聽模式的nc)在socket端偵聽,打印輸出收到的消息。
網絡工具,tc,可以用于發送前,任意修改包內容。
一旦客戶端和服務端對接上,用網絡工具失效所有發出包,發送10秒鐘。
可以在一個臺式機上運行客戶端,服務器在另外一個臺式機上。通過以太交換機連接兩臺設備,如果不用容器運行,結果和我們預想一致,并沒有失效數據被接收到,也就是10秒內沒有失效包被接收到。客戶單停止修改包后,所有10條報文會立刻發出;這確認Linux端TCP棧,如果沒有容器,工作是正常的,失效包會被丟棄并重新發送直到被正確接收為止。
?The way it should work: corrupt data are not delivered; TCP retransmits data
Linux 和容器
現在讓我們快速回顧一下Linux網絡棧如何在容器環境下工作會很有幫助。容器技術使得用戶空間(user-space)應用可以在機器內共存,因此帶來了虛擬環境下的很多益處(減少或者消除應用之間的干擾,允許多應用運行在不同環境,或者不同庫)而不需要虛擬化環境下的消耗。理想地,任何資源之間競爭都應該被隔離,場景包括磁盤請求隊列,緩存和網絡。
Linux下,veth設備用于隔離同一設備中運行的容器。linux網絡棧很復雜,但是一個veth設備本質上應該是用戶角度看來的一個標準以太設備。
為了構建一個擁有虛擬以太設備的容器,必須(1)創建一個虛機,(2)創建一個veth (3)將veth綁定與容器端 (4)給veth指定一個IP地址 (5)設置路由,用于Linux流量控制,這樣包就可以扇入扇出容器了。
為什么是虛擬造成了問題
我們重建了如上測試場景,除了服務端運行于容器中。然后,當開始運行時,我們發現了很多不同:失效數據并未被丟棄,而是被轉遞給應用!通過一個簡單測試(兩個臺式機,和非常簡單的程序)就重現了錯誤。
失效數據被轉遞給應用,參見左側窗口。
我們可以在云平臺重建此測試環境。k8s的默認配置觸發了此問題(也就是說,跟Google Container Engine中使用的一樣),Docker的默認配置(NAT)是安全的,但是Docker的IPv6配置不是。
修復問題
重新檢查Linux核心網絡代碼,很明顯bug是在veth核心模塊中。在核心中,從硬件設備中接收的包有ip_summed字段,如果硬件檢測糾錯碼,就會被設置為CHECKSUM_UNNECESSARY,如果包失效或者不能驗證,者會被設置為CHECKSUM_NONE。
veth.c中的代碼用CHECKSUM_UNNECESSARY代替了CHECKSUM_NONE,這造成了應該由軟件驗證或者拒絕的糾錯碼被默認忽略了。移除此代碼后,包從一個棧轉發到另外一個(如預期,tcpdump在兩端都顯示無效糾錯碼),然后被正確傳遞(或者丟棄)給應用層。我們不想測試每個不同的網絡配置,但是可以嘗試不少通用項,例如橋接容器,在容器和主機之間使用NAT,從硬件設備到容器見路由。我們在推ter生產系統中部署了這些配置(通過在每個veth設備上禁止RX checksum offloading)。
還不確定為什么代碼會這樣設計,但是我們相信這是優化設計的一個嘗試。很多時候,veth設備用于連接統一物理機器中的不同容器。
邏輯上,包在同一物理主機不同容器間傳遞(或者在虛機之間)不需要計算或者驗證糾錯碼:唯一可能失效的是主機的RAM,因為包并未經過線纜傳遞。不幸的是,這項優化并不像預想的那樣工作:本地產生的包,ip_summed字段會被預設為CHECKSUM_PARTIAL,而不是CHECKSUM_NONE。
這段代碼可以回溯到驅動程序第一次提交 (commit e314dbdc1c0dc6a548ecf [NET]: Virtual ethernet device driver). Commit 0b7967503dc97864f283a net/veth: Fix packet checksumming (in December 2010) 修復了本地產生,然后發往硬件設備的包,默認不改變CHECKSUM_PARTIAL的問題。然而,此問題仍然對進入硬件設備的包生效。
核心修復補丁如下:
?核心修復補丁
結論
我對Linux netdev和核心維護團隊的工作很欽佩;代碼確認非常迅速,不到幾個星期補丁就被整合,不到一個月就被回溯到以前的(3.14+)穩定發行版本中(Canonical,SuSE)。在容器化環境占優勢的今天,很驚訝這樣的bug居然存在了很久而沒被發現。很難想象因為這個bug引發多少應用崩潰和不可知的行為!如果確信由此引發問題請及時跟我聯系
來自: http://dockone.io/article/1038