黃 凱,孟慶永,謝雨來,2* ,馮 丹,2,秦磊華,2
由于Docker相對于傳統虛擬機的優勢,越來越多的研究工作者開始使用 Docker來代替虛擬機[1]:Tihfon等[2]借助于Docker實現了多任務PaaS(Platform as a Service)云基礎架構,通過Docker實現了應用的快速部署,應用程序的優化和隔離;Nguyen等[3]通過Docker,實現了用于高性能計算的分布式MPI(Message Passing Interface)集群,借助于Docker,使原本耗時的設置MPI集群工作變得相對簡便;Julian等[4]借助于Docker優化了自動縮放網絡集群,而且他們認為Docker容器可以在更大規模的生產環境中進行更廣泛的應用。
目前Docker swarm提供的資源調度策略比較單一,只考慮節點能否滿足任務需求和節點上的容器數量,沒有在節點之間進行負載比較來選出最合適的節點來部署容器,因此會出現節點上資源使用率不均衡的情況[5]。
針對Docker集群網絡負載的資源調度,Dusia等[6]提出一種調度策略,可以保證不同優先級的應用容器享有不同比重的帶寬,在盡量滿足高優先級網絡服務的同時,將剩余帶寬盡可能分配給低優先級的服務,從而保證網絡上的服務質量(Quality of Service,QoS)。這種調度策略,通過在原Docker架構中增加分組分類器和優先級調度器來實現網絡資源調度。流中的分組被分類并添加到三個可用優先級隊列中的一個。調度器對數據包進行出隊,并根據隊列的優先級將每個數據包發送到容器。同時,該調度策略通過對每個優先級類的流執行隨機排隊規則,使得每個優先級內各容器能夠依次發送數據,保證流傳輸的公平性。此外,該調度策略增加了一個限制容器發送數據包速率的功能,通過這個功能,就可以為容器分配精確的網絡上下限,限制容器的網絡占用,實現更好的資源調度。
McDaniel等[7]對于容器的 I/O爭用提出了一種擴展Docker和Docker swarm的雙層方法(即在集群和節點級別),使得它們能夠監視和控制Docker容器的I/O。在節點級,通過設置容器的優先級來分配給容器對應比例的I/O;而在集群級則通過對節點I/O資源的使用分析,實現更好的負載平衡。
Monsalve等[8]通過將時間切片的概念擴展到虛擬化容器的級別來解決計算資源過度使用的問題。他們使用觀察決定法(Observe Decide Act,ODA)控制循環來擴展Docker,實現了一個簡單的roundrobin時間切片策略。他們為單個容器保留整個系統的CPU資源,然后每個容器只運行一段時間間隔。這種類型的策略在容器級別具有與組調度相同的優點。
以上調度方式都只針對某一資源作了優化,應用在對此資源偏重的負載下能起到不錯的優化作用,但是也存在著問題,優化都是針對于節點內部的,在集群的角度,沒有根據應用的資源偏重,將不同偏重的應用混合放在一個節點上,來從源頭上減少資源爭用的開銷。
為了改善Docker集群的資源調度,盧勝林等[5]提出了另外一種基于Docker swarm集群的容器加權調度策略。通過對節點分配和消耗資源的分析,計算出節點權值,然后將新的容器部署在低權值的節點上,以實現集群的負載均衡。在這種調度策略下,系統統計了4項參數指標。針對不同的集群,對參數分配以不同的權重,以適應不同側重的應用系統,大大改善了Docker swarm的集群調度策略;然而,這種調度策略雖然改善了Docker集群的負載均衡問題,但并沒有對應用的資源偏重進行分析,因為對于集群來說,很難保證各個服務的資源偏重會和已經分配的權重吻合,這樣就存在很大可能當前服務是CPU偏向的,但是被分配到內存占用少的節點上。這樣結果就是某些節點權值高,但只有某項資源消耗很高,其余資源并沒有太多的占用。
在實際應用中,許多工作使用了機器學習的算法,對系統應用的資源需求進行預測,從而提前進行資源分配調度,讓資源在對應時間段分配給不同的服務,從而實現資源的充分利用。
Kang等[9]提出了一種高效的異構Docker容器資源管理策略,使用k-medoid算法、分割周圍類算法等方法,實現了工作負載能效感知的容器代理系統,并通過這個系統既降低了由運行容器應用程序引起的能耗,又保證了系統性能。Calheiros等[10]提出了一個基于自回歸積分滑動平均模型(Autoregressive Integrated Moving Average model,ARIMA)的云計算負載預測模型,平均準確率達到了91%。
機器學習的調度策略能夠實現更加優秀的資源利用,但是相對也更為復雜,需要依據具體的集群設計算法模型,對于大型的項目而言,其優勢明顯;而對于簡單的集群則有些得不償失,因為機器學習算法需要有足夠樣本的訓練集,對于容器集群,則需要收集大量的時間序列的容器資源使用量,有著不小的存儲和計算開銷。
因此本文綜合考慮服務對資源的偏向以及節點上資源利用情況,提出了一種動態加權調度算法。該算法能根據服務對資源的偏向性,動態調整資源權重,由資源利用率和權重計算出代表節點負載的權值,以此來進行調度。該算法既能實現更準確的資源調度,也避免了機器學習調度帶來的巨大開銷。
Docker整體是一個 C/S模式的架構[11],而 Docker的后端屬于松耦合架構,這使得Docker的各個模塊相對獨立,不會因為對于某些模塊的修改或者添加模塊對其余模塊造成很大影響,因此,在對Docker源代碼進行相關改動,修改添加功能時,不會對其他功能造成太大影響。
Docker主要由Docker Client和Docker Daemon兩部分組成,兩者通過相互通信協作,共同實現Docker系統的功能,進行容器管理。
Client主要負責用戶交互,通過用戶指令與Daemon通信,顯示Daemon的執行信息;Daemon屬于主要執行模塊,Docker的容器管理和集群管理等大部分功能都在該部分實現。
Docker swarm提供了 Random(隨機)、Spread(擴散)和Binpack(裝箱)三種資源調度的方法[12]:Random調度策略隨機選擇容器的部署位置,這種調度顯然問題很大,一般只用于集群的測試;Spread調度策略,按照各節點總內存和CPU內核數來部署容器,這樣能夠讓容器相對均勻地分散在每一個節點上;Binpack調度策略,先把一個節點的資源使用完后再向別的節點部署容器,這種調度方式可以盡可能地使用每個節點的資源,節省未啟動節點的資源。
Docker swarm自帶的這三種資源調度方式,可以滿足一些對集群的工作效率要求不是很高的應用,但是對于有些應用(如云平臺),需要充分使用集群的整體性能,其對于負載均衡、資源利用率要求會比較高。在這些情況下,Docker swarm自帶的調度策略則存在明顯的不足。
首先,Docker swarm主要根據各個節點的全部內存大小這一參數來分配任務。因為對于Docker容器來說,內存資源是可以準確統計的,但是對于CPU的資源分配則只能指定內核數或者指定使用的權重大小,并不能準確統計CPU的使用情況。
然后,Docker swarm并沒有考慮每個節點實際的可用資源,以及分配出去的資源使用情況。例如,Docker swarm所存儲的節點內存總量來自節點計算機的內存總量,但是在節點機器自身運行操作系統和軟件都是會占用內存的,這導致節點實際可用內存會比Docker swarm所統計的要少。此外,一般情況下,為了應用能夠穩定運行,都會為應用分配足夠多的資源,但是實際上,大多時候應用都不是在以最高的負載在運行,而且CPU、內存、網絡等也不會同時處在最高負載,這樣分配的資源很多時候都不會用盡,而Docker swarm并不會對未使用的資源重新分配,造成了資源的浪費。
加權調度算法采取以下的計算方式,選擇4項參數:CPU使用率N(c)、內存使用率N(m)、網絡負載N(n)和已分配內存的平均使用率N(u)。使用W(k)代表節點k的總權值來反映節點負載,權值越高,說明節點負載越高。k1、k2、k3、k4來代表4項參數初始所占權重。為了解決某參數值偏大而總權值不高無法準確反映節點負載的情況,引入α、β、γ三項參數進行權值調整。α、β、γ 原值為1,當 N(c)、N(m)、N(n) 有一項超過0.8后,增大對應α、β、γ的值為h(h>1),通過增大該項資源權值以修正節點權值,更確切地反映節點負載。節點k權值計算公式如式(1)所示:
W(k)=k1αN(c)+k2βN(m)+k3γN(n)+k4N(u) (1)
但是為了實現更好的節點資源利用和負載均衡,需要避免出現某項資源利用率過高而其余資源利用較低的情況,在加權調度算法的基礎上對調度策略進行優化。
普通的加權調度算法會出現節點資源使用不平衡的情況,主要是沒有考慮到實際的服務占各類資源的比重和集群設定的資源權重不是一直近似的。因此針對這類特定服務,需要對權重進行重新調整。引入新的參數bias,當創建的服務對資源的需求權重和集群分配的權重近似時,設置bias為空值;當兩者權重不相近時,通過指令對這兩個參數進行動態調節。
例如當前集群的CPU資源權重為0.4,內存資源權重為0.6,而新創建的服務對CPU資源需求比較高,如果按照原本的權重進行調度,因為內存資源占比重較大,所以這個服務很可能被分配到一個內存剩余資源比較多的節點上面去,但是這個節點的CPU資源未必是豐富的。這時通過參數bias告訴系統當前應用服務對CPU資源需求高,將CPU的權值調整為2×0.4,這樣最終CPU資源與內存資源的比重就會變為8∶6,只針對于這個服務而言,本次調度會把其分配到一個CPU資源充分的節點上。這樣不僅能夠對適合集群資源偏向的普通服務進行調度,對于那些資源偏向特殊的服務,也能實現良好的負載均衡。
改進后的節點k權值計算公式如式(2)所示,b1、b2、b3代表創建服務時指定的對應資源的偏向性,在創建服務時由用戶指定,會直接和權重相乘。其他參數同式(1)。
W(k)=b1k1αN(c)+b2k2βN(m)+b3k3γN(n)+k4N(u)
(2)
設計系統整體模塊如圖1,圖中陰影部分為需要增加或修改部分。其中:集群資源獲取模塊,為自行設計實現;指令相關模塊在原本的基礎上修改添加部分功能;而Filter模塊和Scheduler模塊則在源代碼的基礎上進行修改。
指令相關模塊,通過接口函數調用相關服務,實現對系統的調用和控制;Swarmkit接口模塊,屬于指令模塊的一部分,通過添加指令參數和接口函數,實現新指令對服務的調用的通道;集群資源獲取模塊,結合原節點管理模塊,通過節點資源獲取模塊和TCP通信,將各個節點信息發送到主節點,實現對集群資源信息的獲取;調度相關模塊中,Event進行事件監測,觸發事件后通過集群資源獲取模塊獲取集群節點資源信息,而后Filter模塊進行節點過濾,Schedule模塊在過濾后的可用節點上進行服務部署,完成調度。
權值的計算主要包括3部分。首先,通過集群資源獲取模塊收集集群資源信息;然后,將獲取到的資源信息按照發送規則進行解析,同時依據資源利用情況進行權重調整:當資源利用率達到0.8后,對權重進行自動調整,提升權值,從而提升節點的權值,防止單項資源占用過高,而整體權值較低不能反映節點實際負載;另外,根據指令參數bias,主動調整資源權重,針對一些對資源有明顯偏向性的服務,可以將服務分配到需求資源最充足的節點上。
Docker在Scheduler模塊下用函數聲明了節點調整規則和任務部署規則。內容如下:首先判斷兩個節點在規定時間內的失敗次數,如果有節點失敗次數達到了規定的失敗上限,就優先返回失敗次數少的節點,但是如果兩個節點失敗次數相同,就執行后續的比較;然后比較節點上運行的當前服務的任務數量,優先調度當前運行任務量少的節點,如果兩節點任務數量仍然相同,則比較兩個節點運行的任務總數,優先調度總任務量少的節點。如果任務總量仍然相同,就返回false調度當前節點,在完成本次調度后,當前節點服務對應任務數量就會超過下一個節點,在下次調度時就會優先使用后續節點。而動態加權調度算法則是將原本以運行任務總量為依據的判斷改為以權值大小為判斷依據。
相對Docker swarm提供的調度策略而言,優化的動態加權調度算法存在以下優勢:
首先,增加了集群節點資源獲取的功能,所以在進行節點過濾時,可以根據實際資源利用情況選出滿足資源需求的節點,避免了因為節點分配的資源沒有完全使用而造成浪費。
其次,因為對調度策略進行了改進,綜合考慮各項資源利用情況,在將任務分配到節點時,會根據節點實際資源使用率進行分配,使得最終各個節點的資源使用率相對接近,實現了更好的負載均衡。
最后,通過參數bias對資源占用權重進行動態調整,這樣在部署某些對資源需求比較特殊的服務時,也能夠通過改變資源權重,將服務分配到對應資源豐富的節點上,實現節點各項資源使用的相對平衡,充分使用了集群的資源。
本次測試在一臺物理機創建兩個ubuntu虛擬機作為子節點,將本機作為管理節點,這樣一共3臺計算機,構成了一個簡單的集群。各節點信息如表1。

