Docker背后的容器集群管理——從Borg到Kubernetes(二)

jopen 9年前發布 | 37K 次閱讀 Docker

 

在本系列的第一部分《 Docker背后的容器集群管理——從Borg到Kubernetes(一) 》中,我們對Borg系統進行了深入的剖析,并且同它的衍生項目Kubernetes進行了逐一地比較。這一部分對比包括了Borg和 Kubernetes的各類核心概念、任務類型劃分、資源管理和分配方式、配額和優先級機制,以及最關鍵的調度策略。這些分析涵蓋了原論文前四章的主要內容。

從這些比較中我們不難發現,雖然Kubernetes與Borg有很多相似的地方,但是在很多關鍵特性上Kubernetes明顯進行了重新設計。而Borg的作者之所以要這么做,最主要的原因是除了任務容器的編排調度和管理之外,Borg需要比Kubernetes更加關注這樣一個事情:如何最大程度地提高集群的資源利用率?

注:本文作者張磊將在8月28日~29日的CNUT全球容器技術峰會上分享題為《 從0到1:Kubernetes實戰 》的演講,演講中他將重點剖析Kubernetes的核心原理和實踐經驗,并分享大規模容器集群管理所面臨的問題和解決思路。

1. 利用率

所以,本節討論的集群利用率優化可以說是Borg的精華所在,也是Borg系統與其他類似項目相比最大的亮點之一。那么Borg的具體做法是怎樣的呢?

如果用一句話來解釋,Borg做了最主要工作就是來回收再利用任務的空閑資源,并且對不同的回收策略做出了科學的評估。

所以我們接下來先從這個評估方法入手。

1.1 利用率的評估方法

前面已經提到過,Borg中進行任務的調度既需要考慮機器的資源可用性(包括搶占),也要考慮任務本身的約束要求(比如"我需要SSD機器"),還需要考慮應對壓力峰值所必需的空余量。而在本節,我們還要為調度再加上一條規則,那就是對于batch job來說,它們還需要能夠利用從LRS任務上回收來的資源。這種情況下,調度過程中的資源搶占和回收機制帶來了一個負面影響:Borg很難快速而精確地回答"某個機器/集群到底還有多少資源可用"這樣的問題。

可是,為了能夠評價不同的調度算法,Borg必須能夠評估集群的資源使用情況。很多情況下,運維人員會計算一個一段時間內的集群"平均利用率"來作為評價指標,但是Borg則使用了一個叫 "壓縮實驗" 的方法。所謂壓縮實驗,即不斷減少工作單元(Cell)中機器的數量("壓縮"),然后重調度某個指定的任務,直到該任務再也不能正常調度運行在這個集群上。這個過程不斷進行的最終結果,得到就是這個任務運行所需的"最小工作單元"的定義。

這個指標清楚地表明了當資源減少到什么程度時,我們才可以終止這個任務,并且這個生成指標的過程不需要提交任何『模擬』任務,這樣得到的結果要精確很多。

當然,上述『壓縮』的過程自然不能在生產環境中開展。這時我們前面提到的模擬器:Fauxmaster的作用就發揮出來了。Borg可以加載某時刻的checkpoints,從而在Fauxmaster上重現出與當時一模一樣的環境(除了Borglet是模擬的之外)。

