天天看點

從kubernetes看如何設計超大規模資源排程系統

背景

在大資料時代,為了合理配置設定大規模叢集的資源,滿足日益增多的服務和任務的資源需求,出現了諸如Borg,Mesos,YARN,Omega等一系列的叢集資源排程系統。從系統的架構來考慮,可以把它們劃分為集中式排程器(Borg),雙層排程器(Mesos,YARN),以及共享狀态排程器(Omega)。雖然架構不同,但是它們的設計目标(簡單合理的利用叢集資源)和主要職責(為任務配置設定主機資源)都是一樣的。

Borg是集中式排程器的代表,它作為叢集排程器的鼻祖,在google有超過10年的生産經驗,其上運作了包括GFS,BigTable,Gmail,MapReduce等各種類型的任務。Borg雖然是一個集中式的排程器,但是通過多主共同協作可以輕松支撐10K節點以上的叢集,就像Borg論文中說的那樣:We are not sure where the ultimate scalability limit to Borg’s centralized architecture will come from; so far, every time we have approached a limit, we’ve managed to eliminate it。不論是Brog的設計理念,還是其中新穎的功能特性,都是各種開源排程系統可以借鑒和努力超越的目标。

Mesos和YARN的設計理念基本相似,它們是雙層排程器的代表,尤其是Mesos,可以讓多個架構公平的共享叢集資源,已經被包括Twitter, Airbnb, Netflix在内的很多公司應用在生産環境中。雙層排程器系統包括一個輕量級的中央化排程器,以及多個應用架構自己的排程器,中央化排程器把整塊的資源配置設定給應用架構,再由應用架構排程器把資源切分後配置設定給具體的任務。通過這種分層設計,Mesos在模拟環境中可以支撐50K節點規模的叢集,YARN的目标是最高支撐100K節點規模的叢集。

Omega是共享狀态排程器的代表,它也支援使用不同的排程器排程不同類型的任務,主要解決的是資源請求沖突的問題。Omega是google試驗中的産品,沒有在實際的生産環境中使用過。從論文的實驗資料中可以看出,omega在可以支援的叢集規模、排程效率、叢集的資源使用率方面都已經趕超了Borg。

Kubernetes是google開源用來管理docker叢集的項目,使用Kubernetes可以自動部署、運作、擴縮容docker應用。Kubernetes雖然和Borg有所不同,但是繼承了Borg的設計理念,吸納了Borg的精華,被業界認為是開源版的Borg。在​​Kubernetes排程詳解​​中作者簡單介紹過Kubernetes已經實作的預設排程器,本文着重介紹一些Kubernetes在開發或準備開發的一些新特性,從這些特性中看如何設計超大規模的資源排程系統。

幾種排程系統的簡單對比圖如下所示:

排程系統流程簡介

一些開源的雲平台管理系統,如CloudStack,OpenStack等,它們的設計目标及管理的資源類型比較單一,都是使用一個中央化的排程器,根據主機上報的靜态資源量,為虛拟機配置設定主機。

Mesos和YARN的設計目标是通用的資源管理架構,它們可以支援Hadoop,Spark,S4等一系列的應用架構,使用不同的排程器排程不同類型的任務。它們把排程流程一般包括主機上報可使用的資源量,中央排程器申請資源配置設定給應用架構,應用架構再把資源配置設定給具體的任務。

上述的排程系統都是用任務申請時的資源使用情況作為輸入進行排程,沒有考慮在系統長時間運作後的資源變化對整個叢集的影響,這可能會帶來以下幾個方面的負面影響:

  • 任務申請的資源量和實際使用的資源量不比對,通常是申請量遠遠大于實際使用的資源量,導緻叢集中的大部分資源被浪費。
  • 随着任務的增加删除,在叢集中會有越來越多的資源碎片。這些資源碎片由于太小無法配置設定給任務而被浪費。
  • 叢集中的主機負載不均勻,有的主機負載很高,有的主機負載很低。

為了解決上述問題,一個完整的資源排程流程通常包括以下幾個步驟:

  • 主機上報可用資源量
  • 應用架構申請資源
  • 排程器根據請求進行排程,選擇合适的主機
  • 應用程式部署到排程的主機上
  • 采集程式定期采集應用程式及主機的性能資訊,資料發送到存儲後端
  • 分析程式分析主機及應用程式的曆史性能資料,處理後輸出可用來排程的資訊回報給排程器
  • 排程器根據真實的負載資訊進行資源調整