表1 集群節點信息Tab.1 Information of nodes in cluster
分別對Docker的原本調度策略、無參數調整的加權調度策略和動態加權調度策略3種調度方式進行測試,統計各個節點的CPU資源和內存使用情況,來分析各調度策略的優劣。
在調度開始之前,查看當前節點的資源利用狀況,如表2。其中節點Manager的權值明顯高于其余兩個節點,是因為這個節點是本機,運行著虛擬機和其他的各種軟件,所以資源占用一開始就高,不過這樣反而更能測試調度策略的資源調度能力。
測試分為A、B兩組進行,在A、B兩組各部署30個服務:其中A組部署了20個CPU偏向的應用服務,10個內存偏向的應用服務,這個集群對CPU的資源需求更高;而B組部署10個CPU偏向的應用服務,20個內存偏向的應用服務,該集群對內存資源的需求更高。

表2 節點初始資源信息Tab.2 Information of nodes initial resources
因為集群的調度結果和應用服務部署順序有關,所以在A、B兩組內分別按照不同順序對服務進行兩次部署,避免部署順序導致的偶然性,這樣一共進行4組對比實驗。具體測試應用服務部署如表3。
3.3.1 A 組測試結果分析
3種調度策略的A-1組和A-2組測試數據如表4,5所示。可以發現,不論是在A-1組的測試還是A-2組測試,動態加權調度策略的CPU使用率和內存使用率都更加均衡,優于Docker的調度策略和普通加權調度策略。

