天天看點

Docker - 這應該就是你想要的Docker架構分析

Docker - 這應該就是你想要的Docker架構分析

原文位址

  • ​​Docker:架構分解​​

Docker和傳統虛拟化方式的不同之處

下面的圖檔比較了Docker和傳統虛拟化方式的不同之處,可見容器是在作業系統層面上實作虛拟化,直接複用本地主機的作業系統,而傳統方式則是在硬體層面實作。

Docker - 這應該就是你想要的Docker架構分析
Docker - 這應該就是你想要的Docker架構分析

Docker三個基本概念

  • 鏡像(​

    ​Image​

    ​):Docker鏡像就是一個隻讀的模闆。比如,一個鏡像可以包含一個完整的Ubuntu作業系統環境。鏡像可以用來建立Docker容器。另外Docker提供了一個很簡單的機制來建立鏡像或者更新現有鏡像,使用者甚至可以直接從其他人那裡下載下傳一個已經做好的鏡像來直接使用。
  • 容器(​

    ​Container​

    ​​):鏡像(​

    ​Image​

    ​​)和容器(​

    ​Container​

    ​​)的關系,就像是程式和程序一樣,鏡像是靜态的定義,容器則是動态的定義,是鏡像運作時的實體。容器可以被建立、啟動、停止、删除、暫停等。每個容器都是互相隔離的、保證安全的平台。可以把容器看做是一個簡易版的Linux環境(包括​

    ​root​

    ​使用者權限、程序空間、使用者空間和網絡空間等)和運作在其中的應用程式。 下圖從頂層設計層面展示了鏡像和容器間的關系。一旦容器從鏡像上啟動後,二者之間就變成了互相依賴的關系,并且在鏡像上啟動的容器全部停止之前,鏡像是無法被删除的。嘗試删除鏡像而不停止或銷毀使用它的容器,會導緻出錯。
  • 倉庫(​

    ​Repository​

    ​​):倉庫是集中存放鏡像檔案的場所。有時候會把倉庫和倉庫注冊伺服器(Registry)混為一談, 并不嚴格區分。實際上,倉庫注冊伺服器上往往存放着多個倉庫,每個倉庫中又包含了多個鏡像,每個鏡像又有不同的标簽(​

    ​tag​

    ​​),如下圖所示。

    倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。最大的公開倉庫是Docker Hub, 存放了數量龐大的鏡像供使用者下載下傳。國内的公開倉庫包括Docker Pool等,可以提供大陸使用者更穩定快速的通路。當然,使用者也可以在本地網絡内建立一個私有倉庫。當使用者建立了自己的鏡像之後,就可以使用​​

    ​push​

    ​​指令将它上傳到公有或者私有倉庫,這樣下次在另外一台機器上使用這個鏡像時,隻需要從倉庫上​

    ​pull​

    ​下來就可以了。

Docker架構分解

Docker對使用者來講是一個C/S模式的架構。Docker架構圖:

Docker - 這應該就是你想要的Docker架構分析

從上圖可以知道,使用者是通過Docker Client與Docker Daemon建立通信,并發送請求給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容器就處于運作狀态,該容器擁有獨立的檔案系統,獨立并且安全的運作環境等。

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。

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架構如下圖:

Docker - 這應該就是你想要的Docker架構分析

Docker Server

Docker Server在Docker架構中是專門服務于Docker Client的​

​server​

​​。該​

​server​

​的功能是:接受并排程分發Docker Client發送的請求。

在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。

Engine

Engine是Docker架構中的運作引擎,同時也是Docker運作的核心子產品。它扮演Docker中​

​container​

​存儲倉庫的角色,并且通過執行Job的方式來操縱管理這些容器。

在Engine資料結構的設計與實作過程中,有一個​

​handler​

​​對象。該​

​handler​

​​對象存儲的都是關于衆多特定Job的​

​handler​

​​處理通路。舉例說明,Engine的​

​handler​

​​對象中有一項為:​

​{“create”: daemon.ContainerCreate}​

​​,則說明當名為​

​create​

​​的Job在運作時,執行的是​

​daemon.ContainerCreate​

​​的​

​handler​

​。

Job

一個Job可以認為是Docker架構中Engine内部最基本的工作執行單元。Docker可以做的每一項工作,都可以抽象為一個Job。例如:在容器内部運作一個程序,這是一個Job;建立一個新的容器,這是一個Job,從Internet上下載下傳一個文檔,這是一個Job;包括之前在Docker Server部分說過的,建立Server服務于HTTP的API,這也是一個Job,等等。

