閱讀:美團容器平臺架構及容器技術實踐

原文地址:https://note.youdao.com/share/?id=08d7c57b04dda159c53155b00cbbe5cb&type=note#/

容器的實現

容器本質上是把系統中爲同一個業務目標服務的相關進程合成一組,放在一個叫做namespace的空間中,同一個namespace中的進程能夠互相通信,同時看不見其他namespace中的進程。每個namespace可以擁有自己獨立的主機名、進程ID系統、IPC、網絡、文件系統、用戶等等資源,在某種程度上,實現了一個簡單的虛擬:讓一個主機上可以同時運行多個互不感知的系統。

此外,爲了限制namespace對物理資源的使用,對進程能使用的CPU、內存等資源需要做一定的限制,這就是Cgroup技術,Cgroup是Control group的意思。比如我們常說的4c4g的容器,實際上是限制這個容器namespace中所用的進程,最多能夠使用4核的計算資源和4GB的內存。

美團的解法、改進和優化

隔離

之前一直和虛擬機打交道,但直到用上容器,才發現在容器裏面看到的CPU、Memory的信息都是服務器主機的信息,而不是容器自身的配置信息,直到現在,社區版的容器還是這樣。比如一個4c4g的容器,在容器內部可以看到有40顆CPU、196GB內存的資源,這些資源其實是容器所在宿主機的信息。這給人的感覺,就像是容器的“自我膨脹”,覺得自己能力很強,但實際上並沒有,而且還會帶來很多問題。

上圖是一個內存信息隔離的例子。獲取系統內存信息時,社區Linux無論在主機上還是在容器中,內核都是統一返回主機的內存信息,如果容器內的應用,按照它發現的宿主機內存來進行配置的話,實際資源是遠遠不夠的,導致的結果就是:系統很快會發生OOM異常。

我們做的隔離工作,是在容器中獲取內存信息時,內核根據容器的Cgroup信息返回容器的內存信息(類似LXCFS的工作)。

CPU信息隔離的實現和內存的類似,不再贅述,這裏舉一個CPU數目影響應用性能例子。

大家都知道,JVM GC(垃圾對象回收)對Java程序執行性能有一定的影響。默認的JVM使用公式“ParallelGCThreads = (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8)” 來計算做並行GC的線程數,其中ncpus是JVM發現的系統CPU個數。一旦容器中JVM發現了宿主機的CPU個數(通常比容器實際CPU限制多很多),這就會導致JVM啓動過多的GC線程,直接的結果就導致GC性能下降。Java服務的感受就是延時增加,TP監控曲線突刺增加,吞吐量下降。針對這個問題有各種解法:

  • 顯式的傳遞JVM啓動參數“-XX:ParallelGCThreads”告訴JVM應該啓動幾個並行GC線程。它的缺點是需要業務感知,爲不同配置的容器傳不同的JVM參數。
  • 在容器內使用Hack過的glibc,使JVM(通過sysconf系統調用)能正確獲取容器的CPU資源數。我們在一段時間內使用的就是這種方法。其優點是業務不需要感知,並且能自動適配不同配置的容器。缺點是必須使用改過的glibc,有一定的升級維護成本,如果使用的鏡像是原生的glibc,問題也仍然存在。
  • 我們在新平臺上通過對內核的改進,實現了容器中能獲取正確CPU資源數,做到了對業務、鏡像和編程語言都透明(類似問題也可能影響OpenMP、Node.js等應用的性能)。

有一段時間,我們的容器是使用root權限進行運行,實現的方法是在docker run的時候加入‘privileged=true’參數。這種粗放的使用方式,使容器能夠看到所在服務器上所有容器的磁盤,導致了安全問題和性能問題。安全問題很好理解,爲什麼會導致性能問題呢?可以試想一下,每個容器都做一次磁盤狀態掃描的場景。當然,權限過大的問題還體現在可以隨意進行mount操作,可以隨意的修改NTP時間等等。

