Docker的生態系統日趨完善,開發者羣體也在日趨龐大,這讓業界對Docker持續抱有極其樂觀的態度。現在,對於廣大開發者而言,使用Docker這項技術已然不是門檻,享受Docker帶來的技術福利也再也不是困難。然而,如何探尋Docker適應的場景,如何發展Docker周邊的技術,以及如何彌合Docker新技術與傳統物理機或VM技術的鴻溝,已經佔據Docker研究者們的思考與實踐。html
本文爲《Docker源碼分析》第四篇——Docker Daemon之NewDaemon實現,力求幫助廣大Docker愛好者更多得理解Docker 的核心——Docker Daemon的實現。linux
在Docker架構中有不少重要的概念,如:graph,graphdriver,execdriver,networkdriver,volumes,Docker containers等。Docker在實現過程當中,須要將以上實體進行統一化管理,而Docker Daemon中的daemon實例就是設計用來完成這一任務的實體。git
從源碼的角度,NewDaemon函數的執行完成了Docker Daemon建立並加載daemon的任務,最終實現統一管理Docker Daemon的資源。github
本文從源碼角度,分析Docker Daemon加載過程當中NewDaemon的實現,整個分析過程以下圖:golang
圖3.1 Docker Daemon中NewDaemon執行流程圖sql
由上圖可見,Docker Daemon中NewDaemon的執行流程主要包含12個獨立的步驟:處理配置信息、檢測系統支持及用戶權限、配置工做路徑、加載並配置graphdriver、建立Docker Daemon網絡環境、建立並初始化graphdb、建立execdriver、建立daemon實例、檢測DNS配置、加載已有container、設置shutdown處理方法、以及返回daemon實例。docker
下文會在NewDaemon的具體實現中,以12節分別分析以上內容。數據庫
在《Docker源碼分析》系列第三篇中,有一個重要的環節:使用goroutine加載daemon對象並運行。在加載並運行daemon對象時,所作的第一個工做即爲:數組
d, err := daemon.NewDaemon(daemonCfg, eng)
該部分代碼分析以下:安全
進入./docker/daemon/daemon.go中NewDaemon的具體實現,代碼以下:
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的實現細節。
在NewDaemonFromDirectory的實現過程當中,第一個工做是:如何應用傳入的配置信息。這部分配置信息服務於Docker Daemon的運行,並在Docker Daemon啓動初期就初始化完畢。配置信息的主要功能是:供用戶自由配置Docker的可選功能,使得Docker的運行更貼近用戶期待的運行場景。
配置信息的處理包含4部分:
config信息中的Mtu應用於容器網絡的最大傳輸單元(MTU)特性。有關MTU的源碼以下:
if config.Mtu == 0 { config.Mtu = GetDefaultNetworkMtu()
可見,若config信息中Mtu的值爲0的話,則經過GetDefaultNetworkMtu函數將Mtu設定爲默認的值;不然,採用config中的Mtu值。因爲在默認的配置文件./docker/daemon/config.go(下文簡稱爲默認配置文件)中,初始化時Mtu屬性值爲0,故執行GetDefaultNetworkMtu。
GetDefaultNetworkMtu函數的具體實現位於./docker/daemon/config.go:
func GetDefaultNetworkMtu() int { if iface, err := networkdriver.GetDefaultRouteIface(); err == nil { return iface.MTU } return defaultNetworkMtu }
GetDefaultNetworkMtu的實現中,經過networkdriver包的GetDefaultRouteIface方法獲取具體的網絡設備,若該網絡設備存在,則返回該網絡設備的MTU屬性值;不然的話,返回默認的MTU值defaultNetworkMtu,值爲1500。
處理完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,這兩個屬性屬於互斥類型,只能至多指定其中之一。而在默認配置文件中,BridgeIface和BridgeIP均爲空。
檢測容器的通訊配置,主要是針對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。而在默認配置文件中,這兩個屬性的值均爲true。
接着,處理config中的DisableNetwork屬性,以備後續在建立並執行建立Docker Daemon網絡環境時使用,即在名爲」init_networkdriver」的job建立並運行中體現。
config.DisableNetwork = config.BridgeIface == DisableNetworkBridge
因爲config中的BridgeIface屬性值爲空,另外DisableNetworkBridge的值爲字符串」none」,所以最終config中DisableNetwork的值爲false。後續名爲」init_networkdriver」的job在執行過程當中須要使用該屬性。
處理PID文件配置,主要工做是:爲Docker Daemon進程運行時的PID號建立一個PID文件,文件的路徑即爲config中的Pidfile屬性。而且爲Docker Daemon的shutdown操做添加一個刪除該Pidfile的函數,以便在Docker Daemon退出的時候,能夠在第一時間刪除該Pidfile。處理PID文件配置信息的代碼實現以下:
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文件。在默認配置文件中,Pidfile文件的初始值爲」 /var/run/docker.pid」。
以上即是關於配置信息處理的分析。
初步處理完Docker的配置信息以後,Docker對自身運行的環境進行了一系列的檢測,主要包括三個方面:
系統支持與用戶權限檢測的實現較爲簡單,實現代碼以下:
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。實現過程當中,第一個工做是:檢測程序運行所在的處理器架構是否爲「amd64」,而目前Docker運行時只能支持amd64的處理器架構。第二個工做是:檢測Linux內核版本是否知足要求,而目前Docker Daemon運行所需的內核版本若太低,則必須升級至3.8.0。
配置Docker Daemon的工做路徑,主要是建立Docker Daemon運行中所在的工做目錄。實現過程當中,經過config中的Root屬性來完成。在默認配置文件中,Root屬性的值爲」/var/lib/docker」。
在配置工做路徑的代碼實現中,步驟以下:
(1) 使用規範路徑建立一個TempDir,路徑名爲tmp;
(2) 經過tmp,建立一個指向tmp的文件符號鏈接realTmp;
(3) 使用realTemp的值,建立並賦值給環境變量TMPDIR;
(4) 處理config的屬性EnableSelinuxSupport;
(5) 將realRoot從新賦值於config.Root,並建立Docker Daemon的工做根目錄。
加載並配置存儲驅動graphdriver,目的在於:使得Docker Daemon建立Docker鏡像管理所需的驅動環境。Graphdriver用於完成Docker容器鏡像的管理,包括存儲與獲取。
這部份內容的源碼位於./docker/daemon/daemon.go#L743-L790,具體細節分析以下:
graphdriver.DefaultDriver = config.GraphDriver driver, err := graphdriver.New(config.Root, config.GraphOptions)
首先,爲graphdriver包中的DefaultDriver對象賦值,值爲config中的GraphDriver屬性,在默認配置文件中,GraphDriver屬性的值爲空;一樣的,屬性GraphOptions也爲空。而後經過graphDriver中的new函數實現加載graph的存儲驅動。
建立具體的graphdriver是至關重要的一個環節,實現細節由graphdriver包中的New函數來完成。進入./docker/daemon/graphdriver/driver.go中,實現步驟以下:
第一,遍歷數組選擇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,以及其餘三類driver的相應位置。這部份內容的做用是:在沒有優先級drivers數組的時候,一樣能夠經過註冊的driver來選擇具體的graphdriver。
因爲目前在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!") }
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 }
當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。
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。
建立鏡像graph的主要工做是:在文件系統中指定的root目錄下,實例化一個全新的graph對象,做用爲:存儲全部標記的文件系統鏡像,並記錄鏡像之間的關係。實現代碼以下:
g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver)
NewGraph的具體實現位於./docker/graph/graph.go,實現過程當中返回的對象爲Graph類型,定義以下:
type Graph struct { Root string idIndex *truncindex.TruncIndex driver graphdriver.Driver }
其中Root表示graph的工做根目錄,通常爲」/var/lib/docker/graph」;idIndex使得檢索字符串標識符時,容許使用任意一個該字符串惟一的前綴,在這裏idIndex用於經過簡短有效的字符串前綴檢索鏡像與容器的ID;最後driver表示具體的graphdriver類型。
在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對象。
TagStore主要是用於存儲鏡像的倉庫列表(repository list)。代碼以下:
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
NewTagStore位於./docker/graph/tags.go,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類型中的多個屬性的含義:
建立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屬性來判斷,在默認配置文件中,該屬性有過定義,卻沒有初始值。可是在應用配置信息中處理網絡功能配置的時候,將DisableNetwork屬性賦值爲false,故判斷語句結果爲真,執行相應的代碼塊。
首先建立名爲」init_networkdriver」的job,隨後爲該job設置環境變量,環境變量的值以下:
設置完環境變量以後,隨即運行該job,因爲在eng中key爲」init_networkdriver」的handler,value爲bridge.InitDriver函數,故執行bridge.InitDriver函數,具體的實現位於./docker/daemon/networkdriver/bridge/dirver.go,做用爲:
建立Docker網絡設備,屬於Docker Daemon建立網絡環境的第一步,實際工做是建立名爲「docker0」的網橋設備。
在InitDriver函數運行過程當中,首先使用job的環境變量初始化內部變量;而後根據目前網絡環境,判斷是否建立docker0網橋,若Docker專屬網橋已存在,則繼續往下執行;不然的話,建立docker0網橋。具體實現爲createBridge(bridgeIP),以及createBridgeIface(bridgeIface)。
createBridge的功能是:在host主機上啓動建立指定名稱網橋設備的任務,併爲該網橋設備配置一個與其餘設備不衝突的網絡地址。而createBridgeIface經過系統調用負責建立具體實際的網橋設備,並設置MAC地址,經過libcontainer中netlink包的CreateBridge來實現。
建立完網橋以後,Docker Daemon爲容器以及host主機配置iptables,包括爲container之間所須要的link操做提供支持,爲host主機上全部的對外對內流量制定傳輸規則等。代碼位於./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的調用過程當中,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
在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) } }
在網橋設備上建立一條名爲DOCKER的鏈,該鏈的做用是在建立Docker container並設置端口映射時使用。實現代碼位於./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) }
在建立完網橋,並配置完基本的iptables規則以後,Docker Daemon在網絡方面還在Engine中註冊了4個Handler,這些Handler的名稱與做用以下:
因爲在Docker架構中,網絡是極其重要的一部分,所以Docker網絡篇會安排在《Docker源碼分析》系列的第六篇。
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,代碼實現以下:
func NewSqliteConn(root string) (*Database, error) { …… conn, err := sql.Open("sqlite3", root) …… return NewDatabase(conn, initDatabase) }
Execdriver是Docker中用來執行Docker container任務的驅動。建立並初始化graphdb以後,Docker Daemon隨即建立了execdriver,具體代碼以下:
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)
可見,在建立execdriver的時候,須要4部分的信息,如下簡要介紹這4部分信息:
在執行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。若localCopy與sysyInitPath不相等,則說明當前系統中的dockerinit二進制文件,不在localCopy路徑下,須要將其拷貝至localCopy下,並對該文件設定權限。
設定完dockerinit二進制文件的位置以後,Docker Daemon建立sysinfo對象,記錄系統的功能屬性。SysInfo的定義,位於./docker/pkg/sysinfo/sysinfo.go,以下:
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,因爲選擇使用native做爲exec驅動,故執行如下的代碼,返回最終的execdriver,其中native.NewDriver實現位於./docker/daemon/execdriver/native/driver.go:
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
至此,已經建立完畢一個execdriver的實例ed。
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類型實例 |
建立完Daemon類型實例daemon以後,Docker Daemon使用daemon.checkLocaldns()檢測Docker運行環境中DNS的配置, checkLocaldns函數的定義位於./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服務器信息。若本地DNS 文件中有127.0.0.1,而Docker container不能使用該地址,故採用默認外在DNS服務器,爲8.8.8.8,8.8.4.4,並將其賦值給config文件中的Dns屬性。
當Docker Daemon啓動時,會去查看在daemon.repository,也就是在/var/lib/docker/containers中的內容。如有存在Docker container的話,則讓Docker Daemon加載這部分容器,將容器信息收集,並作相應的維護。
加載完已有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部分的操做:
當全部的工做完成以後,Docker Daemon返回daemon實例,並最終返回至mainDaemon()中的加載daemon的goroutine中繼續執行。
本文從源碼的角度深度分析了Docker Daemon啓動過程當中daemon對象的建立與加載。在這一環節中涉及內容極多,本文概括總結daemon實現的邏輯,一一深刻,具體全面。
在Docker的架構中,Docker Daemon的內容是最爲豐富以及全面的,而NewDaemon的實現而是涵蓋了Docker Daemon啓動過程當中的絕大部分。能夠認爲NewDaemon是Docker Daemon實現過程當中的精華所在。深刻理解NewDaemon的實現,即掌握了Docker Daemon運行的前因後果。
http://docs.studygolang.com/pkg/
http://www.iptables.info/en/iptables-matches.html
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt