標籤:
原文連結 http://blog.csdn.net/shlazww/article/details/39178469
Docker源碼分析(一):Docker架構標籤: 雲端運算Docker架構2014-09-10 11:09 7491人閱讀 評論(0) 收藏 舉報本文章已收錄於: Docker知識庫 分類: Docker(14) 雲端運算(26) 系統結構(3)
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
1 背景
1.1 Docker簡介
Docker是Docker公司開源的一個基於輕量級虛擬化技術的容器引擎項目,整個項目基於Go語言開發,並遵從Apache 2.0協議。目前,Docker可以在容器內部快速自動化部署應用,並可以通過核心虛擬化技術(namespaces及cgroups等)來提供容器的資源隔離與安全保障等。由於Docker通過作業系統層的虛擬化實現隔離,所以Docker容器在運行時,不需要類似虛擬機器(VM)額外的作業系統開銷,提高資源使用率,並且提升諸如IO等方面的效能。
由於眾多新穎的特性以及項目本身的開放性,Docker在不到兩年的時間裡迅速獲得諸多廠商的青睞,其中更是包括Google、Microsoft、VMware等業界行業領導者。Google在今年六月份推出了Kubernetes,提供Docker容器的調度服務,而今年8月Microsoft宣布Azure上支援Kubernetes,隨後傳統虛擬化巨頭VMware宣布與Docker強強合作。今年9月中旬,Docker更是獲得4000萬美元的C輪融資,以推動分布式應用方面的發展。
從目前的形勢來看,Docker的前景一片大好。本系列文章從源碼的角度出發,詳細介紹Docker的架構、Docker的運行以及Docker的卓越特性。本文是Docker源碼分析系列的第一篇———Docker架構篇。
1.2 Docker版本資訊
本文關於Docker架構的分析都是基於Docker的源碼與Docker相應版本的運行結果,其中Docker為最新的1.2版本。
2 Docker架構分析內容安排
本文的目的是:在理解Docker原始碼的基礎上,分析Docker架構。分析過程中主要按照以下三個步驟進行:
* Docker的總架構圖展示
* Docker架構圖內部各模組功能與實現分析
* 以Docker命令的執行為例,進行Docker運行流程闡述
3 Docker總架構圖
學習Docker的源碼並不是一個枯燥的過程,反而可以從中理解Docker架構的設計原理。Docker對使用者來講是一個C/S模式的架構,而Docker的後端是一個非常松耦合的架構,模組各司其職,並有機組合,支撐Docker的運行。
在此,先附上Docker總架構,3.1。
圖3.1 Docker總架構圖
3.1,不難看出,使用者是使用Docker Client與Docker Daemon建立通訊,並發送請求給後者。
而Docker Daemon作為Docker架構中的主體部分,首先提供Server的功能使其可以接受Docker Client的請求;而後Engine執行Docker內部的一系列工作,每一項工作都是以一個Job的形式的存在。
Job的運行過程中,當需要容器鏡像時,則從Docker Registry中下載鏡像,並通過鏡像管理驅動graphdriver將下載鏡像以Graph的形式儲存;當需要為Docker建立網路環境時,通過網路管理驅動networkdriver建立並配置Docker容器網路環境;當需要限制Docker容器運行資源或執行使用者指令等操作時,則通過execdriver來完成。
而libcontainer是一項獨立的容器管理組件,networkdriver以及execdriver都是通過libcontainer來實現具體對容器進行的操作。
當執行完運行容器的命令後,一個實際的Docker容器就處於運行狀態,該容器擁有獨立的檔案系統,獨立並且安全的運行環境等。
4 Docker架構內各模組的功能與實現分析
接下來,我們將從Docker總架構圖入手,抽離出架構內各個模組,並對各個模組進行更為細化的架構分析與功能闡述。主要的模組有:Docker Client、Docker Daemon、Docker Registry、Graph、Driver、libcontainer以及Docker container。
4.1 Docker Client
Docker Client是Docker架構中使用者用來和Docker Daemon建立通訊的用戶端。使用者使用的可執行檔為docker,通過docker命令列工具可以發起眾多管理container的請求。
Docker Client可以通過以下三種方式和Docker Daemon建立通訊:tcp://host:port,unix://path_to_socket和fd://socketfd。為了簡單起見,本文一律使用第一種方式作為講述兩者通訊的原型。與此同時,與Docker Daemon建立串連並傳輸請求的時候,Docker Client可以通過設定命令列flag參數的形式設定安全傳輸層協議(TLS)的有關參數,保證傳輸的安全性。
Docker Client發送容器管理請求後,由Docker Daemon接受並處理請求,當Docker Client接收到返回的請求相應並簡單處理後,Docker Client一次完整的生命週期就結束了。當需要繼續發送容器管理請求時,使用者必須再次通過docker可執行檔建立Docker Client。
4.2 Docker Daemon
Docker Daemon是Docker架構中一個常駐在背景系統進程,功能是:接受並處理Docker Client發送的請求。該守護進程在後台啟動了一個Server,Server負責接受Docker Client發送的請求;接受請求後,Server通過路由與分發調度,找到相應的Handler來執行請求。
Docker Daemon啟動所使用的可執行檔也為docker,與Docker Client啟動所使用的可執行檔docker相同。在docker命令執行時,通過傳入的參數來判別Docker Daemon與Docker Client。
Docker Daemon的架構,大致可以分為以下三部分:Docker Server、Engine和Job。Daemon架構4.1。
圖4.1 Docker Daemon架構
4.2.1 Docker Server
Docker Server在Docker架構中是專門服務於Docker Client的server。該server的功能是:接受並調度分發Docker Client發送的請求。Docker Server的架構4.2。
圖4.2 Docker Server架構
在Docker的啟動過程中,通過包gorilla/mux,建立了一個mux.Router,提供請求的路由功能。在Golang中,gorilla/mux是一個強大的URL路由器以及調度分發器。該mux.Router中添加了眾多的路由項,每一個路由項由HTTP要求方法(PUT、POST、GET或DELETE)、URL、Handler三部分組成。
若Docker Client通過HTTP的形式訪問Docker Daemon,建立完mux.Router之後,Docker將Server的監聽地址以及mux.Router作為參數,建立一個httpSrv=http.Server{},最終執行httpSrv.Serve()為請求服務。
在Server的服務過程中,Server在listener上接受Docker Client的訪問請求,並建立一個全新的goroutine來服務該請求。在goroutine中,首先讀取請求內容,然後做解析工作,接著找到相應的路由項,隨後調用相應的Handler來處理該請求,最後Handler處理完請求之後回複該請求。
需要注意的是:Docker Server的運行在Docker的啟動過程中,是靠一個名為"serveapi"的job的運行來完成的。原則上,Docker Server的運行是眾多job中的一個,但是為了強調Docker Server的重要性以及為後續job服務的重要特性,將該"serveapi"的job單獨抽離出來分析,理解為Docker Server。
4.2.2 Engine
Engine是Docker架構中的運行引擎,同時也Docker啟動並執行核心模組。它扮演Docker container儲存倉庫的角色,並且通過執行job的方式來操縱管理這些容器。
在Engine資料結構的設計與實現過程中,有一個handler對象。該handlerObject Storage Service的都是關於眾多特定job的handler處理訪問。舉例說明,Engine的handler對象中有一項為:{"create": daemon.ContainerCreate,},則說明當名為"create"的job在運行時,執行的是daemon.ContainerCreate的handler。
4.2.3 Job
一個Job可以認為是Docker架構中Engine內部最基本的工作執行單元。Docker可以做的每一項工作,都可以抽象為一個job。例如:在容器內部運行一個進程,這是一個job;建立一個新的容器,這是一個job,從Internet上下載一個文檔,這是一個job;包括之前在Docker Server部分說過的,建立Server服務於HTTP的API,這也是一個job,等等。
Job的設計者,把Job設計得與Unix進程相仿。比如說:Job有一個名稱,有參數,有環境變數,有標準的輸入輸出,有錯誤處理,有返回狀態等。
4.3 Docker Registry
Docker Registry是一個儲存容器鏡像的倉庫。而容器鏡像是在容器被建立時,被載入用來初始化容器的檔案架構與目錄。
在Docker的運行過程中,Docker Daemon會與Docker Registry通訊,並實現搜尋鏡像、下載鏡像、上傳鏡像三個功能,這三個功能對應的job名稱分別為"search","pull" 與 "push"。
其中,在Docker架構中,Docker可以使用公有的Docker Registry,即大家熟知的Docker Hub,如此一來,Docker擷取容器鏡像檔案時,必須通過互連網訪問Docker Hub;同時Docker也允許使用者構建本地私人的Docker Registry,這樣可以保證容器鏡像的擷取在內網完成。
4.4 Graph
Graph在Docker架構中扮演已下載容器鏡像的保管者,以及已下載容器鏡像之間關係的記錄者。一方面,Graph儲存著本地具有版本資訊的檔案系統鏡像,另一方面也通過GraphDB記錄著所有檔案系統鏡像彼此之間的關係。Graph的架構4.3。
圖4.3 Graph架構
其中,GraphDB是一個構建在SQLite之上的小型圖資料庫,實現了節點的命名以及節點之間關聯關係的記錄。它僅僅實現了大多數圖資料庫所擁有的一個小的子集,但是提供了簡單的介面表示節點之間的關係。
同時在Graph的本地目錄中,關於每一個的容器鏡像,具體儲存的資訊有:該容器鏡像的中繼資料,容器鏡像的大小資訊,以及該容器鏡像所代表的具體rootfs。
4.5 Driver
Driver是Docker架構中的驅動模組。通過Driver驅動,Docker可以實現對Docker容器執行環境的定製。由於Docker啟動並執行生命週期中,並非使用者所有的操作都是針對Docker容器的管理,另外還有關於Docker運行資訊的擷取,Graph的儲存與記錄等。因此,為了將Docker容器的管理從Docker Daemon內部商務邏輯中區分開來,設計了Driver層驅動來接管所有這部分請求。
在Docker Driver的實現中,可以分為以下三類驅動:graphdriver、networkdriver和execdriver。
graphdriver主要用於完成容器鏡像的管理,包括儲存與擷取。即當使用者需要下載指定的容器鏡像時,graphdriver將容器鏡像儲存在本地的指定目錄;同時當使用者需要使用指定的容器鏡像來建立容器的rootfs時,graphdriver從本地鏡像儲存目錄中擷取指定的容器鏡像。
在graphdriver的初始化過程之前,有4種檔案系統或類檔案系統在其內部註冊,它們分別是aufs、btrfs、vfs和devmapper。而Docker在初始化之時,通過擷取系統內容變數”DOCKER_DRIVER”來提取所使用driver的指定類型。而之後所有的graph操作,都使用該driver來執行。
graphdriver的架構4.4:
圖4.4 graphdriver架構
networkdriver的用途是完成Docker容器網路環境的配置,其中包括Docker啟動時為Docker環境建立橋接器;Docker容器建立時為其建立專屬虛擬網卡裝置;以及為Docker容器分配IP、連接埠並與宿主機做連接埠映射,設定容器防火牆策略等。networkdriver的架構4.5:
圖4.5 networkdriver架構
execdriver作為Docker容器的執行驅動,負責建立容器運行命名空間,負責容器資源使用的統計與限制,負責容器內部進程的真正運行等。在execdriver的實現過程中,原先可以使用LXC驅動調用LXC的介面,來操縱容器的配置以及生命週期,而現在execdriver預設使用native驅動,不依賴於LXC。具體體現在Daemon啟動過程中載入的ExecDriverflag參數,該參數在設定檔已經被設為"native"。這可以認為是Docker在1.2版本上一個很大的改變,或者說Docker實現跨平台的一個先兆。execdriver架構4.6:
圖4.6 execdriver架構
4.6 libcontainer
libcontainer是Docker架構中一個使用Go語言設計實現的庫,設計初衷是希望該庫可以不依靠任何依賴,直接存取核心中與容器相關的API。
正是由於libcontainer的存在,Docker可以直接調用libcontainer,而最終操縱容器的namespace、cgroups、apparmor、網路裝置以及防火牆規則等。這一系列操作的完成都不需要依賴LXC或者其他包。libcontainer架構4.7:
圖4.7 libcontainer
另外,libcontainer提供了一整套標準的介面來滿足上層對容器管理的需求。或者說,libcontainer屏蔽了Docker上層對容器的直接管理。又由於libcontainer使用Go這種跨平台的語言開發實現,且本身又可以被上層多種不同的程式設計語言訪問,因此很難說,未來的Docker就一定會緊緊地和Linux捆綁在一起。而於此同時,Microsoft在其著名雲端運算平台Azure中,也添加了對Docker的支援,可見Docker的開放程度與業界的火熱度。
暫不談Docker,由於libcontainer的功能以及其本身與系統的松耦合特性,很有可能會在其他以容器為原型的平台出現,同時也很有可能催生出雲端運算領域全新的項目。
4.7 Docker container
Docker container(Docker容器)是Docker架構中服務交付的最終體現形式。
Docker按照使用者的需求與指令,訂製相應的Docker容器:
* 使用者通過指定容器鏡像,使得Docker容器可以自訂rootfs等檔案系統;
* 使用者通過指定計算資源的配額,使得Docker容器使用指定的計算資源;
* 使用者通過配置網路及其安全性原則,使得Docker容器擁有獨立且安全的網路環境;
* 使用者通過指定啟動並執行命令,使得Docker容器執行指定的工作。
Docker容器4.8:
圖4.8 Docker容器
5 Docker運行案例分析
上一章節著重於Docker架構中各個部分的介紹。本章的內容,將以串聯Docker各模組來簡要分析,分析原型為Docker中的docker pull與docker run兩個命令。
5.1 docker pull
docker pull命令的作用為:從Docker Registry中下載指定的容器鏡像,並儲存在本地的Graph中,以備後續建立Docker容器時的使用。docker pull命令執行流程5.1。
圖5.1 docker pull命令執行流程
,圖中標記的紅色箭頭表示docker pull命令在發起後,Docker所做的一系列運行。以下逐一分析這些步驟。
(1) Docker Client接受docker pull命令,解析完請求以及收集完請求參數之後,發送一個HTTP請求給Docker Server,HTTP要求方法為POST,請求URL為"/images/create? "+"xxx";
(2) Docker Server接受以上HTTP請求,並交給mux.Router,mux.Router通過URL以及要求方法來確定執行該請求的具體handler;
(3) mux.Router將請求路由分發至相應的handler,具體為PostImagesCreate;
(4) 在PostImageCreate這個handler之中,一個名為"pull"的job被建立,並開始執行;
(5) 名為"pull"的job在執行過程中,執行pullRepository操作,即從Docker Registry中下載相應的一個或者多個image;
(6) 名為"pull"的job將下載的image交給graphdriver;
(7) graphdriver負責將image進行儲存,一方建立graph對象,另一方面在GraphDB中記錄image之間的關係。
5.2 docker run
docker run命令的作用是在一個全新的Docker容器內部運行一條指令。Docker在執行這條命令的時候,所做工作可以分為兩部分:第一,建立Docker容器所需的rootfs;第二,建立容器的網路等運行環境,並真正運行使用者指令。因此,在整個執行流程中,Docker Client給Docker Server發送了兩次HTTP請求,第二次請求的發起取決於第一次請求的返回狀態。Docker run命令執行流程5.2。
圖5.2 docker run命令執行流程
,圖中標記的紅色箭頭表示docker run命令在發起後,Docker所做的一系列運行。以下逐一分析這些步驟。
(1) Docker Client接受docker run命令,解析完請求以及收集完請求參數之後,發送一個HTTP請求給Docker Server,HTTP要求方法為POST,請求URL為"/containers/create? "+"xxx";
(2) Docker Server接受以上HTTP請求,並交給mux.Router,mux.Router通過URL以及要求方法來確定執行該請求的具體handler;
(3) mux.Router將請求路由分發至相應的handler,具體為PostContainersCreate;
(4) 在PostImageCreate這個handler之中,一個名為"create"的job被建立,並開始讓該job運行;
(5) 名為"create"的job在運行過程中,執行Container.Create操作,該操作需要擷取容器鏡像來為Docker容器建立rootfs,即調用graphdriver;
(6) graphdriver從Graph中擷取建立Docker容器rootfs所需要的所有的鏡像;
(7) graphdriver將rootfs所有鏡像,載入安裝至Docker容器指定的檔案目錄下;
(8) 若以上操作全部正常執行,沒有返回錯誤或異常,則Docker Client收到Docker Server返回狀態之後,發起第二次HTTP請求。要求方法為"POST",請求URL為"/containers/"+container_ID+"/start";
(9) Docker Server接受以上HTTP請求,並交給mux.Router,mux.Router通過URL以及要求方法來確定執行該請求的具體handler;
(10) mux.Router將請求路由分發至相應的handler,具體為PostContainersStart;
(11) 在PostContainersStart這個handler之中,名為"start"的job被建立,並開始執行;
(12) 名為"start"的job執行完初步的配置工作後,開始配置與建立網路環境,調用networkdriver;
(13) networkdriver需要為指定的Docker容器建立網路介面裝置,並為其分配IP,port,以及設定防火牆規則,相應的操作轉交至libcontainer中的netlink包來完成;
(14) netlink完成Docker容器的網路環境配置與建立;
(15) 返回至名為"start"的job,執行完一些輔助性操作後,job開始執行使用者指令,調用execdriver;
(16) execdriver被調用,初始化Docker容器內部的運行環境,如命名空間,資源控制與隔離,以及使用者命令的執行,相應的操作轉交至libcontainer來完成;
(17) libcontainer被調用,完成Docker容器內部的運行環境初始化,並最終執行使用者要求啟動的命令。
docker 入門