在新版本中,我們去掉了容器的root權限,發現有一些副作用,比如導致一些系統調用失敗。我們默認給容器額外增加了sys_ptrace和sys_admin兩個權限,讓容器可以運行GDB和更改主機名。如果有特例容器需要更多的權限,可以在我們的平臺上按服務粒度進行配置。

Linux有兩種IO:Direct IO和Buffered IO。Direct IO直接寫磁盤,Duffered IO會先寫到緩存再寫磁盤,大部分場景下都是Buffered IO。

我們使用的Linux內核3.X,社區版本中所有容器Buffer IO共享一個內核緩存,並且緩存不隔離,沒有速率限制,導致高IO容器很容易影響同主機上的其他容器。Buffer IO緩存隔離和限速在Linux 4.X裏通過Cgroup V2實現,有了明顯的改進,我們還借鑑了Cgroup V2的思想,在我們的Linux 3.10內核實現了相同的功能:每個容器根據自己的內存配置有對應比例的IO Cache,Cache的數據寫到磁盤的速率受容器Cgroup IO配置的限制。

Docker本身支持較多對容器的Cgroup資源限制,但是K8s調用Docker時可以傳遞的參數較少,爲了降低容器間的互相影響,我們基於服務畫像的資源分配,對不同服務的容器設定不同的資源限制。除了常見的CPU、內存外,還有IO的限制、ulimit限制、PID限制等等,所以我們擴展了K8s來完成這些工作。

業務在使用容器的過程中產生core dump文件是常見的事。比如C/C++程序內存訪問越界,或者系統OOM時,系統選擇佔用內存多的進程殺死,默認都會生成一個core dump文件。

社區容器系統默認的core dump文件會生成在宿主機上。由於一些core dump文件比較大,比如JVM的core dump通常是幾個GB,或者有些存在Bug的程序,其頻發的core dump,很容易快速寫滿宿主機的存儲,並且會導致高磁盤IO,也會影響到其他容器。還有一個問題是:業務容器的使用者沒有權限訪問宿主機,從而拿不到dump文件進行下一步的分析。

爲此,我們對core dump的流程進行了修改,讓dump文件寫到容器自身的文件系統中,並且使用容器自己的Cgroup IO吞吐限制。

穩定性

我們在實踐中發現,影響系統穩定性的主要是Linux Kernel和Docker。雖然它們本身是很可靠的系統軟件,但是在大規模、高強度的場景中,還是會存在一些Bug。這也從側面說明,我們國內互聯網公司在應用規模和應用複雜度層面也屬於全球領先。

在內核方面,美團發現了Kernel 4.x Buffer IO限制的實現問題,得到了社區的確認和修復。我們還跟進了一系列CentOS的Ext4補丁,解決了一段時間內進程頻繁卡死的問題。

我們碰到了兩個比較關鍵的Red Hat版Docker穩定性問題:

  • 在Docker服務重啓以後,Docker exec無法進入容器,這個問題比較複雜。在解決之前我們用nsenter來代替Docker exec並積極反饋給Red Hat。後來Red Hat在今年初的一個更新解決了這個問題。https://access.redhat.com/errata/RHBA-2017:1620
  • 是在特定條件下Docker Daemon會Panic,導致容器無法刪除。經過我們自己Debug,並對比最新的代碼,發現問題已經在Docker upstream中得到解決,反饋給Red Hat也很快得到了解決。https://github.com/projectatomic/containerd/issues/2