表3 應用服務測試部署順序Tab.3 Order of deployment
3.3.2 B 組測試結果分析
3種調度策略的B-1組和B-2組測試數據如表6,7所示。可以看出,動態加權調度策略的調度結果資源使用率最均衡。Docker原調度和普通加權調度策略都出現了節點內存資源占用超過0.8,然而優化的調度策略內存使用率都保持在0.6~0.7,避免了節點負載過重,優化了資源利用。

表4 A-1組測試結果Tab.4 Test results of group A-1

表5 A-2組測試結果Tab.5 Test results of group A-2

表6 B-1組測試結果Tab.6 Test results of group B-1

表7 B-2組測試結果Tab.7 Test results of group B-2
3.3.3 服務運行速度的影響
從上述測試可以看出,優化的調度策略實現了更優秀的資源負載均衡,然而為了證明新的調度策略不會對服務的運行速度產生負面影響,進行服務運行速度測試。
編寫兩個循環計算程序作為兩個子服務,一個控制程序作為主服務。兩個計算程序在完成計算后,將計算時間發送到主服務,只有在兩個計算都完成之后,才算完成了完整的服務。通過這樣的測試程序來測試調度策略對服務整體運行速度的影響。
當集群資源充足時,難以看出服務執行速度的差異。因此,在進行服務速度測試前,先按照各調度策略在集群上部署10個內存偏向的服務和30個CPU偏向的服務,提高集群的CPU使用率,然后再進行測試服務的部署。
對40個服務的不同部署導致的節點資源利用狀況,分別對3種調度策略進行測試,結果如表8。

