周 毅,鄧玉輝,2
1(暨南大學 信息學院計算機科學系,廣州 510632)2(中國科學院計算技術研究所 計算機體系結構國家重點實驗室,北京 100190)
1http://jaso-nwilde-r.com/blog/2014/08/19/squashing-docker-images/
Docker容器技術是一種類似OpenVZ[6],Zap[7]和LXC[5]的操作系統(tǒng)級虛擬化技術,用戶可以創(chuàng)建和管理容器,高效快捷得使用多個虛擬環(huán)境.因而每一個工作負載都可以運行在獨立的虛擬環(huán)境中,并且容器之間也可以獲得較好的工作效率和隔離度.容器技術以共享內核資源的方式區(qū)別于傳統(tǒng)虛擬機,實現(xiàn)輕量級的應用隔離[3].
現(xiàn)如今,隨著云計算和大數(shù)據規(guī)模的日益擴大,工業(yè)界對產品持續(xù)集成和高效發(fā)布的需求與日俱增[8],學術界也有很多對鏡像存儲和拉取的研究[12].由于鏡像包含了所有運行時依賴,從而導致鏡像的體積往往很大,在本地存儲和導出遷移的過程中會產生較大的磁盤和網絡I/O開銷,增大了部署運維成本,同時也限制了鏡像的遷移和擴展[2].鏡像應該只包含一種服務所需要的依賴環(huán)境,因此,對于過于龐大和冗余的鏡像包的刪減工作就顯得尤為重要[9].
本文提出了一種基于概率模型的Docker鏡像刪減策略,可以實現(xiàn)在本地鏡像存儲時,通過增大基礎鏡像的復用率來減小本地存儲開銷;并且對于要導出的鏡像,則是收集原鏡像運行時訪問到的文件,同時建立導出概率模型,對達到導出閾值的目錄下文件全部導出,從而在減少鏡像大小的同時兼顧了鏡像功能的完備性.
為了使容器最大程度復用鏡像層數(shù)據,減少磁盤占用空間和磁盤I/O開銷,Docker采用如圖1所示的堆棧式的鏡像存儲模型和寫時復制(CoW)機制.CoW機制的核心在于,當讀取已存在于下層的數(shù)據時,上層將不產生任何增量部分,僅當需要修改下層已存在的文件時,上層將會將該文件復制一份,然后再進行修改,而下層的原有文件則不會被更改,但從上層的視角來看,該文件已被修改.同樣的,當上層需要刪除下層中已存在的文件時,只需要添加一種占位符形式的特殊文件——whiteout,雖然從上層視角看確實刪除了,但實際上該文件依然存在于下層中,并且實際占用的磁盤空間不會減少.
圖1 鏡像的寫時復制(CoW)機制Fig.1 Copy on write mechanism of image
Docker容器的組織結構都采用了CoW機制,鏡像層作為只讀層,提供基礎數(shù)據共享,容器層則用于存儲當前鏡像的修改部分,CoW的這種不改變下層數(shù)據的特性也決定了鏡像的大小只會增長不會減小.因此,想要減小鏡像的大小,僅僅直接在上層容器里刪除相關內容是無法作用于下層鏡像層的.而如果直接對鏡像層進行處理,可能會影響到其他依賴該鏡像的容器,經過修改的鏡像也無法保證原有功能的完備性.
我們的工作即針對Docker鏡像的這種特性,在本地存儲和導出鏡像時進行優(yōu)化,使得鏡像在本地存儲時占用更少的存儲空間,對于導出的鏡像則有較小的體積和較完備功能.
在現(xiàn)有工作中,大多數(shù)都是根據鏡像層CoW特性,采用靜態(tài)分析算法1尋找鏡像中的whiteout文件,然后刪除鏡像層中相對應的文件1;或者從dockerfile的編寫上進行規(guī)范,使得生產的鏡像更小更安全[10],其缺點在于無法對已制成的鏡像進行處理.
而算法1的缺點在于只能對特定的aufs文件驅動的鏡像進行處理,而對于其他文件驅動,比如device-mapper,btrfs等,這些文件驅動的鏡像存儲位置和鏡像讀寫方式和aufs有很大區(qū)別[11],無法直接通過路徑訪問,因此也無法進行鏡像刪減.另外,對于不包含whiteout的鏡像文件,即鏡像層中沒有需要刪除的內容,那么算法1就幾乎不能有所刪減.
另一種方法則完全區(qū)別于算法1中采用的靜態(tài)分析思路,使用了算法2的動態(tài)分析方法:程序從待修改的鏡像中,運行起一個容器,并在該容器中植入一個探針,用于實時捕捉在容器中執(zhí)行的進程和訪問到的文件.等到該容器運行結束,探針會匯總在容器運行過程中所訪問到的相關依賴文件,并將這些文件導出,重新生成一個新鏡像,從而達到減少鏡像文件數(shù)量的目的.我們采用如圖2所示的UML活動圖來描述算法2.
Algorlthm 1:Delete Whiteouts Data:FileName,ImageDir,DeletedFile1 for FileName in ImageDir do// If start with whiteout2 if startWith(FileName,“.wh.”)==0 then // Remove deleted files3 removeAll(FileName); // Remove the whiteout itself4 DeletedFile=truncate(FileName,“.wh.”);5 removeAll(DeletedFile);
算法2可以最大程度減小鏡像的大小,并且不會像算法1受限于特定文件驅動.但缺點在于,算法2只依賴于容器運行中實際訪問到的文件,如果在運行過程中沒有較全面覆蓋到所需的文件,則存在過度刪減的可能.
圖2 算法2:獲取容器內訪問文件活動圖Fig.2 Algorithm 2:UML activity diagram of getting container access files
針對上述問題,我們通過共享基礎鏡像減少本地鏡像存儲開銷;同時基于算法2的動態(tài)分析加以概率模型,對導出鏡像所需文件進行預測,使得在減少鏡像大小的同時兼顧鏡像功能的完備性.
為了實現(xiàn)Docker鏡像刪減策略,本文設計了一種本地存儲和導出鏡像優(yōu)化工具——docker-jimp,它可以有效地降低鏡像在本地存儲時所占用的磁盤空間,同時也大大減小了導出的鏡像的大小,便于鏡像的導出和遷移.
對于Docker鏡像的數(shù)據刪減包含本地存儲的鏡像和導出的鏡像.本地存儲時,通過增大鏡像之間基礎鏡像層的復用率,讓更多的上層鏡像復用同一個基礎鏡像,從而減小鏡像在本地的總體存儲開銷;在導出鏡像時,主要通過獲取文件訪問信息,使生成的刪減鏡像包含鏡像功能所依賴的文件,并使用概率模型預測導出鏡像所需文件,構建出新鏡像.其整體結構如圖3.
鏡像刪減的關鍵在于對鏡像行為的分析和基礎鏡像的選取,其決定了生成鏡像的可靠性和最終生成的鏡像大小.過多的刪減可能會導致鏡像功能不穩(wěn)定,而僅僅是刪除鏡像中已標記為刪除的文件又不能達到刪減鏡像的目的.為了達到最佳的刪減效果,需要建立在不同刪減模式下的評估模型.在本文中,我們主要對以下兩種情況的模型分別進行分析.
圖3 docker-jimp整體結構圖Fig.3 Integral structure diagram of docker-jimp
1)本地存儲模式
Docker鏡像在設計時采用了分層結構,其目的就在于希望可以充分利用本地存儲的鏡像層之間的共享關系,從而減少存儲開銷.我們對本地存儲已有的鏡像進行分析得知,鏡像在本地存儲時,鏡像都包括基礎鏡像層及實現(xiàn)自身鏡像功能的其他層.基于上述事實,對于存儲在本地的鏡像,我們盡量加大其對基礎鏡像的復用率,從而降低全局鏡像存儲的總開銷.
(1)
其中,S表示共享基礎鏡像層的大小,L表示該層所共享次數(shù).η值越大表明基礎鏡像層的復用率越高,對全局存儲開銷的提升效果越顯著.因此,可以根據η值,選取本地共享的基礎鏡像.
2)導出鏡像模式
對于導出鏡像,我們希望其在滿足功能的同時大小盡可能小.理論上,我們可以直接保留容器運行中訪問到的文件,但在實際中,僅僅通過保留部分時間運行的文件將很難覆蓋到所有需要的文件.比如,對于gcc鏡像,在運行過程中幾乎不可能訪問過所有頭文件,但對于導出的gcc鏡像,提供較為完整的頭文件庫無疑是確保功能相對完備的保證.
為了使得導出鏡像占用空間較小,同時能更完整包含實現(xiàn)其功能的依賴文件,因此,既需要導出在容器運行過程中訪問到的文件,又要對其他未訪問文件的導出可能性進行預測.本文中我們以目錄為單位,預測目錄y:a、只導出容器運行中訪問到的文件;b、導出目錄下所有文件.進一步的,我們定義:
目錄y={子目錄集Z,依賴文件集X,其他未訪問文件},事件Y={目錄y被全部導出},則目錄y中文件全部導出的概率為P(Y),其取值與目錄y中包含的依賴文件數(shù)量以子目錄的全部導出概率相關,而子目錄的全部導出概率則需要迭代至葉子目錄求得,圖4展示了目錄y的導出模式.這種模型建立依據源于在一般目錄結構設計里,每個目錄里的文件是實現(xiàn)某種功能的集合,當一個目錄內有較多文件被導出,那很有可能表明,這個目錄里的文件是實現(xiàn)某功能的核心,因此這個目錄下的所有文件也應該需要被導出.
圖4 目錄y導出模式示意圖Fig.4 Export schema of directory y
為了實現(xiàn)導出模型,我們定義導出鏡像所需要的所有依賴文件集合為Φ,其容量為n,目錄y中包含的依賴文件集合X={x1,x2,…,xl}、子目錄集Z={z1,z2,…,zM}.
同樣,定義目錄y中所包含的子目錄zj(j∈[1,m])被全部導出的事件為φj={子目錄zj全部導出},那么子目錄zj的全部導出概率則為P(φj),其值大小由子目錄遞歸計算得出,而zj對目錄y導出的條件概率P(Y|φj)則體現(xiàn)為zj對目錄y的影響因子α,即P(Y|φj)=α.對于文件系統(tǒng)中的任何一個目錄,都包括0到多個文件和子目錄.因此,對于任何一個目錄y,其全部導出概率P(Y)可描述為目錄下所有依賴文件及子目錄的全概率,即:
(2)
(3)
其中l(wèi)為目錄y下包含的依賴文件數(shù),m為子目錄數(shù),我們給定一個閾值ε(0ε1),若P(Y)>ε,則將目錄y下的所有文件全部導出.
為了達到預計的目的:動態(tài)收集容器的運行使用依賴情況,完成3.2中的兩種模型.需要實現(xiàn)的內容如下:
1)訪問文件信息收集
為了動態(tài)捕捉鏡像在運行時所訪問的文件,我們采用Fanotify文件系統(tǒng)通知機制[4]對鏡像內文件訪問事件進行收集.結合每個鏡像的功能,使用該鏡像運行若干個用例,對所有用例訪問到的文件取并集,從而得到鏡像訪問文件集合.
具體實現(xiàn)是通過4個并發(fā)進程及線程之間的相互通 信完成,分別為User、Sensor、Monitor和Collector.User是向探針發(fā)出開始或結束指令的進程,通常是運行在主機中,與Sensor通過unix socket進行通信;Sensor即在容器中進行文件訪問信息捕獲的探針進程,主要作用是負責接收Monitor捕獲而來的報告(report),并把這些報告上報給User進程;Monitor線程是負責匯總從Collector線程收集到的事件(even),并整理成報告提交給Sensor.
圖5是文件訪問信息收集的數(shù)據流圖,整個系統(tǒng)有三條數(shù)據流:stop流,由User發(fā)出,在整個探針信息收集完成后,停止探針工作,Sensor收到stop信號后,會將其傳送給Monitor,由Monitor執(zhí)行清理工作(cleanup);report流,由Monitor發(fā)出,在Monitor中執(zhí)行事件處理函數(shù)(ProcessEven),將從Collector收集到的原始訪問信息轉化為report,傳回給Sensor,進而由Sensor發(fā)送給User;even流,由Collector發(fā)出,是Collector在容器運行過程中執(zhí)行事件捕獲函數(shù)(GetEven),實時收集文件訪問原始信息.
圖5 文件訪問信息收集數(shù)據流圖Fig.5 Data flow diagram of access information
2)刪減模型實現(xiàn)
刪減模型的實現(xiàn)分為本地存儲模式和導出模式,我們分別進行討論:
本地存儲模式:根據公式(1)的模型,本地存儲模式的任務核心在于獲取本地每個鏡像的存儲空間大小,以及基礎鏡像層的大小.在實現(xiàn)上,由于與Docker的守護進程進行通信的方式是利用Socket通信,通過向守護進程發(fā)送Get請求來獲取到某一鏡像的配置信息,從而得到鏡像每一層的大小.
之后對新加入的鏡像進行分析,取得其中所包含的文件,剔除所有與共享基礎鏡像重復的部分.最后,使用選定的共享基礎鏡像和剔除后的剩余部分重新生成一個新的鏡像.從而該鏡像的基礎鏡像部分不會額外占用本地的磁盤空間,達到減少本地存儲開銷的目的.
導出鏡像模式:實現(xiàn)導出鏡像模型的核心在于對生成鏡像的文件訪問信息進行收集和分析,由公式(3)可知,我們主要關注的是目錄y下包含的依賴文件數(shù),通過找出每個目錄下文件和子目錄的數(shù)量,并從葉子目錄開始計算,向上迭代直至根目錄,然后代入公式(3)中計算目錄y的導出概率.進而可以得到文件系統(tǒng)中每個目錄對應的導出概率,對于超過閾值ε的目錄,就直接導出目錄中的全部文件.
為了進一步驗證本文提出的刪減策略的有效性,對本地存儲模式和鏡像導出模式分別進行了驗證.對于本地存儲模式,實驗主要對鏡像集在進行基礎鏡像替換后每類鏡像的變化情況以及占用的實際存儲空間的變化情況進行了統(tǒng)計;對于導出鏡像模式,我們以gcc鏡像為例,在該鏡像中編譯多種代碼,統(tǒng)計出每個目錄的導出概率,并得出在不同的導出閾值ε下,需要導出的文件數(shù)量和對應導出鏡像的大小.
本文實驗采用Inter Core i7-6700的 CPU,4GB DDR3 RAM,和7200RPM 500GB SATA硬盤,并操作系統(tǒng)為CentOS7.3,Linux內核版本是3.10.0,Docker版本是1.12.0對應的API版本為1.27,采用的編譯語言Golang版本為1.7.5,實驗具體環(huán)境具體參數(shù)如表1所示.
表1 實驗環(huán)境參數(shù)
Table 1 Parameters of experiment environment
環(huán) 境描 述CPUInter Core i7-6700Memory3797628 kBOSCentOS7.3Kernel3.10.0-514.16.1.e17DockerAPI Version 1.27GolangVersion 1.7.5
本文的數(shù)據集采用文獻[1]中的實驗鏡像集,其中包括共10個數(shù)據庫鏡像,13個操作系統(tǒng)鏡像,17個編程語言類鏡像,4個web框架鏡像,6個web服務鏡像和7個其他工具鏡像,共57個,均從docker hub上拉取.圖6是每個鏡像大小占總鏡像的百分比,從圖中可以看出,操作系統(tǒng)類鏡像所占比例相對其他都要少,這是因為其他鏡像往往都是以某個操作系統(tǒng)鏡像為基礎搭建的.而比重最大的則是編程語言類鏡像,這是因為其中包含大量的依賴文件,其中最大的gcc:7.1.0鏡像,則達到了1.64G.
我們對鏡像集中的鏡像進行分析,主要統(tǒng)計了鏡像每層的占用空間大小.結果發(fā)現(xiàn)每個鏡像各層的大小差異很大,而基礎鏡像層的大小往往位于最大的三層中,平均每個鏡像中最大的層可占鏡像總大小的67%.從圖7中可以看出,一個鏡像中的數(shù)據基本都集中在最大三層中,尤其是操作系統(tǒng)類鏡像,幾乎所有數(shù)據都只存在一層中.因此,通過共享同一個基礎鏡像層,增加本地中基礎鏡像層的復用率,可以達到多個鏡像共用同一個基礎鏡像層的效果,從而減少鏡像整體在本地占用的磁盤空間大小.
圖6 實驗鏡像集Fig.6 Set of experimental images
4.3.1 不同基礎鏡像替換后各個類型鏡像的影響
在本地存儲模式下,通過增大鏡像之間共享的鏡像層的復用率,讓更多的上層鏡像復用同一個基礎鏡像,從而減小鏡像在本地的總體存儲開銷.我們選取鏡像集中43個功能鏡像分別用crux:3.1(341.68M)、debian:jessie(123.5-1M)、fedora:2(230.86M)和alpine:3.5(3.98M)進行基礎鏡像替換.圖8是在基礎鏡像替換后,每一類鏡像的平均虛擬空間占用情況,從中可以看出,新鏡像的虛擬占用空間都比原來鏡像變大一些,而debian:jessie和alpine:3.5的增長程度較低.這是由于原本鏡像多采用debian:jessie基礎鏡像,而使用更大的crux:3.1、fedora:25基礎鏡像,無疑會增加單個鏡像的虛擬空間占用,但實際上基礎鏡像是共享的,所以本地只會存儲一份基礎鏡像.而使用alpine:3.5這種很小的基礎鏡像進行替換,單個鏡像變化不會明顯.
圖7 鏡像各層所占比例關系Fig.7 Proportion of image layers
由公式(1)計算得ηcrux=40.0%,ηfedora=30.8%,ηdebian=25%,ηalpine=1%.理論上應選取η最大的crux基礎鏡像.在實際實驗中,替換前本地存儲實際占用空間大小為21.2G,用crux替換后占用空間大小為20.4G,fedora替換后大小為22.8G,用debian替換后大小為19.5G,而用alpine替換后大小為24G.可以看到,使用fedora和alpine替換后,鏡像的實際占用空間反而更大了,這是由于我們雖然共享了基礎鏡像,但忽略了其他層的共享,導致了一部分數(shù)據的冗余存儲,而效果最好的是用debian進行替換,空間節(jié)約了8%,這是由于原有鏡像的基礎鏡像有較多是使用debian基礎鏡像,對于這些鏡像,我們完全保留了原有的共享關系,同時把其他鏡像也進行了debian基礎鏡像替換,所以可以使得空間的節(jié)約最明顯.
圖8 基礎鏡像層共享關系Fig.8 Shared relation of base image
可見η值可以在一定程度上反映基礎鏡像的復用率,作為基礎鏡像的選取標準,但也應該考慮原本的層共享關系,減少本地存儲時的數(shù)據冗余,這也是我們未來工作需要完善的地方.
4.3.2 概率模型中不同閾值選取對導出鏡像的影響
對于鏡像導出模式,我們選取了gcc:7.1.0鏡像作為實驗對象,其大小為1.64G.在實驗中,我們編譯了nginx-1.12.2、openssl-1.0.2m和 redis-4.0.6,并對運行過程中實際訪問到的文件數(shù)目進行統(tǒng)計,對于取不同的閾值ε,結合公式(3),得出對應目錄的導出概率.圖9是鏡像導出模型的目錄結構拓撲圖(部分結構未畫出).其中files為當前目錄中實際訪問到(包括重復訪問)的文件訪問次數(shù),dirs為當前目錄中被訪問到的子目錄數(shù),P(export)為導出概率.從圖中可以看出,當閾值ε取到0%-5%時,可以全部導出/usr/include,/usr/lib/x86_64-linux-gnu等與gcc鏡像編譯功能密切相關的目錄里的所有文件,從而保證導出鏡像有較好的完備性.
為了驗證選取不同閾值時,對導出文件數(shù)目的影響,我們選取了不同的閾值,并統(tǒng)計在在不同閾值下需要導出的文件數(shù)量.從圖10(a)可以看出,每次運行訪問到的文件數(shù)目基本不變,而當選取較小的閾值,其導出的文件數(shù)目越多,最高可增加133%.隨著閾值的提高,導出文件數(shù)目呈階躍式下降,這是由于對于導出概率大于閾值的目錄會完全導出其中文件,而小于閾值的目錄則按需導出,導致導出文件數(shù)量的跳躍式變化.同時,當閾值ε大于25%后,由于所有目錄的導出概率均小于閾值,不會預測導出文件,因而導出文件數(shù)目和訪問文件數(shù)目一致.
圖9 各目錄訪問次數(shù)及導出概率Fig.9 Number of directory visits and export probability
從圖10(b)中可以看出,gcc鏡像原來的大小為1.64G,隨著閾值的增大,導出鏡像的大小隨之減少.在沒有使用概率模型,即當閾值ε選取到25%以上時,此時僅僅導出了在運行過程中實際訪問到的文件,導出鏡像大小為64.5M,較原有鏡像減少了96%,這已經很難保證鏡像功能的完整性了,而當閾值選取在1%時,導出鏡像為498.12M,減少了69.5%,此時可以保留更多依賴文件.
圖10 不同閾值下導出文件數(shù)目及導出鏡像大小Fig.10 Number of exported files and size of images under different threshold values
本文提出了一種基于概率模型的Docker鏡像刪減方法,
結合鏡像本身的分層機制,加大本地鏡像的基礎鏡像層復用率,從而減少在本地存儲時的存儲開銷.同時,在導出鏡像時,使用概率模型按需構建新鏡像,一方面減少了導出鏡像的大小,另一方面提高了導出鏡像的完備性.在本文中所采用的鏡像動態(tài)分析方法和概率模型,對鏡像行為分析、鏡像安全檢測及鏡像逆向工程也有一定參考價值.
從實驗結果上看,在本地存儲的鏡像,最多可以使空間開銷節(jié)約8%.導出的鏡像同其未使用概率模型的方案相比,減小了61%,同時增大了鏡像在導出后功能的完備性.