流程如下圖所示:

下面我們來看一下kubernetes在整個排程系統流程中的考慮。

主機的可用資源量

現在的排程器都是使用主機的容量做排程,但是主機的容量并不是主機可配置設定的資源量。比如YARN中的nodemanager上報的是主機的容量,實際上系統程式和nodemanager本身會消耗一部分的主機容量,主機可配置設定的資源量要比主機的容量少。如果用主機的容量做排程,可能會導緻主機的負載過高導緻系統不穩定的問題。

為了做更可靠的排程,盡量減少資源過量使用,kubernetes把主機的資源分為幾個部分:

  • Node Capacity:主機容量是一個固定值,是主機的實際的容量。
  • System-Reserved:不屬于kubernetes的程序占用的資源量。
  • Kubelet Allocatable:可以被kubelet用來啟動容器的資源量。
  • Kube-Reserved:被kubernetes的元件占用的資源量,包括docker daemon,kubelet,kube-proxy等。

      [Allocatable] = [Node Capacity] – [Kube-Reserved] – [System-Reserved]

kubernetes排程器在排程Pod和kubelet在部署Pod做資源校驗時都使用Allocatable資源量作為資料輸入。

任務的資源需求

如果能在任務送出的時候就預測出任務在運作期間會占用多少資源,對排程器來說會是一個重大的改進。現階段的排程器使用的任務資源量限制都是由使用者指定的固定值,這個值往往會大于實際需要的資源量,導緻資源浪費。在虛拟機時代,很難預測某個虛拟機在運作期間的資源使用量,因為同樣規格、同一模闆的虛拟機,因為其上運作的業務不同,會導緻資源使用量有較大差異。但是在容器時代,這個資源使用量是可以用曆史資料估算出來的,因為同一鏡像上跑的業務肯定是一樣的。

在這個階段,需要重點關注的問題是如何快速、準确的預測任務在運作期間會占用的資源量,減少對Pod啟動時間帶來的延遲,以及減少因Pod資源變化對主機負載帶來的影響。

Kubernetes使用已經運作在容器中的鏡像(包括名字和标簽)的曆史資料預測任務的資源使用量,容器在運作期間,會定期把CPU和MEMORY的負載資訊存儲到後端,周期預設是1分鐘。

Kubernetes的資源初始化元件按照如下順序為容器設定資源需求值:

  • 使用7天内同樣 image:tag 的容器曆史資料進行預測
  • 使用30天内同樣 image:tag 的容器曆史資料進行預測
  • 使用30天内同樣 image 的容器曆史資料進行預測

在處理過程中按照上述順序進行預測。如果沒有找到相關的曆史資料,會使用預設的LimitRange對容器進行資源限制。

多排程器支援

現階段運作在叢集中的任務可以大緻劃分為兩大類:long-running service(如Gmail)和batch job(如MapReduce),通常來說long-running service的優先級會高于batch job任務的優先級。

Kubernetes設計之初是以service為核心,其内部的概念如service,kube-proxy等都是為更好的服務long-running service而設計。在V1.2版本後,Kubernetes開始支援batch job類型的任務。排程這兩種類型的任務需要考慮的因素有所不同,比如batch job類型的任務通常是低優先級的任務,更多的是考慮如何回填主機的資源空缺,進而提高整個叢集的資源使用率;long-running類型的任務通常是高優先級的任務,更多的是考慮如果預留資源滿足自身的SLA需求。Kubernetes現在隻有一個預設的排程器,很難滿足使用不同的排程政策排程不同類型任務的需求。可喜的是Kubernetes的排程架構是plugin形式的,也就是說,任務架構可以根據自身的需求定制排程政策。

