Docker源码分析(四):Docker Daemon之NewDaemon实现
http://www.infoq.com/cn/articles/docker-source-code-analysis-part4
1. 前言
Docker的生態(tài)系統(tǒng)日趨完善,開發(fā)者群體也在日趨龐大,這讓業(yè)界對(duì)Docker持續(xù)抱有極其樂觀的態(tài)度。如今,對(duì)于廣大開發(fā)者而言,使用Docker這項(xiàng)技術(shù)已然不是門檻,享受Docker帶來的技術(shù)福利也不再是困難。然而,如何探尋Docker適應(yīng)的場(chǎng)景,如何發(fā)展Docker周邊的技術(shù),以及如何彌合Docker新技術(shù)與傳統(tǒng)物理機(jī)或VM技術(shù)的鴻溝,已經(jīng)占據(jù)Docker研究者們的思考與實(shí)踐。
本文為《Docker源碼分析》第四篇——Docker Daemon之NewDaemon實(shí)現(xiàn),力求幫助廣大Docker愛好者更多得理解Docker 的核心——Docker Daemon的實(shí)現(xiàn)。
2. NewDaemon作用簡(jiǎn)介
在Docker架構(gòu)中有很多重要的概念,如:graph,graphdriver,execdriver,networkdriver,volumes,Docker containers等。Docker在實(shí)現(xiàn)過程中,需要將以上實(shí)體進(jìn)行統(tǒng)一化管理,而Docker Daemon中的daemon實(shí)例就是設(shè)計(jì)用來完成這一任務(wù)的實(shí)體。
從源碼的角度,NewDaemon函數(shù)的執(zhí)行完成了Docker Daemon創(chuàng)建并加載daemon的任務(wù),最終實(shí)現(xiàn)統(tǒng)一管理Docker Daemon的資源。
3. NewDaemon源碼分析內(nèi)容安排
本文從源碼角度,分析Docker Daemon加載過程中NewDaemon的實(shí)現(xiàn),整個(gè)分析過程如下圖:
圖3.1 Docker Daemon中NewDaemon執(zhí)行流程圖
由上圖可見,Docker Daemon中NewDaemon的執(zhí)行流程主要包含12個(gè)獨(dú)立的步驟:處理配置信息、檢測(cè)系統(tǒng)支持及用戶權(quán)限、配置工作路徑、加載并配置graphdriver、創(chuàng)建Docker Daemon網(wǎng)絡(luò)環(huán)境、創(chuàng)建并初始化graphdb、創(chuàng)建execdriver、創(chuàng)建daemon實(shí)例、檢測(cè)DNS配置、加載已有container、設(shè)置shutdown處理方法、以及返回daemon實(shí)例。
下文會(huì)在NewDaemon的具體實(shí)現(xiàn)中,以12節(jié)分別分析以上內(nèi)容。
4. NewDaemon具體實(shí)現(xiàn)
在《Docker源碼分析》系列第三篇中,有一個(gè)重要的環(huán)節(jié):使用goroutine加載daemon對(duì)象并運(yùn)行。在加載并運(yùn)行daemon對(duì)象時(shí),所做的第一個(gè)工作即為:
d, err := daemon.NewDaemon(daemonCfg, eng)該部分代碼分析如下:
- 函數(shù)名:NewDaemon;
- 函數(shù)調(diào)用具體實(shí)現(xiàn)所處的包位置:./docker/daemon;
- 函數(shù)具體實(shí)現(xiàn)源文件:./docker/daemon/daemon.go;
- 函數(shù)傳入實(shí)參:daemonCfg,定義了Docker Daemon運(yùn)行過程中所需的眾多配置信息;eng,在mainDaemon中創(chuàng)建的Engine對(duì)象實(shí)例;
- 函數(shù)返回類型:d,具體的Daemon對(duì)象實(shí)例;err,錯(cuò)誤狀態(tài)。
進(jìn)入./docker/daemon/daemon.go中NewDaemon的具體實(shí)現(xiàn),代碼如下:
func NewDaemon(config *Config, eng *engine.Engine) (*Daemon, error) {daemon, err := NewDaemonFromDirectory(config, eng)if err != nil {return nil, err}return daemon, nil }可見,在實(shí)現(xiàn)NewDaemon的過程中,通過NewDaemonFromDirectory函數(shù)來實(shí)現(xiàn)創(chuàng)建Daemon的運(yùn)行環(huán)境。該函數(shù)的實(shí)現(xiàn),傳入?yún)?shù)以及返回類型與NewDaemon函數(shù)相同。下文將大篇幅分析NewDaemonFromDirectory的實(shí)現(xiàn)細(xì)節(jié)。
4.1. 應(yīng)用配置信息
在NewDaemonFromDirectory的實(shí)現(xiàn)過程中,第一個(gè)工作是:如何應(yīng)用傳入的配置信息。這部分配置信息服務(wù)于Docker Daemon的運(yùn)行,并在Docker Daemon啟動(dòng)初期就初始化完畢。配置信息的主要功能是:供用戶自由配置Docker的可選功能,使得Docker的運(yùn)行更貼近用戶期待的運(yùn)行場(chǎng)景。
配置信息的處理包含4部分:
- 配置Docker容器的MTU;
- 檢測(cè)網(wǎng)橋配置信息;
- 查驗(yàn)容器通信配置;
- 處理PID文件配置。
4.1.1. 配置Docker容器的MTU
config信息中的Mtu應(yīng)用于容器網(wǎng)絡(luò)的最大傳輸單元(MTU)特性。有關(guān)MTU的源碼如下:
if config.Mtu == 0 { config.Mtu = GetDefaultNetworkMtu()可見,若config信息中Mtu的值為0的話,則通過GetDefaultNetworkMtu函數(shù)將Mtu設(shè)定為默認(rèn)的值;否則,采用config中的Mtu值。由于在默認(rèn)的配置文件./docker/daemon/config.go(下文簡(jiǎn)稱為默認(rèn)配置文件)中,初始化時(shí)Mtu屬性值為0,故執(zhí)行GetDefaultNetworkMtu。
GetDefaultNetworkMtu函數(shù)的具體實(shí)現(xiàn)位于./docker/daemon/config.go:
func GetDefaultNetworkMtu() int {if iface, err := networkdriver.GetDefaultRouteIface(); err == nil {return iface.MTU}return defaultNetworkMtu }GetDefaultNetworkMtu的實(shí)現(xiàn)中,通過networkdriver包的GetDefaultRouteIface方法獲取具體的網(wǎng)絡(luò)設(shè)備,若該網(wǎng)絡(luò)設(shè)備存在,則返回該網(wǎng)絡(luò)設(shè)備的MTU屬性值;否則的話,返回默認(rèn)的MTU值defaultNetworkMtu,值為1500。
4.1.2. 檢測(cè)網(wǎng)橋配置信息
處理完config中的Mtu屬性之后,馬上檢測(cè)config中BridgeIface和BridgeIP這兩個(gè)信息。BridgeIface和BridgeIP的作用是為創(chuàng)建網(wǎng)橋的任務(wù)”init_networkdriver”提供參數(shù)。代碼如下:
if config.BridgeIface != "" && config.BridgeIP != "" {return nil, fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one.") }以上代碼的含義為:若config中BridgeIface和BridgeIP兩個(gè)屬性均不為空,則返回nil對(duì)象,并返回錯(cuò)誤信息,錯(cuò)誤信息內(nèi)容為:用戶同時(shí)指定了BridgeIface和BridgeIP,這兩個(gè)屬性屬于互斥類型,只能至多指定其中之一。而在默認(rèn)配置文件中,BridgeIface和BridgeIP均為空。
4.1.3. 查驗(yàn)容器通信配置
檢測(cè)容器的通信配置,主要是針對(duì)config中的EnableIptables和InterContainerCommunication這兩個(gè)屬性。EnableIptables屬性的作用是啟用Docker對(duì)iptables規(guī)則的添加功能;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兩個(gè)屬性的值均為false,則返回nil對(duì)象以及錯(cuò)誤信息。其中錯(cuò)誤信息為:用戶將以上兩屬性均置為false,container間通信需要iptables的支持,需設(shè)置至少其中之一為true。而在默認(rèn)配置文件中,這兩個(gè)屬性的值均為true。
4.1.4. 處理網(wǎng)絡(luò)功能配置
接著,處理config中的DisableNetwork屬性,以備后續(xù)在創(chuàng)建并執(zhí)行創(chuàng)建Docker Daemon網(wǎng)絡(luò)環(huán)境時(shí)使用,即在名為”init_networkdriver”的job創(chuàng)建并運(yùn)行中體現(xiàn)。
config.DisableNetwork = config.BridgeIface == DisableNetworkBridge由于config中的BridgeIface屬性值為空,另外DisableNetworkBridge的值為字符串”none”,因此最終config中DisableNetwork的值為false。后續(xù)名為”init_networkdriver”的job在執(zhí)行過程中需要使用該屬性。
4.1.5. 處理PID文件配置
處理PID文件配置,主要工作是:為Docker Daemon進(jìn)程運(yùn)行時(shí)的PID號(hào)創(chuàng)建一個(gè)PID文件,文件的路徑即為config中的Pidfile屬性。并且為Docker Daemon的shutdown操作添加一個(gè)刪除該P(yáng)idfile的函數(shù),以便在Docker Daemon退出的時(shí)候,可以在第一時(shí)間刪除該P(yáng)idfile。處理PID文件配置信息的代碼實(shí)現(xiàn)如下:
if config.Pidfile != "" {if err := utils.CreatePidFile(config.Pidfile); err != nil {return nil, err}eng.OnShutdown(func() {utils.RemovePidFile(config.Pidfile)}) }代碼執(zhí)行過程中,首先檢測(cè)config中的Pidfile屬性是否為空,若為空,則跳過代碼塊繼續(xù)執(zhí)行;若不為空,則首先在文件系統(tǒng)中創(chuàng)建具體的Pidfile,然后向eng的onShutdown屬性添加一個(gè)處理函數(shù),函數(shù)具體完成的工作為utils.RemovePidFile(config.Pidfile),即在Docker Daemon進(jìn)行shutdown操作的時(shí)候,刪除Pidfile文件。在默認(rèn)配置文件中,Pidfile文件的初始值為” /var/run/docker.pid”。
以上便是關(guān)于配置信息處理的分析。
4.2. 檢測(cè)系統(tǒng)支持及用戶權(quán)限
初步處理完Docker的配置信息之后,Docker對(duì)自身運(yùn)行的環(huán)境進(jìn)行了一系列的檢測(cè),主要包括三個(gè)方面:
- 操作系統(tǒng)類型對(duì)Docker Daemon的支持;
- 用戶權(quán)限的級(jí)別;
- 內(nèi)核版本與處理器的支持。
系統(tǒng)支持與用戶權(quán)限檢測(cè)的實(shí)現(xiàn)較為簡(jiǎn)單,實(shí)現(xiàn)代碼如下:
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,檢測(cè)操作系統(tǒng)的類型。runtime.GOOS返回運(yùn)行程序所在操作系統(tǒng)的類型,可以是Linux,Darwin,FreeBSD等。結(jié)合具體代碼,可以發(fā)現(xiàn),若操作系統(tǒng)不為L(zhǎng)inux的話,將報(bào)出Fatal錯(cuò)誤日志,內(nèi)容為“Docker Daemon只能支持Linux操作系統(tǒng)”。
接著,通過os.Geteuid(),檢測(cè)程序用戶是否擁有足夠權(quán)限。os.Geteuid()返回調(diào)用者所在組的group id。結(jié)合具體代碼,也就是說,若返回不為0,則說明不是以root用戶的身份運(yùn)行,報(bào)出Fatal日志。
最后,通過checkKernelAndArch(),檢測(cè)內(nèi)核的版本以及主機(jī)處理器類型。checkKernelAndArch()的實(shí)現(xiàn)同樣位于./docker/daemon/daemon.go。實(shí)現(xiàn)過程中,第一個(gè)工作是:檢測(cè)程序運(yùn)行所在的處理器架構(gòu)是否為“amd64”,而目前Docker運(yùn)行時(shí)只能支持amd64的處理器架構(gòu)。第二個(gè)工作是:檢測(cè)Linux內(nèi)核版本是否滿足要求,而目前Docker Daemon運(yùn)行所需的內(nèi)核版本若過低,則必須升級(jí)至3.8.0。
4.3. 配置工作路徑
配置Docker Daemon的工作路徑,主要是創(chuàng)建Docker Daemon運(yùn)行中所在的工作目錄。實(shí)現(xiàn)過程中,通過config中的Root屬性來完成。在默認(rèn)配置文件中,Root屬性的值為”/var/lib/docker”。
在配置工作路徑的代碼實(shí)現(xiàn)中,步驟如下:
(1) 使用規(guī)范路徑創(chuàng)建一個(gè)TempDir,路徑名為tmp;
(2) 通過tmp,創(chuàng)建一個(gè)指向tmp的文件符號(hào)連接realTmp;
(3) 使用realTemp的值,創(chuàng)建并賦值給環(huán)境變量TMPDIR;
(4) 處理config的屬性EnableSelinuxSupport;
(5) 將realRoot重新賦值于config.Root,并創(chuàng)建Docker Daemon的工作根目錄。
4.4. 加載并配置graphdriver
加載并配置存儲(chǔ)驅(qū)動(dòng)graphdriver,目的在于:使得Docker Daemon創(chuàng)建Docker鏡像管理所需的驅(qū)動(dòng)環(huán)境。Graphdriver用于完成Docker容器鏡像的管理,包括存儲(chǔ)與獲取。
4.4.1. 創(chuàng)建graphdriver
這部分內(nèi)容的源碼位于./docker/daemon/daemon.go#L743-L790,具體細(xì)節(jié)分析如下:
graphdriver.DefaultDriver = config.GraphDriver driver, err := graphdriver.New(config.Root, config.GraphOptions)首先,為graphdriver包中的DefaultDriver對(duì)象賦值,值為config中的GraphDriver屬性,在默認(rèn)配置文件中,GraphDriver屬性的值為空;同樣的,屬性GraphOptions也為空。然后通過graphDriver中的new函數(shù)實(shí)現(xiàn)加載graph的存儲(chǔ)驅(qū)動(dòng)。
創(chuàng)建具體的graphdriver是相當(dāng)重要的一個(gè)環(huán)節(jié),實(shí)現(xiàn)細(xì)節(jié)由graphdriver包中的New函數(shù)來完成。進(jìn)入./docker/daemon/graphdriver/driver.go中,實(shí)現(xiàn)步驟如下:
第一,遍歷數(shù)組選擇graphdriver,數(shù)組內(nèi)容為os.Getenv(“DOCKER_DRIVER”)和DefaultDriver。若不為空,則通過GetDriver函數(shù)直接返回相應(yīng)的Driver對(duì)象實(shí)例,若均為空,則繼續(xù)往下執(zhí)行。這部分內(nèi)容的作用是:讓graphdriver的加載,首先滿足用戶的自定義選擇,然后滿足默認(rèn)值。代碼如下:
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {if name != "" {return GetDriver(name, root, options)} }第二,遍歷優(yōu)先級(jí)數(shù)組選擇graphdriver,優(yōu)先級(jí)數(shù)組的內(nèi)容為依次為”aufs”,”brtfs”,”devicemapper”和”vfs”。若依次驗(yàn)證時(shí),GetDriver成功,則直接返回相應(yīng)的Driver對(duì)象實(shí)例,若均不成功,則繼續(xù)往下執(zhí)行。這部分內(nèi)容的作用是:在沒有指定以及默認(rèn)的Driver時(shí),從優(yōu)先級(jí)數(shù)組中選擇Driver,目前優(yōu)先級(jí)最高的為“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 }第三,從已經(jīng)注冊(cè)的drivers數(shù)組中選擇graphdriver。在”aufs”,”btrfs”,”devicemapper”和”vfs”四個(gè)不同類型driver的init函數(shù)中,它們均向graphdriver的drivers數(shù)組注冊(cè)了相應(yīng)的初始化方法。分別位于./docker/daemon/graphdriver/aufs/aufs.go,以及其他三類driver的相應(yīng)位置。這部分內(nèi)容的作用是:在沒有優(yōu)先級(jí)drivers數(shù)組的時(shí)候,同樣可以通過注冊(cè)的driver來選擇具體的graphdriver。
4.4.2. 驗(yàn)證btrfs與SELinux的兼容性
由于目前在btrfs文件系統(tǒng)上運(yùn)行的Docker不兼容SELinux,因此當(dāng)config中配置信息需要啟用SELinux的支持并且driver的類型為btrfs時(shí),返回nil對(duì)象,并報(bào)出Fatal日志。代碼實(shí)現(xiàn)如下:
// 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. 創(chuàng)建容器倉(cāng)庫(kù)目錄
Docker Daemon在創(chuàng)建Docker容器之后,需要將容器放置于某個(gè)倉(cāng)庫(kù)目錄下,統(tǒng)一管理。而這個(gè)目錄即為daemonRepo,值為:/var/lib/docker/containers,并通過daemonRepo創(chuàng)建相應(yīng)的目錄。代碼實(shí)現(xiàn)如下:
daemonRepo := path.Join(config.Root, "containers") if err := os.MkdirAll(daemonRepo, 0700); err != nil && !os.IsExist(err) {return nil, err }4.4.4. 遷移容器至aufs類型
當(dāng)graphdriver的類型為aufs時(shí),需要將現(xiàn)有g(shù)raph的所有內(nèi)容都遷移至aufs類型;若不為aufs,則繼續(xù)往下執(zhí)行。實(shí)現(xiàn)代碼如下:
if err = migrateIfAufs(driver, config.Root); err != nil { return nil, err }這部分的遷移內(nèi)容主要包括Repositories,Images以及Containers,具體實(shí)現(xiàn)位于./docker/daemon/graphdriver/aufs/migrate.go。
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工作目錄下創(chuàng)建repositories-aufs的文件,存儲(chǔ)所有與images相關(guān)的基本信息。
migrate images的主要功能是:將原有的image鏡像都遷移至aufs driver能識(shí)別并使用的類型,包括aufs所規(guī)定的layers,diff與mnt目錄內(nèi)容。
migrate container的主要功能是:將container內(nèi)部的環(huán)境使用aufs driver來進(jìn)行配置,包括,創(chuàng)建container內(nèi)部的初始層(init layer),以及創(chuàng)建原先container內(nèi)部的其他layers。
4.4.5. 創(chuàng)建鏡像graph
創(chuàng)建鏡像graph的主要工作是:在文件系統(tǒng)中指定的root目錄下,實(shí)例化一個(gè)全新的graph對(duì)象,作用為:存儲(chǔ)所有標(biāo)記的文件系統(tǒng)鏡像,并記錄鏡像之間的關(guān)系。實(shí)現(xiàn)代碼如下:
g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver)NewGraph的具體實(shí)現(xiàn)位于./docker/graph/graph.go,實(shí)現(xiàn)過程中返回的對(duì)象為Graph類型,定義如下:
type Graph struct {Root stringidIndex *truncindex.TruncIndexdriver graphdriver.Driver }其中Root表示graph的工作根目錄,一般為”/var/lib/docker/graph”;idIndex使得檢索字符串標(biāo)識(shí)符時(shí),允許使用任意一個(gè)該字符串唯一的前綴,在這里idIndex用于通過簡(jiǎn)短有效的字符串前綴檢索鏡像與容器的ID;最后driver表示具體的graphdriver類型。
4.4.6. 創(chuàng)建volumesdriver以及volumes graph
在Docker中volume的概念是:可以從Docker宿主機(jī)上掛載到Docker容器內(nèi)部的特定目錄。一個(gè)volume可以被多個(gè)Docker容器掛載,從而Docker容器可以實(shí)現(xiàn)互相共享數(shù)據(jù)等。在實(shí)現(xiàn)volumes時(shí),Docker需要使用driver來管理它,又由于volumes的管理不會(huì)像容器文件系統(tǒng)管理那么復(fù)雜,故Docker采用vfs驅(qū)動(dòng)實(shí)現(xiàn)volumes的管理。代碼實(shí)現(xiàn)如下:
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions) volumes, err := graph.NewGraph(path.Join(config.Root, "volumes"), volumesDriver)主要完成工作為:使用vfs創(chuàng)建volumesDriver;創(chuàng)建相應(yīng)的volumes目錄,并返回volumes graph對(duì)象。
4.4.7. 創(chuàng)建TagStore
TagStore主要是用于存儲(chǔ)鏡像的倉(cāng)庫(kù)列表(repository list)。代碼如下:
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)NewTagStore位于./docker/graph/tags.go,TagStore的定義如下:
type TagStore struct {path stringgraph *GraphRepositories map[string]Repositorysync.MutexpullingPool map[string]chan struct{}pushingPool map[string]chan struct{} }需要闡述的是TagStore類型中的多個(gè)屬性的含義:
- path:TagStore中記錄鏡像倉(cāng)庫(kù)的文件所在路徑;
- graph:相應(yīng)的Graph實(shí)例對(duì)象;
- Repositories:記錄具體的鏡像倉(cāng)庫(kù)的map數(shù)據(jù)結(jié)構(gòu);
- sync.Mutex:TagStore的互斥鎖
- pullingPool :記錄池,記錄有哪些鏡像正在被下載,若某一個(gè)鏡像正在被下載,則駁回其他Docker Client發(fā)起下載該鏡像的請(qǐng)求;
- pushingPool:記錄池,記錄有哪些鏡像正在被上傳,若某一個(gè)鏡像正在被上傳,則駁回其他Docker Client發(fā)起上傳該鏡像的請(qǐng)求;
4.5. 創(chuàng)建Docker Daemon網(wǎng)絡(luò)環(huán)境
創(chuàng)建Docker Daemon運(yùn)行環(huán)境的時(shí)候,創(chuàng)建網(wǎng)絡(luò)環(huán)境是極為重要的一個(gè)部分,這不僅關(guān)系著容器對(duì)外的通信,同樣也關(guān)系著容器間的通信。
在創(chuàng)建網(wǎng)絡(luò)時(shí),Docker Daemon是通過運(yùn)行名為”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屬性來判斷,在默認(rèn)配置文件中,該屬性有過定義,卻沒有初始值。但是在應(yīng)用配置信息中處理網(wǎng)絡(luò)功能配置的時(shí)候,將DisableNetwork屬性賦值為false,故判斷語(yǔ)句結(jié)果為真,執(zhí)行相應(yīng)的代碼塊。
首先創(chuàng)建名為”init_networkdriver”的job,隨后為該job設(shè)置環(huán)境變量,環(huán)境變量的值如下:
- 環(huán)境變量EnableIptables,使用config.EnableIptables來賦值,為true;
- 環(huán)境變量InterContainerCommunication,使用config.InterContainerCommunication來賦值,為true;
- 環(huán)境變量EnableIpForward,使用config.EnableIpForward來賦值,值為true;
- 環(huán)境變量BridgeIface,使用config.BridgeIface來賦值,為空字符串””;
- 環(huán)境變量BridgeIP,使用config.BridgeIP來賦值,為空字符串””;
- 環(huán)境變量DefaultBindingIP,使用config.DefaultIp.String()來賦值,為”0.0.0.0”。
設(shè)置完環(huán)境變量之后,隨即運(yùn)行該job,由于在eng中key為”init_networkdriver”的handler,value為bridge.InitDriver函數(shù),故執(zhí)行bridge.InitDriver函數(shù),具體的實(shí)現(xiàn)位于./docker/daemon/networkdriver/bridge/dirver.go,作用為:
- 獲取為Docker服務(wù)的網(wǎng)絡(luò)設(shè)備的地址;
- 創(chuàng)建指定IP地址的網(wǎng)橋;
- 啟用Iptables功能并配置;
- 另外還為eng實(shí)例注冊(cè)了4個(gè)Handler,如 ”allocate_interface”, ”release_interface”, ”allocate_port”,”link”。
4.5.1. 創(chuàng)建Docker網(wǎng)絡(luò)設(shè)備
創(chuàng)建Docker網(wǎng)絡(luò)設(shè)備,屬于Docker Daemon創(chuàng)建網(wǎng)絡(luò)環(huán)境的第一步,實(shí)際工作是創(chuàng)建名為“docker0”的網(wǎng)橋設(shè)備。
在InitDriver函數(shù)運(yùn)行過程中,首先使用job的環(huán)境變量初始化內(nèi)部變量;然后根據(jù)目前網(wǎng)絡(luò)環(huán)境,判斷是否創(chuàng)建docker0網(wǎng)橋,若Docker專屬網(wǎng)橋已存在,則繼續(xù)往下執(zhí)行;否則的話,創(chuàng)建docker0網(wǎng)橋。具體實(shí)現(xiàn)為createBridge(bridgeIP),以及createBridgeIface(bridgeIface)。
createBridge的功能是:在host主機(jī)上啟動(dòng)創(chuàng)建指定名稱網(wǎng)橋設(shè)備的任務(wù),并為該網(wǎng)橋設(shè)備配置一個(gè)與其他設(shè)備不沖突的網(wǎng)絡(luò)地址。而createBridgeIface通過系統(tǒng)調(diào)用負(fù)責(zé)創(chuàng)建具體實(shí)際的網(wǎng)橋設(shè)備,并設(shè)置MAC地址,通過libcontainer中netlink包的CreateBridge來實(shí)現(xiàn)。
4.5.2. 啟用iptables功能
創(chuàng)建完網(wǎng)橋之后,Docker Daemon為容器以及host主機(jī)配置iptables,包括為container之間所需要的link操作提供支持,為host主機(jī)上所有的對(duì)外對(duì)內(nèi)流量制定傳輸規(guī)則等。代碼位于./docker/daemon/networkdriver/bridge/driver/driver.go#L133-L137,如下:
// Configure iptables for link support if enableIPTables {if err := setupIPTables(addr, icc); err != nil {return job.Error(err)} }其中setupIPtables的調(diào)用過程中,addr地址為Docker網(wǎng)橋的網(wǎng)絡(luò)地址,icc為true,即為允許Docker容器間互相訪問。假設(shè)網(wǎng)橋設(shè)備名為docker0,網(wǎng)橋網(wǎng)絡(luò)地址為docker0_ip,設(shè)置iptables規(guī)則,操作步驟如下:
(1) 使用iptables工具開啟新建網(wǎng)橋的NAT功能,使用命令如下:
iptables -I POSTROUTING -t nat -s docker0_ip ! -o docker0 -j MASQUERADE(2) 通過icc參數(shù),決定是否允許container間通信,并制定相應(yīng)iptables的Forward鏈。Container之間通信,說明數(shù)據(jù)包從container內(nèi)發(fā)出后,經(jīng)過docker0,并且還需要在docker0處發(fā)往docker0,最終轉(zhuǎn)向指定的container。換言之,從docker0出來的數(shù)據(jù)包,如果需要繼續(xù)發(fā)往docker0,則說明是container的通信數(shù)據(jù)包。命令使用如下:
iptables -I FORWARD -i docker0 -o docker0 -j ACCEPT(3) 允許接受從container發(fā)出,且不是發(fā)往其他container數(shù)據(jù)包。換言之,允許所有從docker0發(fā)出且不是繼續(xù)發(fā)向docker0的數(shù)據(jù)包,使用命令如下:
iptables -I FORWARD -i docker0 ! -o docker0 -j ACCEPT(4) 對(duì)于發(fā)往docker0,并且屬于已經(jīng)建立的連接的數(shù)據(jù)包,Docker無條件接受這些數(shù)據(jù)包,使用命令如下:
iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT4.5.3. 啟用系統(tǒng)數(shù)據(jù)包轉(zhuǎn)發(fā)功能
在Linux系統(tǒng)上,數(shù)據(jù)包轉(zhuǎn)發(fā)功能是被默認(rèn)禁止的。數(shù)據(jù)包轉(zhuǎn)發(fā),就是當(dāng)host主機(jī)存在多塊網(wǎng)卡的時(shí),如果其中一塊網(wǎng)卡接收到數(shù)據(jù)包,并需要將其轉(zhuǎn)發(fā)給另外的網(wǎng)卡。通過修改/proc/sys/net/ipv4/ip_forward的值,將其置為1,則可以保證系統(tǒng)內(nèi)數(shù)據(jù)包可以實(shí)現(xiàn)轉(zhuǎn)發(fā)功能,代碼如下:
if ipForward {// Enable IPv4 forwardingif 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. 創(chuàng)建DOCKER鏈
在網(wǎng)橋設(shè)備上創(chuàng)建一條名為DOCKER的鏈,該鏈的作用是在創(chuàng)建Docker container并設(shè)置端口映射時(shí)使用。實(shí)現(xiàn)代碼位于./docker/daemon/networkdriver/bridge/driver/driver.go,如下:
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. 注冊(cè)Handler至Engine
在創(chuàng)建完網(wǎng)橋,并配置完基本的iptables規(guī)則之后,Docker Daemon在網(wǎng)絡(luò)方面還在Engine中注冊(cè)了4個(gè)Handler,這些Handler的名稱與作用如下:
- allocate_interface:為Docker container分配一個(gè)專屬網(wǎng)卡;
- realease_interface:釋放網(wǎng)絡(luò)設(shè)備資源;
- allocate_port:為Docker container分配一個(gè)端口;
- link:實(shí)現(xiàn)Docker container間的link操作。
由于在Docker架構(gòu)中,網(wǎng)絡(luò)是極其重要的一部分,因此Docker網(wǎng)絡(luò)篇會(huì)安排在《Docker源碼分析》系列的第六篇。
4.6. 創(chuàng)建graphdb并初始化
Graphdb是一個(gè)構(gòu)建在SQLite之上的圖形數(shù)據(jù)庫(kù),通常用來記錄節(jié)點(diǎn)命名以及節(jié)點(diǎn)之間的關(guān)聯(lián)。Docker Daemon使用graphdb來記錄鏡像之間的關(guān)聯(lián)。創(chuàng)建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包內(nèi)的NewSqliteConn打開graphdb,使用的驅(qū)動(dòng)為”sqlite3”,數(shù)據(jù)源的名稱為” /var/lib/docker/linkgraph.db”;最后通過NewDatabase函數(shù)初始化整個(gè)graphdb,為graphdb創(chuàng)建entity表,edge表,并在這兩個(gè)表中初始化部分?jǐn)?shù)據(jù)。NewSqliteConn函數(shù)的實(shí)現(xiàn)位于./docker/pkg/graphdb/conn_sqlite3.go,代碼實(shí)現(xiàn)如下:
func NewSqliteConn(root string) (*Database, error) {……conn, err := sql.Open("sqlite3", root)……return NewDatabase(conn, initDatabase) }4.7. 創(chuàng)建execdriver
Execdriver是Docker中用來執(zhí)行Docker container任務(wù)的驅(qū)動(dòng)。創(chuàng)建并初始化graphdb之后,Docker Daemon隨即創(chuàng)建了execdriver,具體代碼如下:
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)可見,在創(chuàng)建execdriver的時(shí)候,需要4部分的信息,以下簡(jiǎn)要介紹這4部分信息:
- config.ExecDriver:Docker運(yùn)行時(shí)中指定使用的exec驅(qū)動(dòng)類別,在默認(rèn)配置文件中默認(rèn)使用”native”,也可以將這個(gè)值改為”lxc”,則使用lxc接口執(zhí)行Docker container內(nèi)部的操作;
- config.Root:Docker運(yùn)行時(shí)的root路徑,默認(rèn)配置文件中為”/var/lib/docker”;
- sysInitPath:系統(tǒng)上存放dockerinit二進(jìn)制文件的路徑,一般為”/var/lib/docker/init/dockerinit-1.2.0”;
- sysInfo:系統(tǒng)功能信息,包括:容器的內(nèi)存限制功能,交換區(qū)內(nèi)存限制功能,數(shù)據(jù)轉(zhuǎn)發(fā)功能,以及AppArmor安全功能等。
在執(zhí)行execdrivers.NewDriver之前,首先通過以下代碼,獲取期望的目標(biāo)dockerinit文件的路徑localPath,以及系統(tǒng)中dockerinit文件實(shí)際所在的路徑sysInitPath:
localCopy := path.Join(config.Root, "init", fmt.Sprintf(" dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy)通過執(zhí)行以上代碼,localCopy為”/var/lib/docker/init/dockerinit-1.2.0”,而sysyInitPath為當(dāng)前Docker運(yùn)行時(shí)中dockerinit-1.2.0實(shí)際所處的路徑,utils.DockerInitPath的實(shí)現(xiàn)位于?./docker/utils/util.go。若localCopy與sysyInitPath不相等,則說明當(dāng)前系統(tǒng)中的dockerinit二進(jìn)制文件,不在localCopy路徑下,需要將其拷貝至localCopy下,并對(duì)該文件設(shè)定權(quán)限。
設(shè)定完dockerinit二進(jìn)制文件的位置之后,Docker Daemon創(chuàng)建sysinfo對(duì)象,記錄系統(tǒng)的功能屬性。SysInfo的定義,位于./docker/pkg/sysinfo/sysinfo.go,如下:
type SysInfo struct {MemoryLimit boolSwapLimit boolIPv4ForwardingDisabled boolAppArmor bool }其中MemoryLimit通過判斷cgroups文件系統(tǒng)掛載路徑下是否均存在memory.limit_in_bytes和memory.soft_limit_in_bytes文件來賦值,若均存在,則置為true,否則置為false。SwapLimit通過判斷memory.memsw.limit_in_bytes文件來賦值,若該文件存在,則置為true,否則置為false。AppArmor通過host主機(jī)是否存在/sys/kernel/security/apparmor來判斷,若存在,則置為true,否則置為false。
執(zhí)行execdrivers.NewDriver時(shí),返回execdriver.Driver對(duì)象實(shí)例,具體代碼實(shí)現(xiàn)位于?./docker/daemon/execdriver/execdrivers/execdrivers.go,由于選擇使用native作為exec驅(qū)動(dòng),故執(zhí)行以下的代碼,返回最終的execdriver,其中native.NewDriver實(shí)現(xiàn)位于./docker/daemon/execdriver/native/driver.go:
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
至此,已經(jīng)創(chuàng)建完畢一個(gè)execdriver的實(shí)例ed。
4.8. 創(chuàng)建daemon實(shí)例
Docker Daemon在經(jīng)過以上諸多設(shè)置以及創(chuàng)建對(duì)象之后,整合眾多內(nèi)容,創(chuàng)建最終的Daemon對(duì)象實(shí)例daemon,實(shí)現(xiàn)代碼如下:
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 | 用于存儲(chǔ)具體Docker容器信息的對(duì)象 |
| graph | 存儲(chǔ)Docker鏡像的graph對(duì)象 |
| repositories | 存儲(chǔ)Docker鏡像元數(shù)據(jù)的文件 |
| idIndex | 用于通過簡(jiǎn)短有效的字符串前綴定位唯一的鏡像 |
| sysInfo | 系統(tǒng)功能信息 |
| volumes | 管理host主機(jī)上volumes內(nèi)容的graphdriver,默認(rèn)為vfs類型 |
| config | Config.go文件中的配置信息,以及執(zhí)行產(chǎn)生的配置DisableNetwork |
| containerGraph | 存放Docker鏡像關(guān)系的graphdb |
| driver | 管理Docker鏡像的驅(qū)動(dòng)graphdriver,默認(rèn)為aufs類型 |
| sysInitPath | 系統(tǒng)dockerinit二進(jìn)制文件所在的路徑 |
| execDriver | Docker Daemon的exec驅(qū)動(dòng),默認(rèn)為native類型 |
| eng | Docker的執(zhí)行引擎Engine類型實(shí)例 |
4.9. 檢測(cè)DNS配置
創(chuàng)建完Daemon類型實(shí)例daemon之后,Docker Daemon使用daemon.checkLocaldns()檢測(cè)Docker運(yùn)行環(huán)境中DNS的配置, checkLocaldns函數(shù)的定義位于./docker/daemon/daemon.go,代碼如下:
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服務(wù)器信息。若本地DNS 文件中有127.0.0.1,而Docker container不能使用該地址,故采用默認(rèn)外在DNS服務(wù)器,為8.8.8.8,8.8.4.4,并將其賦值給config文件中的Dns屬性。
4.10. 啟動(dòng)時(shí)加載已有Docker containers
當(dāng)Docker Daemon啟動(dòng)時(shí),會(huì)去查看在daemon.repository,也就是在/var/lib/docker/containers中的內(nèi)容。若有存在Docker container的話,則讓Docker Daemon加載這部分容器,將容器信息收集,并做相應(yīng)的維護(hù)。
4.11. 設(shè)置shutdown的處理方法
加載完已有Docker container之后,Docker Daemon設(shè)置了多項(xiàng)在shutdown操作中需要執(zhí)行的handler。也就是說:當(dāng)Docker Daemon接收到特定信號(hào),需要執(zhí)行shutdown操作時(shí),先執(zhí)行這些handler完成善后工作,最終再實(shí)現(xiàn)shutdown。實(shí)現(xiàn)代碼如下:
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對(duì)象shutdown操作執(zhí)行時(shí),需要執(zhí)行以上作為參數(shù)的func(){……}函數(shù)。該函數(shù)中,主要完成4部分的操作:
- 運(yùn)行daemon對(duì)象的shutdown函數(shù),做daemon方面的善后工作;
- 通過portallocator.ReleaseAll(),釋放所有之前占用的端口資源;
- 通過daemon.driver.Cleanup(),通過graphdriver實(shí)現(xiàn)unmount所有l(wèi)ayers中的掛載點(diǎn);
- 通過daemon.containerGraph.Close()關(guān)閉graphdb的連接。
4.12. 返回daemon對(duì)象實(shí)例
當(dāng)所有的工作完成之后,Docker Daemon返回daemon實(shí)例,并最終返回至mainDaemon()中的加載daemon的goroutine中繼續(xù)執(zhí)行。
5. 總結(jié)
本文從源碼的角度深度分析了Docker Daemon啟動(dòng)過程中daemon對(duì)象的創(chuàng)建與加載。在這一環(huán)節(jié)中涉及內(nèi)容極多,本文歸納總結(jié)daemon實(shí)現(xiàn)的邏輯,一一深入,具體全面。
在Docker的架構(gòu)中,Docker Daemon的內(nèi)容是最為豐富以及全面的,而NewDaemon的實(shí)現(xiàn)而是涵蓋了Docker Daemon啟動(dòng)過程中的絕大部分。可以認(rèn)為NewDaemon是Docker Daemon實(shí)現(xiàn)過程中的精華所在。深入理解NewDaemon的實(shí)現(xiàn),即掌握了Docker Daemon運(yùn)行的來龍去脈。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/9579675.html
總結(jié)
以上是生活随笔為你收集整理的Docker源码分析(四):Docker Daemon之NewDaemon实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker源码分析(三):Docker
- 下一篇: Docker源码分析(五):Docker