Job的設計者,把Job設計得與Unix程序相仿。比如說:Job有一個名稱,有參數,有環境變量,有标準的輸入輸出,有錯誤處理,有傳回狀态等。

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,這樣可以保證容器鏡像的擷取在内網完成。

Graph

Graph在Docker架構中扮演已下載下傳容器鏡像的保管者,以及已下載下傳容器鏡像之間關系的記錄者。一方面,Graph存儲着本地具有版本資訊的檔案系統鏡像,另一方面也通過GraphDB記錄着所有檔案系統鏡像彼此之間的關系。Graph的架構如下圖:

Docker - 這應該就是你想要的Docker架構分析

其中,GraphDB是一個建構在SQLite之上的小型圖資料庫,實作了節點的命名以及節點之間關聯關系的記錄。它僅僅實作了大多數圖資料庫所擁有的一個小的子集,但是提供了簡單的接口表示節點之間的關系。

同時在Graph的本地目錄中,關于每一個的容器鏡像,具體存儲的資訊有:該容器鏡像的中繼資料,容器鏡像的大小資訊,以及該容器鏡像所代表的具體​

​rootfs​

​。

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​

    ​的架構如下圖:
  • ​networkdriver​

    ​​的用途是完成Docker容器網絡環境的配置,其中包括Docker啟動時為Docker環境建立網橋;Docker容器建立時為其建立專屬虛拟網卡裝置;以及為Docker容器配置設定IP、端口并與主控端做端口映射,設定容器防火牆政策等。​

    ​networkdriver​

    ​的架構如下圖:
  • ​execdriver​

    ​​作為Docker容器的執行驅動,負責建立容器運作命名空間,負責容器資源使用的統計與限制,負責容器内部程序的真正運作等。在​

    ​execdriver​

    ​​的實作過程中,原先可以使用LXC驅動調用LXC的接口,來操縱容器的配置以及生命周期,而現在​

    ​execdriver​

    ​​預設使用​

    ​native​

    ​​驅動,不依賴于LXC。具體展現在Daemon啟動過程中加載的​

    ​ExecDriverflag​

    ​​參數,該參數在配置檔案已經被設為​

    ​native​

    ​​。這可以認為是Docker在​

    ​1.2​

    ​​版本上一個很大的改變,或者說是Docker實作跨平台的一個先兆。​

    ​execdriver​

    ​架構如下圖:

libcontainer

​libcontainer​

​是Docker架構中一個使用Go語言設計實作的庫,設計初衷是希望該庫可以不依靠任何依賴,直接通路核心中與容器相關的API。

正是由于​

​libcontainer​

​​的存在,Docker可以直接調用​

​libcontainer​

​​,而最終操縱容器的​

​namespace​

​​、​

​cgroups​

​​、​

​apparmor​

​​、網絡裝置以及防火牆規則等。這一系列操作的完成都不需要依賴LXC或者其他包。​

​libcontainer​

​架構如下圖:

Docker - 這應該就是你想要的Docker架構分析

另外,​

​libcontainer​

​​提供了一整套标準的接口來滿足上層對容器管理的需求。或者說,​

​libcontainer​

​​屏蔽了Docker上層對容器的直接管理。又由于​

​libcontainer​

​使用Go這種跨平台的語言開發實作,且本身又可以被上層多種不同的程式設計語言通路,是以很難說,未來的Docker就一定會緊緊地和Linux捆綁在一起。而于此同時,Microsoft在其著名雲計算平台Azure中,也添加了對Docker的支援,可見Docker的開放程度與業界的火熱度。

暫不談Docker,由于​

​libcontainer​

​的功能以及其本身與系統的松耦合特性,很有可能會在其他以容器為原型的平台出現,同時也很有可能催生出雲計算領域全新的項目。

Docker container

​Docker container​

​(Docker容器)是Docker架構中服務傳遞的最終展現形式。

Docker按照使用者的需求與指令,訂制相應的Docker容器。

使用者通過指定容器鏡像,使得Docker容器可以自定義​

​rootfs​

​等檔案系統; 使用者通過指定計算資源的配額,使得Docker容器使用指定的計算資源; 使用者通過配置網絡及其安全政策,使得Docker容器擁有獨立且安全的網絡環境; 使用者通過指定運作的指令,使得Docker容器執行指定的工作。

Docker - 這應該就是你想要的Docker架構分析