在具體進行壓縮的過程中,Borg還使用了以下幾個小技巧:

  • 隨機選擇機器來移除。
  • 對每一個任務進行上述壓縮實驗(除了某些跟機器緊密綁定的存儲型任務)。
  • 當Cell的大小減小到一定程度時(比如剩余資源只有Job所需資源的二倍),將硬性約束改為非硬性約束,從而適當增加調度成功的幾率。
  • 允許最多0.2%的”挑剔型“任務掛起,因為它們每次都會被調度到一小撮機器上。
  • 如果某次壓縮實驗需要更大的Cell,管理員可以將原Cell克隆幾份再開始“壓縮”的過程。
  • 上述壓縮實驗會在每一個Cell中重復11次,每一次都會選擇一個不同的隨機數種子來移除機器。
  • </ul>

    所以,通過上述壓縮實驗,Borg提供了一種直觀的、可以用來評估不同調度策略優劣的測試方法。即: 調度策略越優秀,對于同一個任務來說它最后得到的最小工作單元中所需的機器數就越少

    不難發現,Borg對資源使用進行評估的方法關注的是一個任務在某一具體時刻運行起來所需的最少的資源成本,而不是像傳統做法那樣去模擬并重現一個調度算法的執行過程,然后跟蹤檢查一段時間內集群負載等資源指標的變化情況。

    當然,在生產環境中,Borg并不會真的把資源『壓縮』到這么緊,而是給應對各類突發事件留有足夠余量的。

    1.2 Cell的共享

    Borg進行高效的集群管理最直接的一個優化方法就是任務的混部。這里Borg進行了一項對比實驗,即把LRS和batch job分別部署到不同的集群中,結果同樣的硬件和任務條件下我們需要比Borg多20%-30%的機器。造成這種情況的關鍵原因是:prod級別的任務(即大多數的LRS)實際使用的資源比它申請的資源要少很多,而Borg會回收這部分資源來運行non-prod級別的任務(比如大多數batch job)。

    另一方面,不僅是不同類型的任務在Borg中混合部署,不同用戶的任務也是混合部署的。Borg的實驗表明,如果在一定條件下(比如此Cell上任務所屬的不同用戶數量達到一定值)自動將不同用戶的任務隔離到不同Cell上,那么Borg將需要比現在多20-150%的機器才能滿足正常的運行要求。

    看到這里,相信很多讀者也會產生同我一樣的疑問:把不同類型、不同用戶的任務混合在一臺機器上運行,會不會造成CPU時間片的頻繁切換而降低系統性能呢?為了回答這個問題,Borg專門以CPI(cycles per instruction,每條指令所需的時鐘周期)的變化為指標對任務與CPI之間的關系做出了評估。

    這里Borg使用的評估方法是:將Cell分為『共享的Cell』(混部)和『獨享的Cell』(不混部),然后從所有Cell中隨機選擇 12000個prod級別的任務,然后再這些任務中進行為期一周的持續采樣。每次采樣間隔5分鐘,每次采樣都統計被選中任務的CPU時鐘周期和指令數目。這個過程最后得出的結論包括:

    一、CPI數值的變化與兩個變量的變化正相關:機器本身的CPU使用量,機器上的任務數量。任務數量對CPI的影響更大:在一臺機器上每增加一個任務,就會將這臺機器上其他任務的CPI增加0.3%;而機器CPU使用量每增加1%,指令CPI的增長只有0.2%左右。但是,盡管存在上述關系,實際生產環境中Borg只有很小一部分CPI變化是由于上述兩個變量的改變造成的,更多CPI變化的誘因是應用本身實現上(比如算法,數據庫連接等)對CPU 的影響。

    二、共享的Cell與獨享的Cell相比,有大約3%的CPU性能損失。這個結論是通過比較這兩類Cell上的采樣結果得到的,也表明了混部策略確實會一定程度上降低CPU的表現。

    三、為了避免(二)中的結論受到應用本身差異行的干擾(比如被采樣任務本身就是CPU敏感的),Borg專門對Borglet進程做了CPI采樣:因為這個進程在整個集群中完全同質,并且同時分布在獨享Cell和共享Cell的所有機器上。這時,測試得出的結論是獨享Cell上的Borglet 進程的CPU表現要比共享Cell上好1.19倍。

    綜上,通過上述系統地測試,Borg確認了 共享Cell會給任務帶來CPU上的性能損失 ,但是相比任務混部所帶來的機器數量的大幅節省,上述CPU損失還是很可以接受的。更何況,機器數量減少不僅僅節省了CPU,還節省了大量內存和磁盤的成本,與這些相比,任務混部在CPU上造成的一點浪費確實微不足道。

    1.3 使用更大的Cell

    Borg還做了一個很有意義的測試,那就是將一個大Cell中的任務分開部署到跟多小Cell中(這些任務是隨機挑選出來的)。結果表明,為了運行這些任務,小Cell需要比大Cell多得多的機器才能正常工作。

    1.4 細粒度的資源請求

    Borg用戶提交的任務使用一個CPU核心的千分之一作為單位來申請CPU,使用字節為單位來申請內存和磁盤,這與Kubernetes里的資源單位是一樣的。這種情況下,一個常見的資源請求描述如下所示:

    "cpu": 1000,
    "memory": 1048576,

    請求中具體需要某種資源量的大小完全由用戶決定,并且一旦該任務創建成功,上述參數會作為任務進程的cgroup參數來限制任務的資源使用情況。

    不難發現,Borg以及Kubernetes的資源請求粒度都是小而靈活的,這也是基于容器的編排管理平臺的一大特點:資源粒度直接對應到 cgroups的配置上,所以用戶可以進行非常精細的調節。在這種情況下,提供類似于『1個CPU,1G內存』這種固定的資源配額offier來供用戶選擇的做法就不夠明智了。

    事實上,這種細粒度的資源請求一方面能夠減少資源請求的聚集(比如可能90%的任務都要求『個CPU,1G內存』)所造成資源碎片化,另一方面還能有效地避免本來無關的兩種資源發生不必要的關聯(比如為了申請1個CPU,任務必須申請1G內存)。在試驗中,Borg直接展示了如果用戶使用資源配額 offer來持續向Borg請求資源(比如OpenStack就只為用戶提供了的tiny、medium、large等幾種可選的offer),Borg 所管理的集群將需要比原先多30%-50%的機器才能支撐同樣規模的任務運行。

    1.5 資源回收

    終于來到了關鍵的部分。我們前面已經不止一次提到過,Borg通過任務混部提高集群利用率的一個重要基礎就是資源的回收和重分配。

    準確來講,資源回收主要發生在任務調度的資源可行性檢查階段。舉個例子,假設一臺機器的容量是10G內存,并且已經有一個申請了4G內存的任務A 在運行,那么這臺機器的剩余可用資源就是6G,另一個申請8G內存的任務B是通不過可行性檢查的。但實際情況是,Borg會在一段時間后把任務A申請的內存改成1G,使得調度器認為這臺機器的剩余可用資源變成9G。這樣,任務B就可以調度成功并且運行在這臺機器上了,這就是說任務B使用了回收自任務A的一部分資源。這個過程具體實現是怎樣的呢?

    在Borg中,任務申請的資源limit是一個上限。這個上限是Borg用來確定一臺機器的資源是否足夠運行一個新任務的主要標準。

    既然是上限,大多數用戶在設置這個limit時都會故意把這個值設置的稍微高一點,以備不時之需。不過,大多數時候任務的資源使用率都是比較低的。為了解決這個問題,Borg做了一個非常有意義的工作,那就是先為每個任務估算它真正需要使用的資源量,然后把空閑部分資源回收給那些對資源要求不是很高的任務來使用。這部分回收資源的典型使用者就是非生產環境任務,比如batch job。

    這個估算過程由Borgmaster通過Borglet匯報的資源使用情況每隔幾秒計算一次,得出的資源使用量稱為 "資源預留" 。一個任務的資源預留在最開始是與用戶設置的limit相等的(即等于任務請求的資源量),在任務成功啟動一段時間后(300s),它的資源預留會 慢慢減少 為任務的 實際資源使用量加上一個可配置的安全余量 。另一方面,一旦任務的資源使用量突然增加并超過了上述資源預留,Borg會 迅速 增加它的資源預留到初始值來保證任務能夠正常工作。

    需要注意的是,生產級別的任務(prod級別的任務)是永遠不會使用回收而來的資源的。只有對于非prod級別的任務來說,它們在被調度的時候Borg才會把可回收資源納入到可行性檢查的范圍內,正如我們前面介紹的那樣。

    既然是估算,Borg就有可能會把過多的任務調度在一臺機器上從而造成機器的資源被完全耗盡。更糟糕的是對于任務來說,它們的實際資源使用量卻完全是處于各自limit范圍內的。在這種情況下,Borg會殺死一些非prod級別的任務來釋放資源。

    Borg內部的實驗表明如果在當前集群中禁用資源回收的話,將近一半的Cell都需要額外增加30%的機器才能支撐同樣規模的任務。而在G的生產環境中,則有近20%的任務是運行在這些回收來的資源上的。并且Borg對內部任務的統計再次驗證了:對于絕大部分任務而言,它的實際的資源使用相比它申請的資源限制來說都是很低的,將近一半機器的CPU和內存使用量不到20%。

    最后的問題是,我們應該為給任務設置多大的安全余量呢?不難想到,如果安全余量設置得很小,我們就可以實現更高的資源利用率,但是也更容易出現使用量超出資源預留的情況(此時就會OOM)。相反,如果把安全余量設置得很大,那資源利用率就不可能很高,當然OOM的情況也會減少。這里Borg給出的答案是通過實驗來得出合理的值:給定不同的安全余量值,分別觀察不同余量下資源利用率和OOM在一段時間內的的變化,然后選擇一個資源利用率和OOM出現次數的平衡點來作為整個集群的安全余量。

    2 隔離與性能

    前面說了那么多共享的內容,讀者應該可以猜到Borg的機器上運行著的任務密度應該是很高的。事實的確如此:一半以上的Borg機器上同時運行著 9個以上的任務,這其中絕大多數機器運行著約25個任務、開啟了4500個線程。共享給Borg帶來了極高的資源利用率,但是也使得這些任務間的隔離成為了一個不得不重點解決的問題。

    2.1 任務間隔離

    Borg最早是直接使用chroot和cgroup來提供任務間的隔離和約束,并且允許用戶通過ssh登陸到這些隔離環境中來進行調試。后期這個 ssh登陸的辦法被替換成了borgssh指令,即通過Borglet來負責維護一個用戶到隔離環境內shell的ssh連接。需要注意的是,來自外部用戶的應用是運行在GAE或者GCE上的,而不是直接作為Borg任務來跑的。其中GAE作為PaaS主要使用的是砂箱技術來支撐和隔離用戶的應用,而 GCE作為IaaS自然使用的是VM技術。值得一提的是無論GAE還是GCE,它們所需的KVM進程則是作為job運行在Borg上的。

    Borg的早期階段對于資源限制做的還是比較原始的,它只在任務調度成功后對內存、磁盤和CPU的使用情況進行持續的檢查,然后依據檢查結果殺死那些使用了太多資源的任務。在這種策略下,Borg任務間的資源競爭是很普遍的,以至于有些用戶故意將任務的資源請求設置的很大,以期盡量減少機器上同時運行的任務數,這當然大大降低了資源利用率。

    所以,很快Borg就開始使用Linux容器技術來解決限制與隔離的問題。G家內部的容器技術有一個對應的開源項目,這就是曾經名噪一時的lmctfy容器( https://github.com/google/lmctfy )。確切地說,lmctfy給用戶提供了一個可以方便地配置cgroup的工具,并能夠把用戶的這些cgroup配置結合namespace創建出一個任務隔離環境即『容器』出來。由于lmctfy從一開始就是從限制與隔離的角度來開發的,所以它的資源操作接口定義地很豐富,不僅涵蓋了cgroup的大部分子系統,還可以進行嵌套等比較復雜的資源管理。但是,隨著Docker容器鏡像這一殺手級特性的普及以及Docker本身飛快的演化,lmctfy的作者們也不得不放棄了該項目的維護,轉而開始去貢獻libcotainer項目。不過至于現在G家內部,應該還是在使用類似于lmcty這種自研的容器技術棧,而Docker則主要用作對外提供的公有云服務(Google Container Engine)。

    當然,容器也不是萬能的,一些底層的資源共享問題比如內存帶寬的共享或者CPU緩存污染問題在Borg中仍然存在,但是至少在任務的運行資源的限制和調度優化上,上述容器技術已經足夠了。

    2.2 性能優化

    我們前面提到過,Borg中的任務是分為LRS(也可以稱為Latency Sensitive任務)和batch job的,其中前者因為對于訪問延時敏感所以可以享受更好的『待遇』,這個劃分是Borg進行資源回收和在分配的基礎。但是還有個待解決的問題是,哪種資源是可以在不影響任務運行的前提下進行回收的呢?

    凡是能夠進行熱回收的資源在Borg中都稱為可壓縮資源,典型的例子是CPU周期和磁盤I/O帶寬。與之相反,內存和磁盤空間這種就屬于不可壓縮資源。當一臺機器上不可壓縮資源不夠用時,Borglet就不得不按照優先級從低到高殺死任務,直到剩余任務的資源預留能夠得到滿足。而如果是可壓縮資源不足,Borg就能夠從某些任務上面回收一些資源(一般從LRS任務上,因為它們申請的資源一般都比實際使用多一些)而不需要殺死任何任務。如果回收資源仍然解決不了問題,那么Borg才會通過Borgmaster將一些任務從這個機器上調度走。

    具體到Borglet的實現上同樣體現了對資源的限制和管理。Borglet維護了一個內存資源檢測循環,它負責在調度時按照資源使用預測的結果(對于prod級別任務),或者按照當前內存使用情況(對于非prod級別任務)為容器設置內存限額。同時,這個循環還負責檢測OOM事件,一旦發現有任務試圖使用比限額更多的內存時Borglet將殺死它們。而前面剛剛提到的不可壓縮資源(內存)不足時的處理也是這個循環完成的、

    為了讓LRS任務獲得更好的性能,LRS可以完全預留某些或者所有CPU核心,并且不允許其他的LRS任務運行在這些CPU核心上。另一方面,batch job則不受此限制,它們可以隨時運行在任意CPU核心上,但是在CPU調度上batch job會被分配更小的配額,即它們能占有CPU的時間要比LRS少。

    不難看出,為了能夠更加高效的使用CPU資源,Borg就必須引入更加復雜的任務調度策略,這也就意味著調度過程會占用更多的時間。所以,Borg的開發者對CPU調度做了優化以期在最小的時間代價里完成調度過程。這些優化包括在內核3.8引入的以進程或者進程組為單位的新的CPU負載計算方法(per-entity load tracking),在調度算法中允許LRS搶占batch job的資源,以及減少多個LRS共享同一CPU時的算法的執行次數。

    上述優化的結果使得絕大多數任務都能夠在5ms內獲得CPU時間片,這比默認的CFS高效很多。不過在一些極端情況下(比如任務對調度時延非常敏感時),Borg會使用cpuset來直接為任務分配獨享的CPU核心。不過,這種需要設置cpuset的情況非常少見,而且Borg的作者不止一次告誡容器的使用者不要同時設置cpu.shares和cpuset:因為這會給后續系統的CPU超賣,auto-scaling,統一資源單位的抽象等設計帶來很多麻煩。這其實很容易理解:cpu.shares是一個相對值,隨著任務(容器)的增加每個容器真正享有的時間片數量是會不斷變化的,而在一個任務必須和某個CPU綁定的前提下,每個任務到底能分配到多少時間片這種問題就要變得復雜很多。這也是為什么Kubernetes暫不支持用戶設置cpuset 的一個主要原因。

    雖然Borg任務能夠使用的資源取決于它們的limit,其實大多數任務的可壓縮資源比如CPU是可以超賣的,即它們可以使用超出限額的可壓縮資源(比如CPU)從而更好地利用機器的空閑CPU。只有5%的LRS和不到1%的batch Job會禁止超賣以期獲得更精確的資源使用預測。

    與CPU相反,Borg中內存的超賣默認是禁止的,因為這會大大提高任務被殺死的幾率。但即使如此。仍然有10%的LRS和79%的batch job會開啟內存超賣,并且內存超賣對于MapReduce任務來說默認就是開啟的。這也正對應了5.5中Borg關于資源回收的設計:batch job更傾向于使用回收來的資源并且驅使系統進行資源回收。

    大多數情況下以上策略都能夠有助于資源利用率的提高,但是這也使得batch job不得不在某些情況下犧牲自己來給迫切需要運行資源的LRS任務讓路:畢竟batch job使用的資源大多是從別人那里回收來的。

    與Borg相比,Kubernetes的資源模型還在開發的過程中,當前能夠支持的類型也只有CPU和內存兩種,其他類似于磁盤空間、IOPS、存儲時間和網絡帶寬等都還處于概念設計階段。出于跨平臺的考慮,Kubernetes的CPU資源的度量會被換算成一個內部統一的KCU單位,比如x86 上KCU就等于CPU時間(類似Borg)。同Borg一樣,Kubernetes中的CPU也是可壓縮資源,而內存就是不可壓縮資源了:雖然我們隨時可以通過cgroup增加Docker容器的內存限制,但是只有這些page被釋放之后進程才可以使用它們,否則仍然會發生OOM。

    總結

    Borg目前披露的細節就是這么多了,從這些內容上,我們不難看出作為Google超大規模任務容器集群的支撐平臺,Borg對于資源混部與集群利用率的優化工作可以說是整篇文章的精髓所在。另外,盡管作者在最后聲稱后續工作主要是Kubernetes的研發和升級,但是我們絕不能說 Kubernetes就是開源版的Borg。

    盡管很多概念包括架構、甚至一些實現方法都借鑒了Borg,開發者也基本上是同一撥人,但是Kubernetes與Borg關注的問題存在著根本的差異。

    Kubernetes的核心是Pod,然后圍繞Pod,Kubernetes扮演了一個Pod集群編排的角色,在此基礎上它又為這些Pod集群提供了副本管理(ReplicationController)和訪問代理(Service)的能力。所以,Kubernetes實際上更貼近Swarm這樣的Docker容器集群管理工具,只不過它的管理單位是Pod。這樣的定位與Borg專注于支撐內部任務并且最大程度地提高資源利用率的初衷是不一樣的。很難想像會有人把KVM或者MapReduce Job跑在Kubernetes上,而這些卻是Borg的典型任務。

    相比之下,Mesos關注的核心問題是集群環境下任務資源的分配和調度,這與Borg的一部分目標很接近。但是,兩層調度策略決定了Mesos本身的實現是十分輕量的,它的大部分工作在于如何在兼顧公平和效率的情況下向上層框架提供合理的資源邀約,至于對運行其上的任務本身Mesos的關注并不算多(交給上層framework負責即可)。

    那么有沒有一個開源項目能夠配上開源版Borg的稱號呢?目前看來是:Aurora+Mesos。

    Apache Aurora項目在Mesos的基礎上提供了很多非常有意思的特性,這其中就包括了同時運行batch job和普通應用,任務優先級和資源搶占的能力。當然,從時間點上來看很可能是Aurora借鑒了Borg的思想,但最關鍵的是,Aurora把集群利用率的提高當作了該項目的主要目標之一,這才使得它與Borg產生了更多的交集。

    當然,至于選擇哪種,就完全是取決于用戶的需求了。從各個項目的發展上來看,集群規模不大又需要Pod概念的話,直接使用Kubernetes就足夠了,但如果是互聯網企業部署自己內部的私有云的話,還是建議先搭一層Mesos作為集群資源的抽象(已經有自己的資源編排平臺的同學除外,比如百度 Matrix這類),然后使用不同的framework(包括Aurora,Marathon以及Kubernetes本身)來滿足不同場景的需求。

    最后說一句,作為Borg的同源衍生項目,Kubernetes的Roadmap里還有很多Borg的優秀特性待實現,比如我們前面提到過的資源模型,對batch job的支持等都是很好的例子。可以預見,Kubernetes接下來很有可能會發展成為一個『大量借鑒Borg優秀思想、架構、以及代碼實現』的一個加強版Swarm,這本身確實很讓人期待,這也是目前Kubernetes在用戶眼中可能要比Mesos稍勝一籌的原因之一。更何況,Google已經與 Mesos達成了戰略合作,Kubernetes和Mesos今后的分工會更加明確,重疊功能會更少。這也意味著在核心功能足夠完善前,Kubernetes對資源利用率提升等問題上的重視程度依然不會非常高。

    無論如何,當Docker遇上Borg,這絕對是一件非常有意義的事情。在后續的文章里,我們會進一步對以Docker為核心的容器集群的編排管理、調度、監控和性能優化進行更多的深入的剖析和解讀。

    作者簡介

    張磊,浙江大學博士,科研人員, VLIS lab云計算團隊技術負責人、策劃人。

    參考文獻

    1. http://research.google.com/pubs/pub43438.html
    2. https://github.com/googlecloudplatform/kubernetes
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!