表8 各調度策略的服務運行時間測試表 s Tab.8 Operating time of 3 scheduling strategies s
當存在多個子服務時,服務的運行速度取決于最慢的那個子服務。從表8中數據得知,動態加權調度策略的服務執行速度最快,而無參數調整的動態加權調度策略服務執行卻最慢。這主要是因為節點ubuntu01的內存使用率較低,在考慮節點整體權值的情況下,該節點被分配了更多的CPU偏向的服務,最終導致同樣對CPU資源需求量高的子服務1運行變慢,拖慢了整體服務;而動態加權調度策略,各個節點資源利用率相對平均,因此子服務執行時間相差不大,服務總運行時間最短;而Docker原調度策略以平均分配容器的方式進行調度,由于節點ubuntu01和節點ubuntu02的CPU資源總量相同,因此最終這兩個節點的資源占用反而比較相近,使得兩個子服務運行速度沒有太大差異,最終服務運行速度居中。
3.3.4 測試結論
A組和B組的測試結果與調度策略相吻合。Docker原本依據各節點容器數量進行調度,因此各個節點容器數量相同,但是因為沒有考慮節點資源情況,導致節點間的資源利用率不平衡。
對于無參數調整的加權調度策略,嚴格按照節點總權值進行調度,忽視了具體任務對資源的實際需求,因此,在B組測試中出現了明顯的節點各項資源利用不均衡,其總權值是通過兩項資源互相補充實現了相對均衡,而且在B-2組測試中出現了內存資源利用過高導致的CPU資源浪費。對于動態加權調度策略,由于在創建對資源有偏向性的服務時,通過參數bias調整了對應資源的權重,使得系統在節點負載相差不大的情況下,優先將服務部署在所需資源充足的節點上,這樣避免節點出現某項資源利用過高而另外資源利用很低的情況,在A組和B組測試中,優化了資源的均衡利用。
同時,通過對具體服務的運行速度進行測試,發現優化的調度策略在集群負載比較高的情況下,實現了最快的服務運行速度。
總體而言,本次的調度策略優化是比較成功的,實現了預期的目標,相比Docker原本的調度和無參數調整的加權調度,無論是對CPU資源需求多的集群,還是內存資源需求資源多的集群,在不同的調度順序下都實現了更好的負載均衡和資源利用。
本文提出了一種優化的動態加權調度算法。該算法在集群調度時,既綜合考慮CPU、內存、網絡各項資源負載,又實現了節點各項資源的均衡利用,避免了單項資源使用率過高造成的資源浪費,提高了資源使用率。實驗結果表明,本文提出的動態加權算法無論在CPU資源需求高的集群服務還是內存需求高的集群服務,優化的調度策略都能實現比Docker原調度策略和普通加權調度策略更優的集群負載均衡和資源利用,并且對正常服務運行時間的影響最小。