Kubernetes在支援多排程器時主要解決的問題包括:

  • 排程Pod。一個Pod隻能由一個排程器排程。可以通過在Pod定義時指定scheduler.alpha.kubernetes.io/name annotation來指定Pod要使用的排程器。
  • 解決沖突。Kubernetes對多排程器的支援方式和YARN及Mesos不同,比如Mesos,它的資源是由Mesos-Master配置設定給各個架構的,架構隻能看到Mesos允許它使用的那些資源,而且Mesos會保證同一份資源不會同時配置設定給多個架構,是以不存在資源沖突的問題。Kubernetes的實作方式更類似于Omega,各個排程器都可以看到叢集中的所有資源,當兩個排程器同時把Pod排程到同一個主機上時就可能會出現資源沖突。Kubernetes的解決方案是當有沖突發生時,由Kubelet把受影響的Pod重新傳回給排程器,等待排程器重新排程。
  • 除此之外,怎麼樣動态加載排程器也是一個需要重點關注的問題,這其中會涉及admission等一系列問題。

多排程政策支援

Kubernetes通過一組規則,為每一個未排程的Pod選擇一個主機。其過程包括主機篩選和主機打分兩個階段。 主機篩選的目的是過濾掉不符合Pod要求的主機,現在kubernetes中實作的過濾規則主要包括以下幾種:

  • NoDiskConflict:檢查在此主機上是否存在卷沖突。如果這個主機已經挂載了卷,其它同樣使用這個卷的Pod不能排程到這個主機上。GCE,Amazon EBS和Ceph RBD使用的規則如下:
  • GCE允許同時挂載多個卷,隻要這些卷都是隻讀的。
  • Amazon EBS不允許不同的Pod挂載同一個卷。
  • Ceph RBD不允許任何兩個pods分享相同的monitor,match pool和 image。
  • NoVolumeZoneConflict:檢查給定的zone限制前提下,檢查如果在此主機上部署Pod是否存在卷沖突。假定一些volumes可能有zone排程限制, VolumeZonePredicate根據volumes自身需求來評估pod是否滿足條件。必要條件就是任何volumes的zone-labels必須與節點上的zone-labels完全比對。節點上可以有多個zone-labels的限制(比如一個假設的複制卷可能會允許進行區域範圍内的通路)。目前,這個隻對PersistentVolumeClaims支援,而且隻在PersistentVolume的範圍内查找标簽。處理在Pod的屬性中定義的volumes(即不使用PersistentVolume)有可能會變得更加困難,因為要在排程的過程中确定volume的zone,這很有可能會需要調用雲提供商。
  • PodFitsResources:檢查主機的資源是否滿足Pod的需求。根據實際已經配置設定的資源量做排程,而不是使用已實際使用的資源量做排程。
  • PodFitsHostPorts:檢查Pod内每一個容器所需的HostPort是否已被其它容器占用。如果有所需的HostPort不滿足需求,那麼Pod不能排程到這個主機上。
  • HostName:檢查主機名稱是不是Pod指定的HostName。
  • MatchNodeSelector:檢查主機的标簽是否滿足Pod的nodeSelector屬性需求。
  • MaxEBSVolumeCount:確定已挂載的EBS存儲卷不超過設定的最大值。預設值是39。它會檢查直接使用的存儲卷,和間接使用這種類型存儲的PVC。計算不同卷的總目,如果新的Pod部署上去後卷的數目會超過設定的最大值,那麼Pod不能排程到這個主機上。
  • MaxGCEPDVolumeCount:確定已挂載的GCE存儲卷不超過設定的最大值。預設值是16。規則同上。

可以通過配置修改Kubernetes預設支援的過濾規則。

經過過濾後,再對符合需求的主機清單進行打分,最終選擇一個分值最高的主機部署Pod。Kubernetes用一組優先級函數處理每一個待選的主機。每一個優先級函數會傳回一個0-10的分數,分數越高表示主機越“好”, 同時每一個函數也會對應一個表示權重的值。最終主機的得分用以下公式計算得出:

      finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)