面對系統內核、Docker、K8S這些開源社區的系統軟件,存在一種觀點是:我們不需要自己分析問題,只需要拿社區的最新更新就行了。但是我們並不認同,我們認爲技術團隊自身的能力很重要,主要是如下原因:

  • 美團的應用規模大、場景複雜,很多問題也許很多企業都沒有遇到過,不能被動的等別人來解答。
  • 對於一些實際的業務問題或者需求(例如容器內正確返回CPU數目),社區也許覺得不重要,或者不是正確的理念,可能就不會解決。
  • 社區很多時候只在Upstream解決問題,而Upstream通常不穩定,即使有Backport到我們正在使用的版本,排期也很難進行保障。
  • 社區會發布很多補丁,通常描述都比較晦澀難懂,如果沒有對問題的深刻理解,很難把遇到的實際問題和一系列補丁聯繫起來。
  • 對於一些複雜問題,社區的解決方案不一定適用於我們自身的實際場景,我們需要自身有能力進行判斷和取捨。

美團在解決開源系統問題時,一般會經歷五個階段:自己深挖、研發解決、關注社區、和社區交互,最後貢獻給社區。

性能

容器平臺性能,主要包括兩個方面性能:

  • 業務服務運行在容器上的性能。
  • 容器操作(創建、刪除等等)的性能。

上圖是我們CPU分配的一個例子,我們採用的主流服務器是兩路24核服務器,包含兩個Node,每個12核,算上超線程共48顆邏輯CPU。屬於典型的NUMA(非一致訪存)架構:系統中每個Node有自己的內存,Node內的CPU訪問自己的內存的速度,比訪問另一個Node內存的速度快很多(差一倍左右)。

過去我們曾經遇到過網絡中斷集中到CPU0上的問題,在大流量下可能導致網絡延時增加甚至丟包。爲保證網絡處理能力,我們從Node0上劃出了8顆邏輯CPU用來專門處理網絡中斷和宿主機系統上的任務,例如鏡像解壓這類高CPU的工作,這8顆邏輯CPU不運行任何容器的Workload。

在容器調度方面,我們的容器CPU分配儘量不跨Node,實踐證明跨Node訪問內存對應用性能的影響比較大。在一些計算密集型的場景下,容器分配在Node內部會提升30%以上的吞吐量。當然,按Node的分配方案也存在一定的弊端:會導致CPU的碎片增加,爲了更高效地利用CPU資源,在實際系統中,我們會根據服務畫像的信息,分配一些對CPU不敏感的服務容器跨Node使用CPU資源。

上圖是一個真實的服務在CPU分配優化前後,響應延時的TP指標線對比。可以看到TP999線下降了一個數量級,並且所有的指標都更加平穩。

性能優化:文件系統

針對文件系統的性能優化,第一步是選型,根據統計到的應用讀寫特徵,我們選擇了Ext4文件系統(超過85%的文件讀寫是對小於1M文件的操作)。

Ext4文件系統有三種日誌模式:

  • Journal:寫數據前等待Metadata和數據的日誌落盤。
  • Ordered:只記錄Metadata的日誌,寫Metadata日誌前確保數據已經落盤。
  • Writeback:僅記錄Metadata日誌,不保證數據比Metadata先落盤。

我們選擇了Writeback模式(默認是oderded),它在幾種掛載模式中速度最快,缺點是:發生故障時數據不好恢復。我們大部分容器處於無狀態,故障時在別的機器上再拉起一臺即可。因此我們在性能和穩定性中,選擇了性能。容器內部給應用提供可選的基於內存的文件系統tmpfs,可以提升有大量臨時文件讀寫的服務性能。

如上圖所示,在美團內部創建一個虛擬機至少經歷三步,平均時間超過300秒。使用鏡像創建容器平均時間23秒。容器的靈活、快速得到了顯著的體現。

容器擴容23秒的平均時間包含了各個部分的優化,如擴容鏈路優化、鏡像分發優化、初始化和業務拉起優化等等。接下來,本文主要介紹一下我們做的鏡像分發和解壓相關的優化。

上圖是美團容器鏡像管理的總體架構,其特點如下:

  • 存在多個Site。
  • 支持跨Site的鏡像同步,根據鏡像的標籤確定是否需要跨Site同步。
  • 每個Site有鏡像備份。
  • 每個Site內部有實現鏡像分發的P2P網絡。

