Docker源码分析(四):Docker Daemon之NewDaemon实现
http://www.infoq.com/cn/articles/docker-source-code-analysis-part4
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的實現,整個分析過程如下圖:
圖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;
- 函數具體實現源文件:./docker/daemon/daemon.go;
- 函數傳入實參:daemonCfg,定義了Docker Daemon運行過程中所需的眾多配置信息;eng,在mainDaemon中創建的Engine對象實例;
- 函數返回類型:d,具體的Daemon對象實例;err,錯誤狀態。
進入./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的實現細節。
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(下文簡稱為默認配置文件)中,初始化時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。
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,這兩個屬性屬于互斥類型,只能至多指定其中之一。而在默認配置文件中,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。而在默認配置文件中,這兩個屬性的值均為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文件配置信息的代碼實現如下:
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”。
以上便是關于配置信息處理的分析。
4.2. 檢測系統支持及用戶權限
初步處理完Docker的配置信息之后,Docker對自身運行的環境進行了一系列的檢測,主要包括三個方面:
- 操作系統類型對Docker Daemon的支持;
- 用戶權限的級別;
- 內核版本與處理器的支持。
系統支持與用戶權限檢測的實現較為簡單,實現代碼如下:
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。
4.3. 配置工作路徑
配置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的工作根目錄。
4.4. 加載并配置graphdriver
加載并配置存儲驅動graphdriver,目的在于:使得Docker Daemon創建Docker鏡像管理所需的驅動環境。Graphdriver用于完成Docker容器鏡像的管理,包括存儲與獲取。
4.4.1. 創建graphdriver
這部分內容的源碼位于./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。
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。
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,實現過程中返回的對象為Graph類型,定義如下:
type Graph struct {Root stringidIndex *truncindex.TruncIndexdriver 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,TagStore的定義如下:
type TagStore struct {path stringgraph *GraphRepositories map[string]Repositorysync.MutexpullingPool 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屬性來判斷,在默認配置文件中,該屬性有過定義,卻沒有初始值。但是在應用配置信息中處理網絡功能配置的時候,將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,作用為:
- 獲取為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),以及createBridgeIface(bridgeIface)。
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,如下:
// 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 ACCEPT4.5.3. 啟用系統數據包轉發功能
在Linux系統上,數據包轉發功能是被默認禁止的。數據包轉發,就是當host主機存在多塊網卡的時,如果其中一塊網卡接收到數據包,并需要將其轉發給另外的網卡。通過修改/proc/sys/net/ipv4/ip_forward的值,將其置為1,則可以保證系統內數據包可以實現轉發功能,代碼如下:
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. 創建DOCKER鏈
在網橋設備上創建一條名為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) }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,代碼實現如下:
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驅動類別,在默認配置文件中默認使用”native”,也可以將這個值改為”lxc”,則使用lxc接口執行Docker container內部的操作;
- config.Root:Docker運行時的root路徑,默認配置文件中為”/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。若localCopy與sysyInitPath不相等,則說明當前系統中的dockerinit二進制文件,不在localCopy路徑下,需要將其拷貝至localCopy下,并對該文件設定權限。
設定完dockerinit二進制文件的位置之后,Docker Daemon創建sysinfo對象,記錄系統的功能屬性。SysInfo的定義,位于./docker/pkg/sysinfo/sysinfo.go,如下:
type SysInfo struct {MemoryLimit boolSwapLimit boolIPv4ForwardingDisabled boolAppArmor 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。
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,代碼如下:
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運行的來龍去脈。
轉載于:https://www.cnblogs.com/davidwang456/articles/9579675.html
總結
以上是生活随笔為你收集整理的Docker源码分析(四):Docker Daemon之NewDaemon实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker源码分析(三):Docker
- 下一篇: Docker源码分析(五):Docker