現在支援的優先級函數包括以下幾種:

  • LeastRequestedPriority:如果新的pod要配置設定給一個節點,這個節點的優先級就由節點空閑的那部分與總容量的比值(即(總容量-節點上pod的容量總和-新pod的容量)/總容量)來決定。CPU和memory權重相當,比值最大的節點的得分最高。需要注意的是,這個優先級函數起到了按照資源消耗來跨節點配置設定pods的作用。計算公式如下:
  • cpu((capacity – sum(requested)) * 10 / capacity) + memory((capacity – sum(requested)) * 10 / capacity) / 2
  • BalancedResourceAllocation:盡量選擇在部署Pod後各項資源更均衡的機器。BalancedResourceAllocation不能單獨使用,而且必須和LeastRequestedPriority同時使用,它分别計算主機上的cpu和memory的比重,主機的分值由cpu比重和memory比重的“距離”決定。計算公式如下:
  • score = 10 – abs(cpuFraction-memoryFraction)*10
  • SelectorSpreadPriority:對于屬于同一個service、replication controller的Pod,盡量分散在不同的主機上。如果指定了區域,則會盡量把Pod分散在不同區域的不同主機上。排程一個Pod的時候,先查找Pod對于的service或者replication controller,然後查找service或replication controller中已存在的Pod,主機上運作的已存在的Pod越少,主機的打分越高。
  • CalculateAntiAffinityPriority:對于屬于同一個service的Pod,盡量分散在不同的具有指定标簽的主機上。
  • ImageLocalityPriority:根據主機上是否已具備Pod運作的環境來打分。ImageLocalityPriority會判斷主機上是否已存在Pod運作所需的鏡像,根據已有鏡像的大小傳回一個0-10的打分。如果主機上不存在Pod所需的鏡像,傳回0;如果主機上存在部分所需鏡像,則根據這些鏡像的大小來決定分值,鏡像越大,打分就越高。
  • NodeAffinityPriority(Kubernetes1.2實驗中的新特性):Kubernetes排程中的親和性機制。Node Selectors(排程時将pod限定在指定節點上),支援多種操作符(In, NotIn, Exists, DoesNotExist, Gt, Lt),而不限于對節點labels的精确比對。另外,Kubernetes支援兩種類型的選擇器,一種是“hard(requiredDuringSchedulingIgnoredDuringExecution)”選擇器,它保證所選的主機必須滿足所有Pod對主機的規則要求。這種選擇器更像是之前的nodeselector,在nodeselector的基礎上增加了更合适的表現文法。另一種是“soft(preferresDuringSchedulingIgnoredDuringExecution)”選擇器,它作為對排程器的提示,排程器會盡量但不保證滿足NodeSelector的所有要求。

QoS

叢集排程器的設計目标是簡單合理的利用叢集資源,也就是如何在保障業務穩定的前提下盡量提高叢集的資源使用率,這是一個非常困難的問題。在被過度使用的系統中(資源請求總和>機器容量)系統會變得不穩定,任務可能最終會被殺掉。如果系統的CPU或者記憶體資源被耗盡,較為理想的做法是殺掉不太重要的任務,保證高優先級任務的資源需求,在有資源空閑時重新運作低優先級的任務。Borg通過混合部署高低優先級的任務使叢集的資源使用率提高了20%以上。

Kubernetes借鑒了Borg的實作思路,對于每一種資源,Kubernetes将容器分為3種QoS等級:Guaranteed,Burstable和Best-Effort,這三種優先級逐漸遞減。

  • 目前,僅關注記憶體方面。在CPU資源限制不能滿足時容器并不會被殺掉(在系統任務或者守護程序占用大量CPU的情況下),容器隻會被暫時阻塞。
  • 記憶體最低限制為0的容器會被分級為記憶體Best-Effort級别的容器。這種容器沒有最低資源限制,優先級也最低(如果系統記憶體被耗盡,這些容器的程序将首先被殺掉)。
  • 資源請求與資源限制相同(非0)的容器被分級為記憶體Guaranteed級别,這些容器要求明确定義的資源數量,優先級也最高(在記憶體使用方面)。
  • 其它所有的容器屬于Burstable級别的容器,優先級中等。這些容器有最小的資源限制,在資源富餘的時候,有權限使用更多資源。
  • 在目前的分級政策和具體實施中,從技術層面看,Best-Effort容器是Burstable容器的一個子集(隻是資源請求為0),但是Best-Effort容器是一個重要的特殊的類别,Best-Effort容器不需要任何資源限制,這樣它們可以利用叢集中未使用的資源。

