<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC=3] ## 1\. 前言 Docker的生態系統日趨完善,開發者群體也在日趨龐大,這讓業界對Docker持續抱有極其樂觀的態度。如今,對于廣大開發者而言,使用Docker這項技術已然不是門檻,享受Docker帶來的技術福利也不再是困難。然而,如何探尋Docker適應的場景,如何發展Docker周邊的技術,以及如何彌合Docker新技術與傳統物理機或VM技術的鴻溝,已經占據Docker研究者們的思考與實踐。 本文為《Docker源碼分析》第四篇——Docker Daemon之NewDaemon實現,力求幫助廣大Docker愛好者更多得理解Docker 的核心——Docker Daemon的實現。 ## 2\. NewDaemon作用簡介 在Docker架構中有很多重要的概念,如:graph,graphdriver,execdriver,networkdriver,volumes,Docker containers等。Docker在實現過程中,需要將以上實體進行統一化管理,而Docker Daemon中的daemon實例就是設計用來完成這一任務的實體。 從源碼的角度,NewDaemon函數的執行完成了Docker Daemon創建并加載daemon的任務,最終實現統一管理Docker Daemon的資源。 ## 3\. NewDaemon源碼分析內容安排 本文從源碼角度,分析Docker Daemon加載過程中NewDaemon的實現,整個分析過程如下圖: ![](https://box.kancloud.cn/2015-11-13_56452fd64df77.png) 圖3.1 Docker Daemon中NewDaemon執行流程圖 由上圖可見,Docker Daemon中NewDaemon的執行流程主要包含12個獨立的步驟:處理配置信息、檢測系統支持及用戶權限、配置工作路徑、加載并配置graphdriver、創建Docker Daemon網絡環境、創建并初始化graphdb、創建execdriver、創建daemon實例、檢測DNS配置、加載已有container、設置shutdown處理方法、以及返回daemon實例。 下文會在NewDaemon的具體實現中,以12節分別分析以上內容。 ## 4\. NewDaemon具體實現 在《Docker源碼分析》系列第三篇中,有一個重要的環節:使用goroutine加載daemon對象并運行。在加載并運行daemon對象時,所做的第一個工作即為: ~~~ d, err := daemon.NewDaemon(daemonCfg, eng) ~~~ 該部分代碼分析如下: * 函數名:NewDaemon; * 函數調用具體實現所處的包位置:[./docker/daemon](https://github.com/docker/docker/tree/v1.2.0/daemon); * 函數具體實現源文件:[./docker/daemon/daemon.go](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L665-L671); * 函數傳入實參:daemonCfg,定義了Docker Daemon運行過程中所需的眾多配置信息;eng,在mainDaemon中創建的Engine對象實例; * 函數返回類型:d,具體的Daemon對象實例;err,錯誤狀態。 進入./docker/daemon/daemon.go中[NewDaemon的具體實現](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L665-L671),代碼如下: ~~~ func NewDaemon(config *Config, eng *engine.Engine) (*Daemon, error) { daemon, err := NewDaemonFromDirectory(config, eng) if err != nil { return nil, err } return daemon, nil } ~~~ 可見,在實現NewDaemon的過程中,通過NewDaemonFromDirectory函數來實現創建Daemon的運行環境。該函數的實現,傳入參數以及返回類型與NewDaemon函數相同。下文將大篇幅分析NewDaemonFromDirectory的實現細節。 ### 4.1\. 應用配置信息 在NewDaemonFromDirectory的實現過程中,第一個工作是:如何應用傳入的配置信息。這部分配置信息服務于Docker Daemon的運行,并在Docker Daemon啟動初期就初始化完畢。配置信息的主要功能是:供用戶自由配置Docker的可選功能,使得Docker的運行更貼近用戶期待的運行場景。 配置信息的處理包含4部分: * 配置Docker容器的MTU; * 檢測網橋配置信息; * 查驗容器通信配置; * 處理PID文件配置。 #### 4.1.1\. 配置Docker容器的MTU config信息中的Mtu應用于容器網絡的最大傳輸單元(MTU)特性。有關MTU的源碼如下: ~~~ if config.Mtu == 0 { config.Mtu = GetDefaultNetworkMtu() ~~~ 可見,若config信息中Mtu的值為0的話,則通過GetDefaultNetworkMtu函數將Mtu設定為默認的值;否則,采用config中的Mtu值。由于在默認的配置文件[./docker/daemon/config.go](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L57)(下文簡稱為默認配置文件)中,初始化時Mtu屬性值為0,故執行GetDefaultNetworkMtu。 GetDefaultNetworkMtu函數的具體實現位于[./docker/daemon/config.go](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L65-L70): ~~~ func GetDefaultNetworkMtu() int { if iface, err := networkdriver.GetDefaultRouteIface(); err == nil { return iface.MTU } return defaultNetworkMtu } ~~~ GetDefaultNetworkMtu的實現中,通過networkdriver包的GetDefaultRouteIface方法獲取具體的網絡設備,若該網絡設備存在,則返回該網絡設備的MTU屬性值;否則的話,返回默認的MTU值defaultNetworkMtu,值為1500。 #### 4.1.2\. 檢測網橋配置信息 處理完config中的Mtu屬性之后,馬上檢測config中BridgeIface和BridgeIP這兩個信息。BridgeIface和BridgeIP的作用是為創建網橋的任務”init_networkdriver”提供參數。代碼如下: ~~~ if config.BridgeIface != "" && config.BridgeIP != "" { return nil, fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one.") } ~~~ 以上代碼的含義為:若config中BridgeIface和BridgeIP兩個屬性均不為空,則返回nil對象,并返回錯誤信息,錯誤信息內容為:用戶同時指定了BridgeIface和BridgeIP,這兩個屬性屬于互斥類型,只能至多指定其中之一。而在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L51-L52)中,BridgeIface和BridgeIP均為空。 #### 4.1.3\. 查驗容器通信配置 檢測容器的通信配置,主要是針對config中的EnableIptables和InterContainerCommunication這兩個屬性。EnableIptables屬性的作用是啟用Docker對iptables規則的添加功能;InterContainerCommunication的作用是啟用Docker container之間互相通信的功能。代碼如下: ~~~ if !config.EnableIptables && !config.InterContainerCommunication { return nil, fmt.Errorf("You specified --iptables=false with --icc= false. ICC uses iptables to function. Please set --icc or --iptables to true.") } ~~~ 代碼含義為:若EnableIptables和InterContainerCommunication兩個屬性的值均為false,則返回nil對象以及錯誤信息。其中錯誤信息為:用戶將以上兩屬性均置為false,container間通信需要iptables的支持,需設置至少其中之一為true。而在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L49-L53)中,這兩個屬性的值均為true。 #### 4.1.4\. 處理網絡功能配置 接著,處理config中的DisableNetwork屬性,以備后續在創建并執行創建Docker Daemon網絡環境時使用,即在名為”init_networkdriver”的job創建并運行中體現。 ~~~ config.DisableNetwork = config.BridgeIface == DisableNetworkBridge ~~~ 由于config中的BridgeIface屬性值為空,另外DisableNetworkBridge的值為字符串”none”,因此最終config中DisableNetwork的值為false。后續名為”init_networkdriver”的job在執行過程中需要使用該屬性。 #### 4.1.5\. 處理PID文件配置 處理PID文件配置,主要工作是:為Docker Daemon進程運行時的PID號創建一個PID文件,文件的路徑即為config中的Pidfile屬性。并且為Docker Daemon的shutdown操作添加一個刪除該Pidfile的函數,以便在Docker Daemon退出的時候,可以在第一時間刪除該Pidfile。[處理PID文件配置信息](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L691-L699)的代碼實現如下: ~~~ if config.Pidfile != "" { if err := utils.CreatePidFile(config.Pidfile); err != nil { return nil, err } eng.OnShutdown(func() { utils.RemovePidFile(config.Pidfile) }) } ~~~ 代碼執行過程中,首先檢測config中的Pidfile屬性是否為空,若為空,則跳過代碼塊繼續執行;若不為空,則首先在文件系統中創建具體的Pidfile,然后向eng的onShutdown屬性添加一個處理函數,函數具體完成的工作為utils.RemovePidFile(config.Pidfile),即在Docker Daemon進行shutdown操作的時候,刪除Pidfile文件。在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L46)中,Pidfile文件的初始值為” /var/run/docker.pid”。 以上便是關于配置信息處理的分析。 ### 4.2\. 檢測系統支持及用戶權限 初步處理完Docker的配置信息之后,Docker對自身運行的環境進行了一系列的檢測,主要包括三個方面: * 操作系統類型對Docker Daemon的支持; * 用戶權限的級別; * 內核版本與處理器的支持。 系統支持與用戶權限檢測的實現較為簡單,[實現代碼](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L703-L711)如下: ~~~ if runtime.GOOS != "linux" { log.Fatalf("The Docker daemon is only supported on linux") } if os.Geteuid() != 0 { log.Fatalf("The Docker daemon needs to be run as root") } if err := checkKernelAndArch(); err != nil { log.Fatalf(err.Error()) } ~~~ 首先,通過runtime.GOOS,檢測操作系統的類型。runtime.GOOS返回運行程序所在操作系統的類型,可以是Linux,Darwin,FreeBSD等。結合具體代碼,可以發現,若操作系統不為Linux的話,將報出Fatal錯誤日志,內容為“Docker Daemon只能支持Linux操作系統”。 接著,通過os.Geteuid(),檢測程序用戶是否擁有足夠權限。os.Geteuid()返回調用者所在組的group id。結合具體代碼,也就是說,若返回不為0,則說明不是以root用戶的身份運行,報出Fatal日志。 最后,通過checkKernelAndArch(),檢測內核的版本以及主機處理器類型。checkKernelAndArch()的實現同樣位于[./docker/daemon/daemon.go](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L1097-L1119)。實現過程中,第一個工作是:檢測程序運行所在的處理器架構是否為“amd64”,而目前Docker運行時只能支持amd64的處理器架構。第二個工作是:檢測Linux內核版本是否滿足要求,而目前Docker Daemon運行所需的內核版本若過低,則必須升級至3.8.0。 ### 4.3\. 配置工作路徑 配置Docker Daemon的工作路徑,主要是創建Docker Daemon運行中所在的工作目錄。實現過程中,通過config中的Root屬性來完成。在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L47)中,Root屬性的值為”/var/lib/docker”。 在[配置工作路徑的代碼實現](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L714-L741)中,步驟如下: (1) 使用規范路徑創建一個TempDir,路徑名為tmp; (2) 通過tmp,創建一個指向tmp的文件符號連接realTmp; (3) 使用realTemp的值,創建并賦值給環境變量TMPDIR; (4) 處理config的屬性EnableSelinuxSupport; (5) 將realRoot重新賦值于config.Root,并創建Docker Daemon的工作根目錄。 ### 4.4\. 加載并配置graphdriver 加載并配置存儲驅動graphdriver,目的在于:使得Docker Daemon創建Docker鏡像管理所需的驅動環境。Graphdriver用于完成Docker容器鏡像的管理,包括存儲與獲取。 #### 4.4.1\. 創建graphdriver 這部分內容的源碼位于[./docker/daemon/daemon.go#L743-L790](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L743-L790),具體細節分析如下: ~~~ graphdriver.DefaultDriver = config.GraphDriver driver, err := graphdriver.New(config.Root, config.GraphOptions) ~~~ 首先,為graphdriver包中的DefaultDriver對象賦值,值為config中的GraphDriver屬性,在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L54)中,GraphDriver屬性的值為空;同樣的,屬性GraphOptions也為空。然后通過graphDriver中的new函數實現加載graph的存儲驅動。 創建具體的graphdriver是相當重要的一個環節,實現細節由graphdriver包中的New函數來完成。進入[./docker/daemon/graphdriver/driver.go](https://github.com/docker/docker/blob/v1.2.0/daemon/graphdriver/driver.go#L81-L111)中,實現步驟如下: 第一,遍歷數組選擇graphdriver,數組內容為os.Getenv(“DOCKER_DRIVER”)和DefaultDriver。若不為空,則通過GetDriver函數直接返回相應的Driver對象實例,若均為空,則繼續往下執行。這部分內容的作用是:讓graphdriver的加載,首先滿足用戶的自定義選擇,然后滿足默認值。代碼如下: ~~~ for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { return GetDriver(name, root, options) } } ~~~ 第二,遍歷優先級數組選擇graphdriver,優先級數組的內容為依次為”aufs”,”brtfs”,”devicemapper”和”vfs”。若依次驗證時,GetDriver成功,則直接返回相應的Driver對象實例,若均不成功,則繼續往下執行。這部分內容的作用是:在沒有指定以及默認的Driver時,從優先級數組中選擇Driver,目前優先級最高的為“aufs”。代碼如下: ~~~ for _, name := range priority { driver, err = GetDriver(name, root, options) if err != nil { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue } return nil, err } return driver, nil } ~~~ 第三,從已經注冊的drivers數組中選擇graphdriver。在”aufs”,”btrfs”,”devicemapper”和”vfs”四個不同類型driver的init函數中,它們均向graphdriver的drivers數組注冊了相應的初始化方法。分別位于[./docker/daemon/graphdriver/aufs/aufs.go](https://github.com/docker/docker/blob/v1.2.0/daemon/graphdriver/aufs/aufs.go#L49-L51),以及其他三類driver的相應位置。這部分內容的作用是:在沒有優先級drivers數組的時候,同樣可以通過注冊的driver來選擇具體的graphdriver。 #### 4.4.2\. 驗證btrfs與SELinux的兼容性 由于目前在btrfs文件系統上運行的Docker不兼容SELinux,因此當config中配置信息需要啟用SELinux的支持并且driver的類型為btrfs時,返回nil對象,并報出Fatal日志。代碼實現如下: ~~~ // As Docker on btrfs and SELinux are incompatible at present, error on both being enabled if config.EnableSelinuxSupport && driver.String() == "btrfs" { return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver!") } ~~~ #### 4.4.3\. 創建容器倉庫目錄 Docker Daemon在創建Docker容器之后,需要將容器放置于某個倉庫目錄下,統一管理。而這個目錄即為daemonRepo,值為:/var/lib/docker/containers,并通過daemonRepo創建相應的目錄。代碼實現如下: ~~~ daemonRepo := path.Join(config.Root, "containers") if err := os.MkdirAll(daemonRepo, 0700); err != nil && !os.IsExist(err) { return nil, err } ~~~ #### 4.4.4\. 遷移容器至aufs類型 當graphdriver的類型為aufs時,需要將現有graph的所有內容都遷移至aufs類型;若不為aufs,則繼續往下執行。實現代碼如下: ~~~ if err = migrateIfAufs(driver, config.Root); err != nil { return nil, err } ~~~ 這部分的遷移內容主要包括Repositories,Images以及Containers,具體實現位于[./docker/daemon/graphdriver/aufs/migrate.go](https://github.com/docker/docker/blob/v1.2.0/daemon/graphdriver/aufs/migrate.go#L39-L50)。 ~~~ func (a *Driver) Migrate(pth string, setupInit func(p string) error) error { if pathExists(path.Join(pth, "graph")) { if err := a.migrateRepositories(pth); err != nil { return err } if err := a.migrateImages(path.Join(pth, "graph")); err != nil { return err } return a.migrateContainers(path.Join(pth, "containers"), setupInit) } return nil } ~~~ migrate repositories的功能是:在Docker Daemon的root工作目錄下創建repositories-aufs的文件,存儲所有與images相關的基本信息。 migrate images的主要功能是:將原有的image鏡像都遷移至aufs driver能識別并使用的類型,包括aufs所規定的layers,diff與mnt目錄內容。 migrate container的主要功能是:將container內部的環境使用aufs driver來進行配置,包括,創建container內部的初始層(init layer),以及創建原先container內部的其他layers。 #### 4.4.5\. 創建鏡像graph 創建鏡像graph的主要工作是:在文件系統中指定的root目錄下,實例化一個全新的graph對象,作用為:存儲所有標記的文件系統鏡像,并記錄鏡像之間的關系。實現代碼如下: ~~~ g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver) ~~~ NewGraph的具體實現位于[./docker/graph/graph.go](https://github.com/docker/docker/blob/v1.2.0/graph/graph.go#L34-L53),實現過程中返回的對象為Graph類型,定義如下: ~~~ type Graph struct { Root string idIndex *truncindex.TruncIndex driver graphdriver.Driver } ~~~ 其中Root表示graph的工作根目錄,一般為”/var/lib/docker/graph”;idIndex使得檢索字符串標識符時,允許使用任意一個該字符串唯一的前綴,在這里idIndex用于通過簡短有效的字符串前綴檢索鏡像與容器的ID;最后driver表示具體的graphdriver類型。 #### 4.4.6\. 創建volumesdriver以及volumes graph 在Docker中volume的概念是:可以從Docker宿主機上掛載到Docker容器內部的特定目錄。一個volume可以被多個Docker容器掛載,從而Docker容器可以實現互相共享數據等。在實現volumes時,Docker需要使用driver來管理它,又由于volumes的管理不會像容器文件系統管理那么復雜,故Docker采用vfs驅動實現volumes的管理。代碼實現如下: ~~~ volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions) volumes, err := graph.NewGraph(path.Join(config.Root, "volumes"), volumesDriver) ~~~ 主要完成工作為:使用vfs創建volumesDriver;創建相應的volumes目錄,并返回volumes graph對象。 #### 4.4.7\. 創建TagStore TagStore主要是用于存儲鏡像的倉庫列表(repository list)。代碼如下: ~~~ repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g) ~~~ NewTagStore位于[./docker/graph/tags.go](https://github.com/docker/docker/blob/v1.2.0/graph/tags.go#L33-L54),TagStore的定義如下: ~~~ type TagStore struct { path string graph *Graph Repositories map[string]Repository sync.Mutex pullingPool map[string]chan struct{} pushingPool map[string]chan struct{} } ~~~ 需要闡述的是TagStore類型中的多個屬性的含義: * path:TagStore中記錄鏡像倉庫的文件所在路徑; * graph:相應的Graph實例對象; * Repositories:記錄具體的鏡像倉庫的map數據結構; * sync.Mutex:TagStore的互斥鎖 * pullingPool :記錄池,記錄有哪些鏡像正在被下載,若某一個鏡像正在被下載,則駁回其他Docker Client發起下載該鏡像的請求; * pushingPool:記錄池,記錄有哪些鏡像正在被上傳,若某一個鏡像正在被上傳,則駁回其他Docker Client發起上傳該鏡像的請求; ### 4.5\. 創建Docker Daemon網絡環境 創建Docker Daemon運行環境的時候,創建網絡環境是極為重要的一個部分,這不僅關系著容器對外的通信,同樣也關系著容器間的通信。 在創建網絡時,Docker Daemon是通過運行名為”init_networkdriver”的job來完成的。代碼如下: ~~~ if !config.DisableNetwork { job := eng.Job("init_networkdriver") job.SetenvBool("EnableIptables", config.EnableIptables) job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication) job.SetenvBool("EnableIpForward", config.EnableIpForward) job.Setenv("BridgeIface", config.BridgeIface) job.Setenv("BridgeIP", config.BridgeIP) job.Setenv("DefaultBindingIP", config.DefaultIp.String()) if err := job.Run(); err != nil { return nil, err } } ~~~ 分析以上源碼可知,通過config中的DisableNetwork屬性來判斷,在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L36)中,該屬性有過定義,卻沒有初始值。但是在應用配置信息中處理網絡功能配置的時候,將DisableNetwork屬性賦值為false,故判斷語句結果為真,執行相應的代碼塊。 首先創建名為”init_networkdriver”的job,隨后為該job設置環境變量,環境變量的值如下: * 環境變量EnableIptables,使用config.EnableIptables來賦值,為true; * 環境變量InterContainerCommunication,使用config.InterContainerCommunication來賦值,為true; * 環境變量EnableIpForward,使用config.EnableIpForward來賦值,值為true; * 環境變量BridgeIface,使用config.BridgeIface來賦值,為空字符串””; * 環境變量BridgeIP,使用config.BridgeIP來賦值,為空字符串””; * 環境變量DefaultBindingIP,使用config.DefaultIp.String()來賦值,為”0.0.0.0”。 設置完環境變量之后,隨即運行該job,由于在eng中key為”init_networkdriver”的handler,value為bridge.InitDriver函數,故執行bridge.InitDriver函數,具體的實現位于[./docker/daemon/networkdriver/bridge/dirver.go](https://github.com/docker/docker/blob/v1.2.0/daemon/networkdriver/bridge/driver.go#L79),作用為: * 獲取為Docker服務的網絡設備的地址; * 創建指定IP地址的網橋; * 啟用Iptables功能并配置; * 另外還為eng實例注冊了4個Handler,如 ”allocate_interface”, ”release_interface”, ”allocate_port”,”link”。 #### 4.5.1\. 創建Docker網絡設備 創建Docker網絡設備,屬于Docker Daemon創建網絡環境的第一步,實際工作是創建名為“docker0”的網橋設備。 在InitDriver函數運行過程中,首先使用job的環境變量初始化內部變量;然后根據目前網絡環境,判斷是否創建docker0網橋,若Docker專屬網橋已存在,則繼續往下執行;否則的話,創建docker0網橋。具體實現為[createBridge(bridgeIP)](https://github.com/docker/docker/blob/v1.2.0/daemon/networkdriver/bridge/driver.go#L108),以及[createBridgeIface(bridgeIface)](https://github.com/docker/docker/blob/v1.2.0/daemon/networkdriver/bridge/driver.go#L285)。 createBridge的功能是:在host主機上啟動創建指定名稱網橋設備的任務,并為該網橋設備配置一個與其他設備不沖突的網絡地址。而createBridgeIface通過系統調用負責創建具體實際的網橋設備,并設置MAC地址,通過libcontainer中netlink包的CreateBridge來實現。 #### 4.5.2\. 啟用iptables功能 創建完網橋之后,Docker Daemon為容器以及host主機配置iptables,包括為container之間所需要的link操作提供支持,為host主機上所有的對外對內流量制定傳輸規則等。代碼位于[./docker/daemon/networkdriver/bridge/driver/driver.go#L133-L137](https://github.com/docker/docker/blob/v1.2.0/daemon/networkdriver/bridge/driver.go#L133-L137),如下: ~~~ // Configure iptables for link support if enableIPTables { if err := setupIPTables(addr, icc); err != nil { return job.Error(err) } } ~~~ 其中setupIPtables的調用過程中,addr地址為Docker網橋的網絡地址,icc為true,即為允許Docker容器間互相訪問。假設網橋設備名為docker0,網橋網絡地址為docker0_ip,設置iptables規則,操作步驟如下: (1) 使用iptables工具開啟新建網橋的NAT功能,使用命令如下: ~~~ iptables -I POSTROUTING -t nat -s docker0_ip ! -o docker0 -j MASQUERADE ~~~ (2) 通過icc參數,決定是否允許container間通信,并制定相應iptables的Forward鏈。Container之間通信,說明數據包從container內發出后,經過docker0,并且還需要在docker0處發往docker0,最終轉向指定的container。換言之,從docker0出來的數據包,如果需要繼續發往docker0,則說明是container的通信數據包。命令使用如下: ~~~ iptables -I FORWARD -i docker0 -o docker0 -j ACCEPT ~~~ (3) 允許接受從container發出,且不是發往其他container數據包。換言之,允許所有從docker0發出且不是繼續發向docker0的數據包,使用命令如下: ~~~ iptables -I FORWARD -i docker0 ! -o docker0 -j ACCEPT ~~~ (4) 對于發往docker0,并且屬于已經建立的連接的數據包,Docker無條件接受這些數據包,使用命令如下: ~~~ iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT ~~~ #### 4.5.3\. 啟用系統數據包轉發功能 在Linux系統上,數據包轉發功能是被默認禁止的。數據包轉發,就是當host主機存在多塊網卡的時,如果其中一塊網卡接收到數據包,并需要將其轉發給另外的網卡。通過修改/proc/sys/net/ipv4/ip_forward的值,將其置為1,則可以保證系統內數據包可以實現轉發功能,代碼如下: ~~~ if ipForward { // Enable IPv4 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { job.Logf("WARNING: unable to enable IPv4 forwarding: %s\n", err) } } ~~~ #### 4.5.4\. 創建DOCKER鏈 在網橋設備上創建一條名為DOCKER的鏈,該鏈的作用是在創建Docker container并設置端口映射時使用。實現代碼位于[./docker/daemon/networkdriver/bridge/driver/driver.go](https://github.com/docker/docker/blob/v1.2.0/daemon/networkdriver/bridge/driver.go#L147-L157),如下: ~~~ if err := iptables.RemoveExistingChain("DOCKER"); err != nil { return job.Error(err) } if enableIPTables { chain, err := iptables.NewChain("DOCKER", bridgeIface) if err != nil { return job.Error(err) } portmapper.SetIptablesChain(chain) } ~~~ #### 4.5.5\. 注冊Handler至Engine 在創建完網橋,并配置完基本的iptables規則之后,Docker Daemon在網絡方面還在Engine中注冊了4個Handler,這些Handler的名稱與作用如下: * allocate_interface:為Docker container分配一個專屬網卡; * realease_interface:釋放網絡設備資源; * allocate_port:為Docker container分配一個端口; * link:實現Docker container間的link操作。 由于在Docker架構中,網絡是極其重要的一部分,因此Docker網絡篇會安排在《Docker源碼分析》系列的第六篇。 ### 4.6\. 創建graphdb并初始化 Graphdb是一個構建在SQLite之上的圖形數據庫,通常用來記錄節點命名以及節點之間的關聯。Docker Daemon使用graphdb來記錄鏡像之間的關聯。創建graphdb的代碼如下: ~~~ graphdbPath := path.Join(config.Root, "linkgraph.db") graph, err := graphdb.NewSqliteConn(graphdbPath) if err != nil { return nil, err } ~~~ 以上代碼首先確定graphdb的目錄為/var/lib/docker/linkgraph.db;隨后通過graphdb包內的NewSqliteConn打開graphdb,使用的驅動為”sqlite3”,數據源的名稱為” /var/lib/docker/linkgraph.db”;最后通過NewDatabase函數初始化整個graphdb,為graphdb創建entity表,edge表,并在這兩個表中初始化部分數據。NewSqliteConn函數的實現位于[./docker/pkg/graphdb/conn_sqlite3.go](https://github.com/docker/docker/blob/v1.2.0/pkg/graphdb/conn_sqlite3.go),代碼實現如下: ~~~ func NewSqliteConn(root string) (*Database, error) { …… conn, err := sql.Open("sqlite3", root) …… return NewDatabase(conn, initDatabase) } ~~~ ### 4.7\. 創建execdriver Execdriver是Docker中用來執行Docker container任務的驅動。創建并初始化graphdb之后,Docker Daemon隨即創建了execdriver,具體代碼如下: ~~~ ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo) ~~~ 可見,在創建execdriver的時候,需要4部分的信息,以下簡要介紹這4部分信息: * config.ExecDriver:Docker運行時中指定使用的exec驅動類別,在[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L55)中默認使用”native”,也可以將這個值改為”lxc”,則使用lxc接口執行Docker container內部的操作; * config.Root:Docker運行時的root路徑,[默認配置文件](https://github.com/docker/docker/blob/v1.2.0/daemon/config.go#L47)中為”/var/lib/docker”; * sysInitPath:系統上存放dockerinit二進制文件的路徑,一般為”/var/lib/docker/init/dockerinit-1.2.0”; * sysInfo:系統功能信息,包括:容器的內存限制功能,交換區內存限制功能,數據轉發功能,以及AppArmor安全功能等。 在執行execdrivers.NewDriver之前,首先通過以下代碼,獲取期望的目標dockerinit文件的路徑localPath,以及系統中dockerinit文件實際所在的路徑sysInitPath: ~~~ localCopy := path.Join(config.Root, "init", fmt.Sprintf(" dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) ~~~ 通過執行以上代碼,localCopy為”/var/lib/docker/init/dockerinit-1.2.0”,而sysyInitPath為當前Docker運行時中dockerinit-1.2.0實際所處的路徑,utils.DockerInitPath的實現位于 [./docker/utils/util.go](https://github.com/docker/docker/blob/v1.2.0/utils/utils.go#L120-L158)。若localCopy與sysyInitPath不相等,則說明當前系統中的dockerinit二進制文件,不在localCopy路徑下,需要將其拷貝至localCopy下,并對該文件設定權限。 設定完dockerinit二進制文件的位置之后,Docker Daemon創建sysinfo對象,記錄系統的功能屬性。SysInfo的定義,位于[./docker/pkg/sysinfo/sysinfo.go](https://github.com/docker/docker/blob/v1.2.0/pkg/sysinfo/sysinfo.go#L12-L17),如下: ~~~ type SysInfo struct { MemoryLimit bool SwapLimit bool IPv4ForwardingDisabled bool AppArmor bool } ~~~ 其中MemoryLimit通過判斷cgroups文件系統掛載路徑下是否均存在memory.limit_in_bytes和memory.soft_limit_in_bytes文件來賦值,若均存在,則置為true,否則置為false。SwapLimit通過判斷memory.memsw.limit_in_bytes文件來賦值,若該文件存在,則置為true,否則置為false。AppArmor通過host主機是否存在/sys/kernel/security/apparmor來判斷,若存在,則置為true,否則置為false。 執行execdrivers.NewDriver時,返回execdriver.Driver對象實例,具體代碼實現位于 [./docker/daemon/execdriver/execdrivers/execdrivers.go](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/execdrivers/execdrivers.go),由于選擇使用native作為exec驅動,故執行以下的代碼,返回最終的execdriver,其中native.NewDriver實現位于[./docker/daemon/execdriver/native/driver.go](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/native/driver.go#L45-L60): return native.NewDriver(path.Join(root, "execdriver", "native"), initPath) 至此,已經創建完畢一個execdriver的實例ed。 ### 4.8\. 創建daemon實例 Docker Daemon在經過以上諸多設置以及創建對象之后,整合眾多內容,創建最終的Daemon對象實例daemon,實現代碼如下: ~~~ daemon := &Daemon{ repository: daemonRepo, containers: &contStore{s: make(map[string]*Container)}, graph: g, repositories: repositories, idIndex: truncindex.NewTruncIndex([]string{}), sysInfo: sysInfo, volumes: volumes, config: config, containerGraph: graph, driver: driver, sysInitPath: sysInitPath, execDriver: ed, eng: eng, } ~~~ 以下分析Daemon類型的屬性: | 屬性名| 作用 | |---|---| | repository | 部署所有Docker容器的路徑 | | containers | 用于存儲具體Docker容器信息的對象 | | graph | 存儲Docker鏡像的graph對象 | | repositories | 存儲Docker鏡像元數據的文件 | | idIndex | 用于通過簡短有效的字符串前綴定位唯一的鏡像 | | sysInfo | 系統功能信息 | | volumes | 管理host主機上volumes內容的graphdriver,默認為vfs類型 | | config | Config.go文件中的配置信息,以及執行產生的配置DisableNetwork | | containerGraph | 存放Docker鏡像關系的graphdb | | driver | 管理Docker鏡像的驅動graphdriver,默認為aufs類型 | | sysInitPath | 系統dockerinit二進制文件所在的路徑 | | execDriver | Docker Daemon的exec驅動,默認為native類型 | | eng | Docker的執行引擎Engine類型實例 | ### 4.9\. 檢測DNS配置 創建完Daemon類型實例daemon之后,Docker Daemon使用daemon.checkLocaldns()檢測Docker運行環境中DNS的配置, checkLocaldns函數的定義位于[./docker/daemon/daemon.go](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L1053-L1063),代碼如下: ~~~ func (daemon *Daemon) checkLocaldns() error { resolvConf, err := resolvconf.Get() if err != nil { return err } if len(daemon.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { log.Infof("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v", DefaultDns) daemon.config.Dns = DefaultDns } return nil } ~~~ 以上代碼首先通過resolvconf.Get()方法獲取/etc/resolv.conf中的DNS服務器信息。若本地DNS 文件中有127.0.0.1,而Docker container不能使用該地址,故采用默認外在DNS服務器,為8.8.8.8,8.8.4.4,并將其賦值給config文件中的Dns屬性。 ### 4.10\. 啟動時加載已有Docker containers 當Docker Daemon啟動時,會去查看在daemon.repository,也就是在/var/lib/docker/containers中的內容。若有存在Docker container的話,則讓Docker Daemon加載這部分容器,將容器信息收集,并做相應的維護。 ### 4.11\. 設置shutdown的處理方法 加載完已有Docker container之后,Docker Daemon設置了多項在shutdown操作中需要執行的handler。也就是說:當Docker Daemon接收到特定信號,需要執行shutdown操作時,先執行這些handler完成善后工作,最終再實現shutdown。實現代碼如下: ~~~ eng.OnShutdown(func() { if err := daemon.shutdown(); err != nil { log.Errorf("daemon.shutdown(): %s", err) } if err := portallocator.ReleaseAll(); err != nil { log.Errorf("portallocator.ReleaseAll(): %s", err) } if err := daemon.driver.Cleanup(); err != nil { log.Errorf("daemon.driver.Cleanup(): %s", err.Error()) } if err := daemon.containerGraph.Close(); err != nil { log.Errorf("daemon.containerGraph.Close(): %s", err.Error()) } }) ~~~ 可知,eng對象shutdown操作執行時,需要執行以上作為參數的func(){……}函數。該函數中,主要完成4部分的操作: * 運行daemon對象的shutdown函數,做daemon方面的善后工作; * 通過portallocator.ReleaseAll(),釋放所有之前占用的端口資源; * 通過daemon.driver.Cleanup(),通過graphdriver實現unmount所有layers中的掛載點; * 通過daemon.containerGraph.Close()關閉graphdb的連接。 ### 4.12\. 返回daemon對象實例 當所有的工作完成之后,Docker Daemon返回daemon實例,并最終返回至mainDaemon()中的加載daemon的goroutine中繼續執行。 ## 5\. 總結 本文從源碼的角度深度分析了Docker Daemon啟動過程中daemon對象的創建與加載。在這一環節中涉及內容極多,本文歸納總結daemon實現的邏輯,一一深入,具體全面。 在Docker的架構中,Docker Daemon的內容是最為豐富以及全面的,而NewDaemon的實現而是涵蓋了Docker Daemon啟動過程中的絕大部分。可以認為NewDaemon是Docker Daemon實現過程中的精華所在。深入理解NewDaemon的實現,即掌握了Docker Daemon運行的來龍去脈。 ## 6\. 作者簡介 孫宏亮,[DaoCloud](http://www.daocloud.io/)初創團隊成員,軟件工程師,浙江大學VLIS實驗室應屆研究生。讀研期間活躍在PaaS和Docker開源社區,對Cloud Foundry有深入研究和豐富實踐,擅長底層平臺代碼分析,對分布式平臺的架構有一定經驗,撰寫了大量有深度的技術博客。2014年末以合伙人身份加入DaoCloud團隊,致力于傳播以Docker為主的容器的技術,推動互聯網應用的容器化步伐。郵箱:[allen.sun@daocloud.io](mailto:allen.sun@daocloud.io) ## 7\. 參考文獻 [http://docs.studygolang.com/pkg/](http://docs.studygolang.com/pkg/) [http://www.iptables.info/en/iptables-matches.html](http://www.iptables.info/en/iptables-matches.html) [https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt) [http://crosbymichael.com/the-lost-packages-of-docker.html](http://crosbymichael.com/the-lost-packages-of-docker.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看