1. 程式人生 > >美團容器平臺架構及容器技術實踐_Kubernetes中文社群

美團容器平臺架構及容器技術實踐_Kubernetes中文社群

本文根據美團基礎架構部/容器研發中心技術總監歐陽堅在2018 QCon(全球軟體開發大會)上的演講內容整理而成。

背景

美團的容器叢集管理平臺叫做HULK。漫威動畫裡的HULK在發怒時會變成“綠巨人”,它的這個特性和容器的“彈性伸縮”很像,所以我們給這個平臺起名為HULK。貌似有一些公司的容器平臺也叫這個名字,純屬巧合。

2016年,美團開始使用容器,當時美團已經具備一定的規模,在使用容器之前就已經存在的各種系統,包括CMDB、服務治理、監控告警、釋出平臺等等。我們在探索容器技術時,很難放棄原有的資產。所以容器化的第一步,就是打通容器的生命週期和這些平臺的互動,例如容器的申請/建立、刪除/釋放、釋出、遷移等等。然後我們又驗證了容器的可行性,證實容器可以作為線上核心業務的執行環境。

2018年,經過兩年的運營和實踐探索,我們對容器平臺進行了一次升級,這就是容器叢集管理平臺HULK 2.0。

  • 把基於OpenStack的排程系統升級成容器編排領域的事實標準Kubernetes(以後簡稱K8s)。
  • 提供了更豐富可靠的容器彈性策略。
  • 針對之前在基礎系統上碰到的一些問題,進行了優化和打磨。

美團的容器使用狀況是:目前線上業務已經超過3000個服務,容器例項數超過30000個,很多大併發、低延時要求的核心鏈路服務,已經穩定地執行在HULK之上。本文主要介紹我們在容器技術上的一些實踐,屬於基礎系統優化和打磨。

美團容器平臺的基本架構

首先介紹一下美團容器平臺的基礎架構,相信各家的容器平臺架構大體都差不多。

首先,容器平臺對外對接服務治理、釋出平臺、CMDB、監控告警等等系統。通過和這些系統打通,容器實現了和虛擬機器基本一致的使用體驗。研發人員在使用容器時,可以和使用VM一樣,不需要改變原來的使用習慣。

此外,容器提供彈性擴容能力,能根據一定的彈性策略動態增加和減少服務的容器節點數,從而動態地調整服務處理能力。這裡還有個特殊的模組——“服務畫像”,它的主要功能是通過對服務容器例項執行指標的蒐集和統計,更好的完成排程容器、優化資源分配。比如可以根據某服務的容器例項的CPU、記憶體、IO等使用情況,來分辨這個服務屬於計算密集型還是IO密集型服務,在排程時儘量把互補的容器放在一起。再比如,我們可以知道某個服務的每個容器例項在執行時會有大概500個程序,我們就會在建立容器時,給該容器加上一個合理的程序數限制(比如最大1000個程序),從而避免容器在出現問題時,佔用過多的系統資源。如果這個服務的容器在執行時,突然申請建立20000個程序,我們有理由相信是業務容器遇到了Bug,通過之前的資源約束對容器進行限制,併發出告警,通知業務及時進行處理。

往下一層是“容器編排”和“映象管理”。容器編排解決容器動態例項的問題,包括容器何時被建立、建立到哪個位置、何時被刪除等等。映象管理解決容器靜態例項的問題,包括容器映象應該如何構建、如何分發、分發的位置等等。

最下層是我們的容器執行時,美團使用主流的Linux+Docker容器方案,HULK Agent是我們在伺服器上的管理代理程式。

把前面的“容器執行時”具體展開,可以看到這張架構圖,按照從下到上的順序介紹:

  • 最下層是CPU、記憶體、磁碟、網路這些基礎物理資源。
  • 往上一層,我們使用的是CentOS7作為宿主機作業系統,Linux核心的版本是3.10。我們在CentOS發行版預設核心的基礎上,加入一些美團為容器場景研發的新特性,同時為高併發、低延時的服務型業務做了一些核心引數的優化。
  • 再往上一層,我們使用的是CentOS發行版裡自帶的Docker,當前的版本是1.13,同樣,加入了一些我們自己的特性和增強。HULK Agent是我們自己開發的主機管理Agent,在宿主機上管理Agent。Falcon Agent同時存在於宿主機和容器內部,它的作用是收集宿主機和容器的各種基礎監控指標,上報給後臺和監控平臺。
  • 最上一層是容器本身。我們現在主要支援CentOS 6和CentOS 7兩種容器。在CentOS 6中有一個container init程序,它是我們開發容器內部的1號程序,作用是初始化容器和拉起業務程序。在CentOS 7中,我們使用了系統自帶的systemd作為容器中的1號程序。我們的容器支援各種主流程式語言,包括Java、Python、Node.js、C/C++等等。在語言層之上是各種代理服務,包括服務治理的Agent、日誌Agent、加密Agent等等。同時,我們的容器也支援美團內部的一些業務環境,例如set資訊、泳道資訊等,配合服務治理體系,可以實現服務呼叫的智慧路由。