對于每一種資源,容器可以指定一個資源請求和資源限制,0 <= 資源請求<= 資源限制<= 無窮。如果容器被成功排程至節點,容器的資源請求是能夠保證的。Kubernetes把資源分為兩類,可壓縮的資源和不可壓縮的資源。如果資源使用量大于可壓縮的資源,容器會被阻塞但是不會被殺掉,如果資源使用量大于不可壓縮的資源,容器會被殺掉。

可壓縮的資源保障:

  • 目前,僅支援CPU。
  • 最小的CPU資源限制為10M。這是Linux核心限制決定的。
  • 容器能夠獲得要求的CPU量,能否獲得額外的CPU時間取決于其它運作的任務。
  • 除了請求的CPU數量,額外的CPU資源是共享的。比如說,假定A容器指定需要CPU的60%,B容器請求CPU的30%,假設兩個容器都盡可能多地使用CPU資源,那額外的10%的CPU資源将按照2:1的比例分别配置設定給容器A與容器B。
  • 容器資源使用超過資源限制就會被阻塞,如果沒有指定資源限制,當有CPU資源可以使用時,容器就可以使用額外的CPU。

不可壓縮的資源保障:

  • 目前,僅支援記憶體。
  • 容器可以獲得請求的記憶體資源量,如果超出記憶體資源請求,容器會被殺掉(當其它容器需要記憶體時),但是如果容器消耗的資源小于請求的資源量,将不會被殺掉(除非系統任務或者守護程序需要更多的記憶體)。
  • 在容器記憶體使用量超過記憶體資源限制時,容器會被殺掉。

資源調整

資源調整在kubernetes中也稱為rescheduler。目前YARN,Mesos等排程系統的排程決策都是在初期資源申請的時候做出的,随着系統的運作,以及其它任務的增加或删除,整個叢集的資源使用情況會發生很大的變化,這個時候把其中的一部分任務遷移到别的主機上,對均衡叢集中主機的負載、消除資源碎片會有很大幫助。應用架構在申請資源時,配置設定的主機隻需要滿足它們的一些條件限制,而應用并不關心任務會運作在哪台具體的主機上。容器時代的資源調整不同于虛拟機時代的資源調整,虛拟機熱遷移技術是虛拟機叢集資源調整的基礎,容器叢集資源調整的基礎是服務發現、共享存儲等技術。

資源調整在Kubernetes中的使用場景大概包括以下兩種:

  1. 聚合Pod:聚合Pod是指把Pod集中配置設定到部分主機上。這樣做的好處有兩個,第一個是叢集的資源狀态是一直在變化的,在batch-job類型的任務運作結束後,會在主機上留有資源碎片,這些資源碎片會因為不足以容納未運作的任務的資源需求被浪費掉。把Pod集中起來部署到一部分主機上,可以把資源碎片聚合起來,把未運作的任務排程到這些資源上,進而提高叢集的資源使用率。另一個好處是,如果每個主機上都隻運作少量的任務,但是因為每台主機上都有任務在運作,必須保證所有的主機都是開機狀态。把Pod集中起來部署到一部分主機上,可以關掉沒有運作任務的主機,進而節約電力資源。
  2. 分散Pod:随着時間推移,叢集中很可能會出現主機負載不均衡的情況,負載很高的主機其上的業務存在運作不穩定,同時負載很低的主機資源被大量浪費,資源調整可以改善這種情況,将高負載主機的任務重新排程到負載低的主機上,有利于維持叢集的負載均衡,解決任務運作不穩定與資源浪費的情況。

資源調整也可以達到高優先級任務搶占低優先級任務資源優先運作的要求。

當然在實作的過程中有很多的因素需要考慮,Kubernetes資源調整是有破壞性的,因為現在還沒有容器熱遷移的技術,資源調整隻能通過殺掉Pod在其它機器上重新開機,怎麼樣在對Pod影響最小的情況下進行資源調整是一個重要的問題。資源調整的過程中,必須滿足Pod在建立時的一些NodeAffinity限制,避免資源調整的結果和叢集排程器産生沖突。

結語

  • 使用不同的排程器排程不同類型的任務(分析任務和部署虛拟機任務)
  • 使用處理後的主機真實負載做排程決策
  • 設定基于時間的排程規則
  • 定期動态調整叢集資源情況,根據政策均衡主機資源使用率或集中部署任務關掉部分主機
  • 支援CloudStack,OpenStack,VMware等虛拟化平台