這篇文章主要講解了“docker源碼分析Libcontainer”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“docker源碼分析Libcontainer”吧!
10年的淥口網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。全網(wǎng)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整淥口建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)公司從事“淥口網(wǎng)站設(shè)計”,“淥口網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
要了解Libcontainer首先要了解linux container所用到的一些基本技術(shù)。linux container是一種內(nèi)核虛擬化技術(shù),可以提供輕量級的虛擬化,以便隔離進程和資源。而這正是docker容器技術(shù)和核心,docker正是linux container的一種實現(xiàn)。linux container所用到的基本技術(shù)包括namespace、cgroup、chroot、veth、union FS、iptables和netfilter、TC、quota、setrlimit,下面對這些基本技術(shù)做一個簡要的概括:
1. Namespace:用來做資源隔離以實現(xiàn)輕量級虛擬化,包括六種namespace,UTS namespace提供了主機名和域名之間的隔離;IPC namespace提供了進程間通信的隔離;Network namespace提供了網(wǎng)絡(luò)的隔離包括網(wǎng)絡(luò)設(shè)備、網(wǎng)絡(luò)棧、端口等;Mount namespace提供了文件系統(tǒng)的隔離;User namespace提供了用戶權(quán)限間的隔離。
2. Cgroups:實現(xiàn)資源限制,可以限制、記錄任務(wù)組所使用的物理資源。還可用于優(yōu)先級分配,通過分配的CPU時間片數(shù)量及磁盤IO寬帶大小控制任務(wù)運行的優(yōu)先級。用于資源統(tǒng)計,統(tǒng)計系統(tǒng)的資源使用量,如CPU使用時長、內(nèi)存用量等。用于任務(wù)控制,可以對任務(wù)執(zhí)行掛起、控制等操作。
3. Chroot:更改root目錄,用于在container里查看到的文件系統(tǒng)。他有三大優(yōu)點:增加系統(tǒng)的安全性,限制了用戶的權(quán)利;建立一個與原系統(tǒng)隔離的系統(tǒng)目錄,這一點對容器極為重要;切換系統(tǒng)的根目錄位置。
4. Veth:把一個從網(wǎng)絡(luò)用戶空間(network namespace )發(fā)出的數(shù)據(jù)包轉(zhuǎn)發(fā)到另一個用戶空間。即實現(xiàn)容器和宿主機之間的通信。
5. Union FS:疊加的文件系統(tǒng),其中包括aufs一種支持聯(lián)合掛載的文件系統(tǒng)。
6. Iptables,netfilter:主要用來做ip數(shù)據(jù)包的過濾。
7. TC:主要用來做流量隔離,帶寬的限制。
8. Quota:用來做磁盤讀寫大小的限制,用來限制用戶可用空間的大小。
9. Setrlimit:可以限制container中打開的進程數(shù),限制打開的文件個數(shù)等。
基于上文對linux container相關(guān)技術(shù),docker基本是實現(xiàn)了前五個的技術(shù),用libcontainer做了一層封裝。也就是說docker通過libcontainer封裝了linux container的部分技術(shù),這樣使得Docker具有持續(xù)部署與測試、跨云平臺支持、環(huán)境標(biāo)準化和版本控制、高資源利用率與隔離、容器跨平臺性與鏡像、易于理解且易用以及具有應(yīng)用鏡像倉庫等優(yōu)點。Libcontainer本質(zhì)上是Docker中用于容器管理的包,基于Go語言實現(xiàn),通過管理namespace、Cgroups、capabilities以及文件系統(tǒng)等來進行容器控制。Libcontainer可用于創(chuàng)建容器并對容器進行生命周期管理。提到Libcontainer就要提到execdriver,execdriver封裝了對namespace、cgroups等對OS資源進行操作的所有方法,而Libcontainer是execdriver的默認實現(xiàn)。execdriver通過得到的command信息加載生成容器配置container,然后調(diào)用libcontainer加載容器配置container,創(chuàng)建真正的docker容器,完成容器的創(chuàng)建并對容器的生命周期進行管理。
Execdriver的工作流程如圖2.1所示:
圖2.1 execdriver的工作流程
Execdriver首先得到Docker daemon提交的command信息,提交過來的command信息包含namespace、cgroup等配置容器所需的重要信息。相對應(yīng)的command結(jié)構(gòu)體源碼如圖2.2,其中包含了生成容器所需的基本配置,有namespace相關(guān)比如UTS可提主機名和域名之間的隔離;IPC提供了進程間通信的隔離;Network提供了網(wǎng)絡(luò)的隔離包括網(wǎng)絡(luò)設(shè)備、網(wǎng)絡(luò)棧、端口等;Mount提供了文件系統(tǒng)的隔離。Resource包含了cgroup相關(guān)的信息,ProcessConfig表示容器中運行的進程的信息。
對圖2.2中的部分參數(shù)做簡要解釋,其中:ContainerPid表示容器中進程的pid;ID是容器ID,代表容器的唯一標(biāo)識,非常重要;Mount是namespace的一種用于文件系統(tǒng)的隔離;Network也是namespace的一種用于進行網(wǎng)絡(luò)的隔離;ProcessConfig描述了容器中運行的進程的信息;Resource提供了cgroup相關(guān)的信息,后面會對Resource結(jié)構(gòu)體展開做詳細的分析;Rootfs是容器的根目錄系統(tǒng);WorkingDir顧名思義是容器的工作路徑;TmpDir是用來存儲docker臨時文件的目錄;
圖2.2 command結(jié)構(gòu)體
Cgroups用于實現(xiàn)資源限制,可以限制、記錄任務(wù)組所使用的物理資源。cgroups相關(guān)信息包含在resource里,resource包含了對driver配置的所有資源的信息,resource結(jié)構(gòu)體相關(guān)定義如圖2.3,其中:memory表示所使用的存儲容量,還定義了CPU用量等cgroup所需的信息。
圖2.3 resource結(jié)構(gòu)體
ProcessConfig中包含了表示容器中運行的進程的信息,ProcessConfig結(jié)構(gòu)體相關(guān)定義源碼如圖2.4,
圖2.4ProcessConfig結(jié)構(gòu)體
圖2.1中所示的工作流程相對應(yīng)的源碼在deamon/execdriver/native/driver.go的run函數(shù)中,run函數(shù)部分截圖如圖2.5所示,其中container, err := d.createContainer(c, hooks)語句的作用是調(diào)用createContainer函數(shù)創(chuàng)建容器配置。函數(shù)傳入的參數(shù)c表示execdriver.Command,即上文提到的command結(jié)構(gòu)體,也就是說createContainer函數(shù)根據(jù)command參數(shù)創(chuàng)建相關(guān)的容器配置。
圖2.5 Run函數(shù)部分函數(shù)體
上文說到createContainer函數(shù)根據(jù)command參數(shù)創(chuàng)建相關(guān)的容器配置,下面我們看一下createContainer函數(shù)的內(nèi)部結(jié)構(gòu),如圖2.6為createContainer函數(shù)的部分結(jié)構(gòu)。其中container = execdriver.InitContainer(c)可以看到調(diào)用InitContainer函數(shù)通過傳入的execdriver.Command參數(shù)生成容器配置container。其中一系列的createXXX()方法根據(jù)InitContainer函數(shù)得到的container填充模板,配置IPC、Pid、network等所需字段。其中createIpc()表示配置Ipc提供提供了進程間通信的隔離;createPid()表示配置Pid;createUTS()表示配置UTS提供主機名和域名之間的隔離;createNetwork()配置Network提供了網(wǎng)絡(luò)的隔離包括網(wǎng)絡(luò)設(shè)備、網(wǎng)絡(luò)棧、端口等。
圖2.6 createContainer函數(shù)的部分函數(shù)體
由createContainer函數(shù)的源碼的內(nèi)部結(jié)構(gòu)可以看到在createContainer函數(shù)中首先調(diào)用InitContainer函數(shù)生成了一個叫做container的變量,InitContainer函數(shù)通過傳入的execdriver.Command參數(shù)生成容器配置container,如圖2.7是execdriver.InitContainer函數(shù)的內(nèi)部結(jié)構(gòu)。在InitContainer函數(shù)中根據(jù)command配置container的hostname主機名、cgroup、devices、rootfs等信息,最后返回一個容器配置container,這時候的返回的container其實是一個Config對象,表示容器配置。后面再由createContainer函數(shù)中的createXXX()方法根據(jù)InitContainer函數(shù)返回的container容器配置,配置相應(yīng)IPC、Pid、network等所需字段。
圖2.7 InitContainer函數(shù)的部分函數(shù)體
至此我們已經(jīng)分析完了deamon/execdriver/native/driver.go的run函數(shù)中container, err := d.createContainer(c, hooks)語句,簡單的說該語句的結(jié)果就是生成了一份container容器配置。接下來在run函數(shù)中execdriver調(diào)用libcontainer加載已經(jīng)生成好的容器配置container,創(chuàng)建真正的Docker容器。
在deamon/execdriver/native/driver.go的run函數(shù)中,成功生成container容器配置以后,工作就交由libcontainer。libcontainer的主要工作為:
1. 創(chuàng)建libcontainer構(gòu)建容器所需要使用的進程對象,即Process。對應(yīng)源碼如圖3.1所示。Process指定了容器內(nèi)進程對象的配置和IO,其中有指定若干參數(shù),并對參數(shù)賦值。Args表示將要運行的一系列指令;Env指定該進程對象的環(huán)境變量;Cwd將進程的工作目錄改至容器的rootfs中;User將為容器中的正在運行的進程設(shè)置UID和GID。
圖3.1 構(gòu)建Process
2.接下來在run函數(shù)中err := setupPipes(container, &c.ProcessConfig, p, pipes);語句調(diào)用setupPipes函數(shù)設(shè)置容器的輸出管道。而setupPipes函數(shù)即為設(shè)置容器輸出管道函數(shù),其函數(shù)體定義在deamon/execdriver/native/driver.go的setupPipes函數(shù)中。setupPipes函數(shù)主要通過execdriver.Pipes配置容器的輸出管道,其主要作用是將容器的輸出成標(biāo)準輸入、標(biāo)準輸出和標(biāo)準錯誤。
3. 使用Factory工廠類,用容器ID和容器配置container創(chuàng)建邏輯容器Container,在run函數(shù)中對應(yīng)的源碼為:d.factory.Create(c.ID, container),其中c為execdriver.Command,c.ID為容器ID,container即為之前多次提到的容器配置。在生成邏輯容器的過程中,容器配置container的各項會填充到邏輯容器Container對像的配置項config里。
4.接下來用啟動容器,啟動容器對應(yīng)的語句為cont.Start(p),其中cont為d.factory.Create(c.ID, container)函數(shù)生成的Container邏輯容器,而參數(shù)p為之前生成的容器所需要使用的進程對象Process。
5. 下面的代碼p.Wait()即為process.Wait(),表示等待之前Process的所有工作都完成,直到物理容器創(chuàng)建成功。Processd的Wait函數(shù)所對應(yīng)的源碼為圖3.2所示。
圖3.2 Process的Wait()函數(shù)
6. 最后的cont.Destroy()表示Container.Destory(),即在需要的情況下可以銷毀容器。
通過上述對libcontainer主要工作分析,我們發(fā)現(xiàn)libcontainer的重點正是Process、Container、Factory這3個邏輯實體的實現(xiàn)。其中Factory用于創(chuàng)建一個邏輯上的容器對象;Container是包含容器配置信息的邏輯容器;Process用于物理容器中進程的配置和IO管理。下面我們libcontainer中這三個邏輯實體進行詳細的解析。
Factory的作用是用給定的容器ID創(chuàng)建一個新的容器,并在該容器中啟動初始進程。并且接受的容器ID為只包含字母、數(shù)字、下劃線組成的字符創(chuàng),且長度必須在1到1024之間。容器ID不能與已經(jīng)存在的容器的ID重合,使用同一路徑(和文件系統(tǒng))的Factory創(chuàng)建的容器必須有不同的標(biāo)識。最后用一個正在運行的進程返回一個新的容器。
在這個過程中可能出現(xiàn)的錯誤有:IdInUse表示容器ID已經(jīng)被其他容器占用;InvalidIdFormat表示容器ID的格式不正確;ConfigInvalid表示配置信息無用;Systemerror表示系統(tǒng)錯誤。一但發(fā)生錯誤,那么任何已經(jīng)創(chuàng)建的容器部分都會被清除,保證了容器創(chuàng)建的原子性,要不全部創(chuàng)建成功,否則全部不創(chuàng)建。
Factory對象中包含三個函數(shù),他們分別為:
1. Create()函數(shù):其傳入?yún)?shù)為一個容器ID和一份Config類型的配置參數(shù),并且接受的容器ID為只包含字母、數(shù)字、下劃線組成的字符創(chuàng),且長度必須在1到1024之間。容器ID不能與已經(jīng)存在的容器的ID重合,使用同一路徑(和文件系統(tǒng))的Factory創(chuàng)建的容器必須有不同的標(biāo)識。根據(jù)傳入的這兩個參數(shù)創(chuàng)建并返回一個Container類,其中包括容器ID、容器工作目錄、容器配置、初始化指令和參數(shù)、以及Cgroup管理器等信息。在這個函數(shù)中Container創(chuàng)建完畢。其中可能出現(xiàn)路徑不存在、容器已經(jīng)停止、系統(tǒng)故障等錯誤。
2. Load()函數(shù):傳入?yún)?shù)為一個已經(jīng)被成功Create過的容器的容器ID,返回該容器的信息。如果容器已經(jīng)Create過說明存在id目錄,則會從id目錄下直接讀取state.json來載入容器信息。其中可能出現(xiàn)的錯誤有管道連接錯誤和系統(tǒng)故障。
3. StartInitialization()函數(shù):是容器初始化函數(shù),是Libcontainer在容器重新執(zhí)行期間會調(diào)用的內(nèi)部API。
4. Type()函數(shù):返回容器管理的類型,比如lxc或libcontainer等。
至此,F(xiàn)actory對象完成了容器的創(chuàng)建和初始化。接下來就了解一下包含包含容器配置信息的邏輯容器Container。
Container對象相當(dāng)于是邏輯容器主要包含了容器配置、控制、狀態(tài)顯示等功能。其中ID表示容器的ID。Status表示容器內(nèi)進程的狀態(tài),容器的狀態(tài)包括:Running表示容器存在并且正在運行;Pausing表示容器存在并且進程正在被停止;Paused表示容器存在但是所有的進程都被停止了;Checkpointed表示容器存在并且容器狀態(tài)都已保存至磁盤;Destoryed表示容器不存在。
Container對象中具有一系列容器相關(guān)的函數(shù)操作,其中包括:
ID():返回容器的ID,代表容器的唯一標(biāo)識
Status():返回容器內(nèi)進程的狀態(tài),可能為運行狀態(tài)也可能是停止?fàn)顟B(tài)??赡軖伋龅腻e誤為ContainerDestroyed表示容器不存在已經(jīng)被銷毀;Systemerror表示系統(tǒng)錯誤。
State():返回容器的狀態(tài)信息,包括容器ID、配置信息、初始進程ID、進程啟動時間、cgroup文件路徑、namespace路徑等??赡艹霈F(xiàn)的錯誤為Systemerror即系統(tǒng)錯誤。
Config():返回容器的配置信息
Processes():返回容器的PID,這個PID即為用來調(diào)用進程的namespace。有些PID可能不在與容器中的進程相關(guān),除非容器的狀態(tài)是PAUSED,這樣才能保證每一個PID都是有效的。
Stats():返回容器統(tǒng)計信息,包括cgroup中的統(tǒng)計以及網(wǎng)卡設(shè)備的統(tǒng)計信息。
Set():設(shè)置容器的資源配置,例如cgroup各個子系統(tǒng)的文件路徑等。
Start():在容器內(nèi)啟動一個進程,如果進程啟動失敗就返回一個錯誤??梢愿鶕?jù)以往的Process結(jié)構(gòu)追蹤進程的生命周期。其中主要工作有兩個:創(chuàng)建ParentProcess實例,執(zhí)行ParentProcess.start()來啟動物理容器。ParentProcess是一個接口其具體實現(xiàn)為initProcess對象,initProcess用于創(chuàng)建容器所需的ParentProcess,為創(chuàng)建物理容器做準備。用邏輯容器Container執(zhí)行initProcess.start(),真正的物理容器即Docker容器就生成了。
Destory():在結(jié)束所有的正在運行的進程以后銷毀容器。
Process分為兩類,一類是Process另外一類是ParentProcess。Process用于容器內(nèi)進程的配置和IO的管理,其參數(shù)包括:Args表示將要運行的一系列指令;Env指定該進程對象的環(huán)境變量;Cwd將進程的工作目錄改至容器的rootfs中;User將為容器中的正在運行的進程設(shè)置UID和GID;Stdin io.Reader表示標(biāo)準輸入;Stdout io.Writer表示標(biāo)準輸出;Stderr io.Writer表示標(biāo)準錯誤;consolePath表示到容器的控制臺的路徑;Capabilities表示容器中進程運行所需的權(quán)限;ops表示ParentProcess對象。ParentProcess負責(zé)處理容器啟動工作,包含一系列的函數(shù)動作:
pid():返回一個正在運行的進程的pid,可以通過管道從已啟動的容器進程中獲得。
start():開始容器中的執(zhí)行進程。
terminate():發(fā)送SIGKILL信號結(jié)束進程。
StartTime():獲取進程啟動時間。
signal():發(fā)送信號給進程。
wait():等待程序執(zhí)行結(jié)束,返回結(jié)束的程序狀態(tài)。
感謝各位的閱讀,以上就是“docker源碼分析Libcontainer”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對docker源碼分析Libcontainer這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!