日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Docker源码分析(四):Docker Daemon之NewDaemon实现

發布時間:2025/4/5 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 ACCEPT

4.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实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。