鏡像分發是影響容器擴容時長的一個重要環節。

  • 跨Site同步:保證服務器總能從就近的鏡像倉庫拉取到擴容用的鏡像,減少拉取時間,降低跨Site帶寬消耗。
  • 基礎鏡像預分發:美團的基礎鏡像是構建業務鏡像的公共鏡像,通常有幾百兆的大小。業務鏡像層是業務的應用代碼,通常比基礎鏡像小很多。在容器擴容的時候如果基礎鏡像已經在本地,只需要拉取業務鏡像的部分,可以明顯地加快擴容速度。爲達到這樣的效果,我們會把基礎鏡像事先分發到所有的服務器上。
  • P2P鏡像分發:基礎鏡像預分發在有些場景會導致上千個服務器同時從鏡像倉庫拉取鏡像,對鏡像倉庫服務和帶寬帶來很大的壓力。因此我們開發了鏡像P2P分發的功能,服務器不僅能從鏡像倉庫中拉取鏡像,還能從其他服務器上獲取鏡像的分片。

從上圖可以看出,隨着分發服務器數目的增加,原有分發時間也快速增加,而P2P鏡像分發時間基本上保持穩定。

Docker的鏡像拉取是一個並行下載,串行解壓的過程,爲了提升解壓的速度,我們美團也做了一些優化工作。

對於單個層的解壓,我們使用並行解壓算法替換Docker默認的串行解壓算法,實現上是使用pgzip替換gzip。

Docker的鏡像具有分層結構,對鏡像層的合併是一個“解壓一層合併一層,再解壓一層,再合併一層”的串行操作。實際上只有合併是需要串行的,解壓可以並行起來。我們把多層的解壓改成並行,解壓出的數據先放在臨時存儲空間,最後根據層之間的依賴進行串行合併。前面的改動(並行解壓所有的層到臨時空間)導致磁盤IO的次數增加了近一倍,也會導致解壓過程不夠快。於是,我們使用基於內存的Ramdisk來存儲解壓出來的臨時文件,減輕了額外文件寫帶來的開銷。做了上面這些工作以後,我們又發現,容器的分層也會影響下載加解壓的時間。上圖是我們簡單測試的結果:無論對於怎麼分層的鏡像並行解壓,都能大幅提升解壓時間,對於層數多的鏡像提升更加明顯。

推廣

推廣容器的第一步是能說出容器的優勢,我們認爲容器有如下優勢:

  • 輕量級:容器小、快,能夠實現秒級啓動。
  • 應用分發:容器使用鏡像分發,開發測試容器和部署容器配置完全一致。
  • 彈性:可以根據CPU、內存等資源使用或者QPS、延時等業務指標快速擴容容器,提升服務能力。

這三個特性的組合,可以給業務帶來更大的靈活度和更低的計算成本。

因爲容器平臺本身是一個技術產品,它的客戶是各個業務的RD團隊,因此我們需要考慮下面一些因素:

  • 產品優勢:推廣容器平臺從某種程度上講,自身是一個ToB的業務,首先要有好的產品,它相對於以前的解決方案(虛擬機)存在很多優勢。
  • 和已有系統打通:這個產品要能和客戶現有的系統很好的進行集成,而不是讓客戶推翻所有的系統重新再來。
  • 原生應用的開發平臺、工具:這個產品要易於使用,要有配合工作的工具鏈。
  • 虛擬機到容器的平滑遷移:最好能提供從原有方案到新產品的遷移方案,並且容易實施。
  • 與應用RD緊密配合:要提供良好的客戶支持,(即使有些問題不是這個產品導致的也要積極幫忙解決)。
  • 資源傾斜:從戰略層面支持顛覆性新技術:資源上向容器平臺傾斜,沒有足夠的理由,儘量不給配置虛擬機資源。

原文地址:https://note.youdao.com/share/?id=08d7c57b04dda159c53155b00cbbe5cb&type=note#/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章