美團主要使用了CentOS系列的開源元件,因為我們認為Red Hat有很強的開源技術實力,比起直接使用開源社群的版本,我們希望Red Hat的開源版本能夠幫助解決大部分的系統問題。我們也發現,即使部署了CentOS的開源元件,仍然有可能會碰到社群和Red Hat沒有解決的問題。從某種程度上也說明,國內大型互聯公司在技術應用的場景、規模、複雜度層面已經達到了世界領先的水平,所以才會先於社群、先於Red Hat的客戶遇到這些問題。

容器遇到的一些問題

在容器技術本身,我們主要遇到了4個問題:隔離、穩定性、效能和推廣。

  • 隔離包含兩個層面:第一個問題是,容器能不能正確認識自身資源配置;第二個問題是,執行在同一臺伺服器上的容器會不會互相影響。比如某一臺容器的IO很高,就會導致同主機上的其他容器服務延時增加。
  • 穩定性:這是指在高壓力、大規模、長時間執行以後,系統功能可能會出現不穩定的問題,比如容器無法建立、刪除,因為軟體問題發生卡死、宕機等問題。
  • 效能:在虛擬化技術和容器技術比較時,大家普遍都認為容器的執行效率會更高,但是在實踐中,我們遇到了一些特例:同樣的程式碼在同樣配置的容器上,服務的吞吐量、響應時延反而不如虛擬機器。
  • 推廣:當我們把前面幾個問題基本上都解決以後,仍然可能會碰到業務不願意使用容器的情況,其中原因一部分是技術因素,例如容器接入難易程度、周邊工具、生態等都會影響使用容器的成本。推廣也不是一個純技術問題,跟公司內部的業務發展階段、技術文化、組織設定和KPI等因素都密切相關。

容器的實現

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

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

簡而言之,Linux核心提供namespace完成隔離,Cgroup完成資源限制。namespace+Cgroup構成了容器的底層技術(rootfs是容器檔案系統層技術)。

美團的解法、改進和優化

隔離

之前一直和虛擬機器打交道,但直到用上容器,才發現在容器裡面看到的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直接寫磁碟,Buffered 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並積極反饋給RedHat。後來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緊密配合:要提供良好的客戶支援,(即使有些問題不是這個產品導致的也要積極幫忙解決)。
  • 資源傾斜:從戰略層面支援顛覆性新技術:資源上向容器平臺傾斜,沒有足夠的理由,儘量不給配置虛擬機器資源。

總結

Docker容器加Kubernetes編排是當前容器雲的主流實踐之一,美團容器叢集管理平臺HULK也採用了這樣的方案。本文主要分享了美團在容器技術上做的一些探索和實踐。內容主要涵蓋美團容器雲在Linux Kernel、Docker和Kubernetes層面做的一些優化工作,以及美團內部推動容器化程序的一些思考,歡迎大家跟我們交流、探討。

作者簡介

歐陽堅,2006年畢業於清華大學計算機系,擁有12年資料中心開發管理經驗。曾任VMware中國Staff Engineer,無雙科技CTO,中科睿光首席架構師。現任美團基礎架構部/容器研發中心技術總監,負責美團容器化的相關工作。

招聘資訊

美團點評基礎架構團隊誠招Java高階、資深技術專家,Base北京、上海。我們是集團致力於研發公司級、業界領先基礎架構元件的核心團隊,涵蓋分散式監控、服務治理、高效能通訊、訊息中介軟體、基礎儲存、容器化、叢集排程等技術領域。歡迎有興趣的同學投送簡歷到 [email protected]

原文連結:https://tech.meituan.com/docker_architecture_and_evolution_practice.html