Docker源码分析(三):Docker Daemon启动
http://www.infoq.com/cn/articles/docker-source-code-analysis-part3
1 前言
Docker誕生以來,便引領(lǐng)了輕量級虛擬化容器領(lǐng)域的技術(shù)熱潮。在這一潮流下,Google、IBM、Redhat等業(yè)界翹楚紛紛加入Docker陣營。雖然目前Docker仍然主要基于Linux平臺,但是Microsoft卻多次宣布對Docker的支持,從先前宣布的Azure支持Docker與Kubernetes,到如今宣布的下一代Windows Server原生態(tài)支持Docker。Microsoft的這一系列舉措多少喻示著向Linux世界的妥協(xié),當(dāng)然這也不得不讓世人對Docker的巨大影響力有重新的認(rèn)識。
Docker的影響力不言而喻,但如果需要深入學(xué)習(xí)Docker的內(nèi)部實現(xiàn),筆者認(rèn)為最重要的是理解Docker Daemon。在Docker架構(gòu)中,Docker Client通過特定的協(xié)議與Docker Daemon進(jìn)行通信,而Docker Daemon主要承載了Docker運(yùn)行過程中的大部分工作。本文即為《Docker源碼分析》系列的第三篇-——Docker Daemon篇。
2 Docker Daemon簡介
Docker Daemon是Docker架構(gòu)中運(yùn)行在后臺的守護(hù)進(jìn)程,大致可以分為Docker Server、Engine和Job三部分。Docker Daemon可以認(rèn)為是通過Docker Server模塊接受Docker Client的請求,并在Engine中處理請求,然后根據(jù)請求類型,創(chuàng)建出指定的Job并運(yùn)行,運(yùn)行過程的作用有以下幾種可能:向Docker Registry獲取鏡像,通過graphdriver執(zhí)行容器鏡像的本地化操作,通過networkdriver執(zhí)行容器網(wǎng)絡(luò)環(huán)境的配置,通過execdriver執(zhí)行容器內(nèi)部運(yùn)行的執(zhí)行工作等。
以下為Docker Daemon的架構(gòu)示意圖:
3 Docker Daemon源碼分析內(nèi)容安排
本文從源碼的角度,主要分析Docker Daemon的啟動流程。由于Docker Daemon和Docker Client的啟動流程有很大的相似之處,故在介紹啟動流程之后,本文著重分析啟動流程中最為重要的環(huán)節(jié):創(chuàng)建daemon過程中mainDaemon()的實現(xiàn)。
4 Docker Daemon的啟動流程
由于Docker Daemon和Docker Client的啟動都是通過可執(zhí)行文件docker來完成的,因此兩者的啟動流程非常相似。Docker可執(zhí)行文件運(yùn)行時,運(yùn)行代碼通過不同的命令行flag參數(shù),區(qū)分兩者,并最終運(yùn)行兩者各自相應(yīng)的部分。
啟動Docker Daemon時,一般可以使用以下命令:docker --daemon=true; docker –d; docker –d=true等。接著由docker的main()函數(shù)來解析以上命令的相應(yīng)flag參數(shù),并最終完成Docker Daemon的啟動。
首先,附上Docker Daemon的啟動流程圖:
由于《Docker源碼分析》系列之Docker Client篇中,已經(jīng)涉及了關(guān)于Docker中main()函數(shù)運(yùn)行的很多前續(xù)工作(可參見Docker Client篇),并且Docker Daemon的啟動也會涉及這些工作,故本文略去相同部分,而主要針對后續(xù)僅和Docker Daemon相關(guān)的內(nèi)容進(jìn)行深入分析,即mainDaemon()的具體源碼實現(xiàn)。
5 mainDaemon( )的具體實現(xiàn)
通過Docker Daemon的流程圖,可以得出一個這樣的結(jié)論:有關(guān)Docker Daemon的所有的工作,都被包含在mainDaemon()方法的實現(xiàn)中。
宏觀來講,mainDaemon()完成創(chuàng)建一個daemon進(jìn)程,并使其正常運(yùn)行。
從功能的角度來說,mainDaemon()實現(xiàn)了兩部分內(nèi)容:第一,創(chuàng)建Docker運(yùn)行環(huán)境;第二,服務(wù)于Docker Client,接收并處理相應(yīng)請求。
從實現(xiàn)細(xì)節(jié)來講,mainDaemon()的實現(xiàn)過程主要包含以下步驟:
- daemon的配置初始化(這部分在init()函數(shù)中實現(xiàn),即在mainDaemon()運(yùn)行前就執(zhí)行,但由于這部分內(nèi)容和mainDaemon()的運(yùn)行息息相關(guān),故可認(rèn)為是mainDaemon()運(yùn)行的先決條件);
- 命令行flag參數(shù)檢查;
- 創(chuàng)建engine對象;
- 設(shè)置engine的信號捕獲及處理方法;
- 加載builtins;
- 使用goroutine加載daemon對象并運(yùn)行;
- 打印Docker版本及驅(qū)動信息;
- Job之”serveapi”的創(chuàng)建與運(yùn)行。
下文將一一深入分析以上步驟。
5.0 配置初始化
在mainDaemon()運(yùn)行之前,關(guān)于Docker Daemon所需要的config配置信息均已經(jīng)初始化完畢。具體實現(xiàn)如下,位于./docker/docker/daemon.go:
var (daemonCfg = &daemon.Config{} ) func init() {daemonCfg.InstallFlags() }首先,聲明一個為daemon包中Config類型的變量,名為daemonCfg。而Config對象,定義了Docker Daemon所需的配置信息。在Docker Daemon在啟動時,daemonCfg變量被傳遞至Docker Daemon并被使用。
Config對象的定義如下(含部分屬性的解釋),位于./docker/daemon/config.go:
type Config struct {Pidfile string //Docker Daemon所屬進(jìn)程的PID文件Root string //Docker運(yùn)行時所使用的root路徑AutoRestart bool //已被啟用,轉(zhuǎn)而支持docker run時的重啟Dns []string //Docker使用的DNS Server地址DnsSearch []string //Docker使用的指定的DNS查找域名Mirrors []string //指定的優(yōu)先Docker Registry鏡像EnableIptables bool //啟用Docker的iptables功能EnableIpForward bool //啟用net.ipv4.ip_forward功能EnableIpMasq bool //啟用IP偽裝技術(shù)DefaultIp net.IP //綁定容器端口時使用的默認(rèn)IPBridgeIface string //添加容器網(wǎng)絡(luò)至已有的網(wǎng)橋BridgeIP string //創(chuàng)建網(wǎng)橋的IP地址FixedCIDR string //指定IP的IPv4子網(wǎng),必須被網(wǎng)橋子網(wǎng)包含InterContainerCommunication bool //是否允許相同host上容器間的通信GraphDriver string //Docker運(yùn)行時使用的特定存儲驅(qū)動GraphOptions []string //可設(shè)置的存儲驅(qū)動選項ExecDriver string // Docker運(yùn)行時使用的特定exec驅(qū)動Mtu int //設(shè)置容器網(wǎng)絡(luò)的MTUDisableNetwork bool //有定義,之后未初始化EnableSelinuxSupport bool //啟用SELinux功能的支持Context map[string][]string //有定義,之后未初始化 }已經(jīng)有聲明的daemonCfg之后,init()函數(shù)實現(xiàn)了daemonCfg變量中各屬性的賦值,具體的實現(xiàn)為:daemonCfg.InstallFlags(),位于./docker/daemon/config.go,代碼如下:
func (config *Config) InstallFlags() {flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, "/var/run/docker.pid","Path to use for daemon PID file")flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the Docker runtime")……opts.IPVar(&config.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")opts.ListVar(&config.GraphOptions, []string{"-storage-opt"}, "Set storage driver options")…… }在InstallFlags()函數(shù)的實現(xiàn)過程中,主要是定義某種類型的flag參數(shù),并將該參數(shù)的值綁定在config變量的指定屬性上,如:
flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, " /var/run/docker.pid", "Path to use for daemon PID file")
以上語句的含義為:
- 定義一個為String類型的flag參數(shù);
- 該flag的名稱為”p”或者”-pidfile”;
- 該flag的值為” /var/run/docker.pid”,并將該值綁定在變量config.Pidfile上;
- 該flag的描述信息為"Path to use for daemon PID file"。
至此,關(guān)于Docker Daemon所需要的配置信息均聲明并初始化完畢。
5.1 flag參數(shù)檢查
從這一節(jié)開始,真正進(jìn)入Docker Daemon的mainDaemon()運(yùn)行分析。
第一個步驟即flag參數(shù)的檢查。具體而言,即當(dāng)docker命令經(jīng)過flag參數(shù)解析之后,判斷剩余的參數(shù)是否為0。若為0,則說明Docker Daemon的啟動命令無誤,正常運(yùn)行;若不為0,則說明在啟動Docker Daemon的時候,傳入了多余的參數(shù),此時會輸出錯誤提示,并退出運(yùn)行程序。具體代碼如下:
if flag.NArg() != 0 {flag.Usage()return }5.2 創(chuàng)建engine對象
在mainDaemon()運(yùn)行過程中,flag參數(shù)檢查完畢之后,隨即創(chuàng)建engine對象,代碼如下:
eng := engine.New()Engine是Docker架構(gòu)中的運(yùn)行引擎,同時也是Docker運(yùn)行的核心模塊。Engine扮演著Docker container存儲倉庫的角色,并且通過job的形式來管理這些容器。
在./docker/engine/engine.go中,Engine結(jié)構(gòu)體的定義如下:
type Engine struct {handlers map[string]Handlercatchall Handlerhack Hack // data for temporary hackery (see hack.go)id stringStdout io.WriterStderr io.WriterStdin io.ReaderLogging booltasks sync.WaitGroupl sync.RWMutex // lock for shutdownshutdown boolonShutdown []func() // shutdown handlers }其中,Engine結(jié)構(gòu)體中最為重要的即為handlers屬性。該handlers屬性為map類型,key為string類型,value為Handler類型。其中Handler類型的定義如下:
type Handler func(*Job) Status可見,Handler為一個定義的函數(shù)。該函數(shù)傳入的參數(shù)為Job指針,返回為Status狀態(tài)。
介紹完Engine以及Handler,現(xiàn)在真正進(jìn)入New()函數(shù)的實現(xiàn)中:
func New() *Engine {eng := &Engine{handlers: make(map[string]Handler),id: utils.RandomString(),Stdout: os.Stdout,Stderr: os.Stderr,Stdin: os.Stdin,Logging: true,}eng.Register("commands", func(job *Job) Status {for _, name := range eng.commands() {job.Printf("%s\n", name)}return StatusOK})// Copy existing global handlersfor k, v := range globalHandlers {eng.handlers[k] = v}return eng }分析以上代碼,可以知道New()函數(shù)最終返回一個Engine對象。而在代碼實現(xiàn)部分,第一個工作即為創(chuàng)建一個Engine結(jié)構(gòu)體實例eng;第二個工作是向eng對象注冊名為commands的Handler,其中Handler為臨時定義的函數(shù)func(job *Job) Status{ } , 該函數(shù)的作用是通過job來打印所有已經(jīng)注冊完畢的command名稱,最終返回狀態(tài)StatusOK;第三個工作是:將已定義的變量globalHandlers中的所有的Handler,都復(fù)制到eng對象的handlers屬性中。最后成功返回eng對象。
5.3 設(shè)置engine的信號捕獲
回到mainDaemon()函數(shù)的運(yùn)行中,執(zhí)行后續(xù)代碼:
signal.Trap(eng.Shutdown)該部分代碼的作用是:在Docker Daemon的運(yùn)行中,設(shè)置Trap特定信號的處理方法,特定信號有SIGINT,SIGTERM以及SIGQUIT;當(dāng)程序捕獲到SIGINT或者SIGTERM信號時,執(zhí)行相應(yīng)的善后操作,最后保證Docker Daemon程序退出。
該部分的代碼的實現(xiàn)位于./docker/pkg/signal/trap.go。實現(xiàn)的流程分為以下4個步驟:
- 創(chuàng)建并設(shè)置一個channel,用于發(fā)送信號通知;
- 定義signals數(shù)組變量,初始值為os.SIGINT, os.SIGTERM;若環(huán)境變量DEBUG為空的話,則添加os.SIGQUIT至signals數(shù)組;
- 通過gosignal.Notify(c, signals...)中Notify函數(shù)來實現(xiàn)將接收到的signal信號傳遞給c。需要注意的是只有signals中被羅列出的信號才會被傳遞給c,其余信號會被直接忽略;
- 創(chuàng)建一個goroutine來處理具體的signal信號,當(dāng)信號類型為os.Interrupt或者syscall.SIGTERM時,執(zhí)行傳入Trap函數(shù)的具體執(zhí)行方法,形參為cleanup(),實參為eng.Shutdown。
Shutdown()函數(shù)的定義位于./docker/engine/engine.go,主要做的工作是為Docker Daemon的關(guān)閉做一些善后工作。
善后工作如下:
- Docker Daemon不再接收任何新的Job;
- Docker Daemon等待所有存活的Job執(zhí)行完畢;
- Docker Daemon調(diào)用所有shutdown的處理方法;
- 當(dāng)所有的handler執(zhí)行完畢,或者15秒之后,Shutdown()函數(shù)返回。
由于在signal.Trap( eng.Shutdown )函數(shù)的具體實現(xiàn)中執(zhí)行eng.Shutdown,在執(zhí)行完eng.Shutdown之后,隨即執(zhí)行os.Exit(0),完成當(dāng)前程序的立即退出。
5.4 加載builtins
為eng設(shè)置完Trap特定信號的處理方法之后,Docker Daemon實現(xiàn)了builtins的加載。代碼實現(xiàn)如下:
if err := builtins.Register(eng); err != nil {log.Fatal(err) }加載builtins的主要工作是為:為engine注冊多個Handler,以便后續(xù)在執(zhí)行相應(yīng)任務(wù)時,運(yùn)行指定的Handler。這些Handler包括:網(wǎng)絡(luò)初始化、web API服務(wù)、事件查詢、版本查看、Docker Registry驗證與搜索。代碼實現(xiàn)位于./docker/builtins/builtins.go,如下:
func Register(eng *engine.Engine) error {if err := daemon(eng); err != nil {return err}if err := remote(eng); err != nil {return err}if err := events.New().Install(eng); err != nil {return err}if err := eng.Register("version", dockerVersion); err != nil {return err}return registry.NewService().Install(eng) }以下分析實現(xiàn)過程中最為主要的5個部分:daemon(eng)、remote(eng)、events.New().Install(eng)、eng.Register(“version”,dockerVersion)以及registry.NewService().Install(eng)。
5.4.1 注冊初始化網(wǎng)絡(luò)驅(qū)動的Handler
daemon(eng)的實現(xiàn)過程,主要為eng對象注冊了一個key為”init_networkdriver”的Handler,該Handler的值為bridge.InitDriver函數(shù),代碼如下:
func daemon(eng *engine.Engine) error {return eng.Register("init_networkdriver", bridge.InitDriver) }需要注意的是,向eng對象注冊Handler,并不代表Handler的值函數(shù)會被直接運(yùn)行,如bridge.InitDriver,并不會直接運(yùn)行,而是將bridge.InitDriver的函數(shù)入口,寫入eng的handlers屬性中。
Bridge.InitDriver的具體實現(xiàn)位于./docker/daemon/networkdriver/bridge/driver.go?,主要作用為:
- 獲取為Docker服務(wù)的網(wǎng)絡(luò)設(shè)備的地址;
- 創(chuàng)建指定IP地址的網(wǎng)橋;
- 配置網(wǎng)絡(luò)iptables規(guī)則;
- 另外還為eng對象注冊了多個Handler,如 ”allocate_interface”, ”release_interface”, ”allocate_port”,”link”。
5.4.2 注冊API服務(wù)的Handler
remote(eng)的實現(xiàn)過程,主要為eng對象注冊了兩個Handler,分別為”serveapi”與”acceptconnections”。代碼實現(xiàn)如下:
func remote(eng *engine.Engine) error {if err := eng.Register("serveapi", apiserver.ServeApi); err != nil {return err}return eng.Register("acceptconnections", apiserver.AcceptConnections) }注冊的兩個Handler名稱分別為”serveapi”與”acceptconnections”,相應(yīng)的執(zhí)行方法分別為apiserver.ServeApi與apiserver.AcceptConnections,具體實現(xiàn)位于./docker/api/server/server.go。其中,ServeApi執(zhí)行時,通過循環(huán)多種協(xié)議,創(chuàng)建出goroutine來配置指定的http.Server,最終為不同的協(xié)議請求服務(wù);而AcceptConnections的實現(xiàn)主要是為了通知init守護(hù)進(jìn)程,Docker Daemon已經(jīng)啟動完畢,可以讓Docker Daemon進(jìn)程接受請求。
5.4.3 注冊events事件的Handler
events.New().Install(eng)的實現(xiàn)過程,為Docker注冊了多個event事件,功能是給Docker用戶提供API,使得用戶可以通過這些API查看Docker內(nèi)部的events信息,log信息以及subscribers_count信息。具體的代碼位于./docker/events/events.go,如下:
func (e *Events) Install(eng *engine.Engine) error {jobs := map[string]engine.Handler{"events": e.Get,"log": e.Log,"subscribers_count": e.SubscribersCount,}for name, job := range jobs {if err := eng.Register(name, job); err != nil {return err}}return nil }5.4.4 注冊版本的Handler
eng.Register(“version”,dockerVersion)的實現(xiàn)過程,向eng對象注冊key為”version”,value為”dockerVersion”執(zhí)行方法的Handler,dockerVersion的執(zhí)行過程中,會向名為version的job的標(biāo)準(zhǔn)輸出中寫入Docker的版本,Docker API的版本,git版本,Go語言運(yùn)行時版本以及操作系統(tǒng)等版本信息。dockerVersion的具體實現(xiàn)如下:
func dockerVersion(job *engine.Job) engine.Status {v := &engine.Env{}v.SetJson("Version", dockerversion.VERSION)v.SetJson("ApiVersion", api.APIVERSION)v.Set("GitCommit", dockerversion.GITCOMMIT)v.Set("GoVersion", runtime.Version())v.Set("Os", runtime.GOOS)v.Set("Arch", runtime.GOARCH)if kernelVersion, err := kernel.GetKernelVersion(); err == nil {v.Set("KernelVersion", kernelVersion.String())}if _, err := v.WriteTo(job.Stdout); err != nil {return job.Error(err)}return engine.StatusOK }5.4.5 注冊registry的Handler
registry.NewService().Install(eng)的實現(xiàn)過程位于./docker/registry/service.go,在eng對象對外暴露的API信息中添加docker registry的信息。當(dāng)registry.NewService()成功被Install安裝完畢的話,則有兩個調(diào)用能夠被eng使用:”auth”,向公有registry進(jìn)行認(rèn)證;”search”,在公有registry上搜索指定的鏡像。
Install的具體實現(xiàn)如下:
func (s *Service) Install(eng *engine.Engine) error {eng.Register("auth", s.Auth)eng.Register("search", s.Search)return nil }至此,所有builtins的加載全部完成,實現(xiàn)了向eng對象注冊特定的Handler。
5.5 使用goroutine加載daemon對象并運(yùn)行
執(zhí)行完builtins的加載,回到mainDaemon()的執(zhí)行,通過一個goroutine來加載daemon對象并開始運(yùn)行。這一環(huán)節(jié)的執(zhí)行,主要包含三個步驟:
- 通過init函數(shù)中初始化的daemonCfg與eng對象來創(chuàng)建一個daemon對象d;
- 通過daemon對象的Install函數(shù),向eng對象中注冊眾多的Handler;
- 在Docker Daemon啟動完畢之后,運(yùn)行名為”acceptconnections”的job,主要工作為向init守護(hù)進(jìn)程發(fā)送”READY=1”信號,以便開始正常接受請求。
代碼實現(xiàn)如下:
go func() {d, err := daemon.MainDaemon(daemonCfg, eng)if err != nil {log.Fatal(err)}if err := d.Install(eng); err != nil {log.Fatal(err)}if err := eng.Job("acceptconnections").Run(); err != nil {log.Fatal(err)} }()以下分別分析三個步驟所做的工作。
5.5.1 創(chuàng)建daemon對象
daemon.MainDaemon(daemonCfg, eng)是創(chuàng)建daemon對象d的核心部分。主要作用為初始化Docker Daemon的基本環(huán)境,如處理config參數(shù),驗證系統(tǒng)支持度,配置Docker工作目錄,設(shè)置與加載多種driver,創(chuàng)建graph環(huán)境等,驗證DNS配置等。
由于daemon.MainDaemon(daemonCfg, eng)是加載Docker Daemon的核心部分,且篇幅過長,故安排《Docker源碼分析》系列的第四篇專文分析這部分。
5.5.2 通過daemon對象為engine注冊Handler
當(dāng)創(chuàng)建完daemon對象,goroutine執(zhí)行d.Install(eng),具體實現(xiàn)位于./docker/daemon/daemon.go:
func (daemon *Daemon) Install(eng *engine.Engine) error {for name, method := range map[string]engine.Handler{"attach": daemon.ContainerAttach,……"image_delete": daemon.ImageDelete, } {if err := eng.Register(name, method); err != nil {return err}}if err := daemon.Repositories().Install(eng); err != nil {return err}eng.Hack_SetGlobalVar("httpapi.daemon", daemon)return nil }以上代碼的實現(xiàn)分為三部分:
- 向eng對象中注冊眾多的Handler對象;
- daemon.Repositories().Install(eng)實現(xiàn)了向eng對象注冊多個與image相關(guān)的Handler,Install的實現(xiàn)位于./docker/graph/service.go;
- eng.Hack_SetGlobalVar("httpapi.daemon", daemon)實現(xiàn)向eng對象中map類型的hack對象中添加一條記錄,key為”httpapi.daemon”,value為daemon。
5.5.3 運(yùn)行acceptconnections的job
在goroutine內(nèi)部最后運(yùn)行名為”acceptconnections”的job,主要作用是通知init守護(hù)進(jìn)程,Docker Daemon可以開始接受請求了。
這是源碼分析系列中第一次涉及具體Job的運(yùn)行,以下簡單分析”acceptconnections”這個job的運(yùn)行。
可以看到首先執(zhí)行eng.Job("acceptconnections"),返回一個Job,隨后再執(zhí)行eng.Job("acceptconnections").Run(),也就是該執(zhí)行Job的run函數(shù)。
eng.Job(“acceptconnections”)的實現(xiàn)位于./docker/engine/engine.go,如下:
func (eng *Engine) Job(name string, args ...string) *Job {job := &Job{Eng: eng,Name: name,Args: args,Stdin: NewInput(),Stdout: NewOutput(),Stderr: NewOutput(),env: &Env{},}if eng.Logging {job.Stderr.Add(utils.NopWriteCloser(eng.Stderr))}if handler, exists := eng.handlers[name]; exists {job.handler = handler} else if eng.catchall != nil && name != "" {job.handler = eng.catchall}return job }由以上代碼可知,首先創(chuàng)建一個類型為Job的job對象,該對象中Eng屬性為函數(shù)的調(diào)用者eng,Name屬性為”acceptconnections”,沒有參數(shù)傳入。另外在eng對象所有的handlers屬性中尋找鍵為”acceptconnections”記錄的值,由于在加載builtins操作中的remote(eng)中已經(jīng)向eng注冊過這樣的一條記錄,key為”acceptconnections”,value為apiserver.AcceptConnections。因此job對象的handler為apiserver.AcceptConnections。最后返回已經(jīng)初始化完畢的對象job。
創(chuàng)建完job對象之后,隨即執(zhí)行該job對象的run()函數(shù)。Run()函數(shù)的實現(xiàn)位于./docker/engine/job.go,該函數(shù)執(zhí)行指定的job,并在job執(zhí)行完成前一直阻塞。對于名為”acceptconnections”的job對象,運(yùn)行代碼為job.status = job.handler(job),由于job.handler值為apiserver.AcceptConnections,故真正執(zhí)行的是job.status = apiserver.AcceptConnections(job)。
進(jìn)入AcceptConnections的具體實現(xiàn),位于./docker/api/server/server.go,如下:
func AcceptConnections(job *engine.Job) engine.Status {// Tell the init daemon we are accepting requestsgo systemd.SdNotify("READY=1")if activationLock != nil {close(activationLock)}return engine.StatusOK }重點為go systemd.SdNotify("READY=1")的實現(xiàn),位于./docker/pkg/system/sd_notify.go,主要作用是通知init守護(hù)進(jìn)程Docker Daemon的啟動已經(jīng)全部完成,潛在的功能是使得Docker Daemon開始接受Docker Client發(fā)送來的API請求。
至此,已經(jīng)完成通過goroutine來加載daemon對象并運(yùn)行。
5.6 打印Docker版本及驅(qū)動信息
回到mainDaemon()的運(yùn)行流程中,在goroutine的執(zhí)行之時,mainDaemon()函數(shù)內(nèi)部其它代碼也會并發(fā)執(zhí)行。
第一個執(zhí)行的即為顯示docker的版本信息,以及ExecDriver和GraphDriver這兩個驅(qū)動的具體信息,代碼如下:
log.Printf("docker daemon: %s %s; execdriver: %s; graphdriver: %s",dockerversion.VERSION,dockerversion.GITCOMMIT,daemonCfg.ExecDriver,daemonCfg.GraphDriver, )5.7 Job之serveapi的創(chuàng)建與運(yùn)行
打印部分Docker具體信息之后,Docker Daemon立即創(chuàng)建并運(yùn)行名為”serveapi”的job,主要作用為讓Docker Daemon提供API訪問服務(wù)。實現(xiàn)代碼位于./docker/docker/daemon.go#L66,如下:
job := eng.Job("serveapi", flHosts...) job.SetenvBool("Logging", true) job.SetenvBool("EnableCors", *flEnableCors) job.Setenv("Version", dockerversion.VERSION) job.Setenv("SocketGroup", *flSocketGroup)job.SetenvBool("Tls", *flTls) job.SetenvBool("TlsVerify", *flTlsVerify) job.Setenv("TlsCa", *flCa) job.Setenv("TlsCert", *flCert) job.Setenv("TlsKey", *flKey) job.SetenvBool("BufferRequests", true) if err := job.Run(); err != nil {log.Fatal(err) }實現(xiàn)過程中,首先創(chuàng)建一個名為”serveapi”的job,并將flHosts的值賦給job.Args。flHost的作用主要是為Docker Daemon提供使用的協(xié)議與監(jiān)聽的地址。隨后,Docker Daemon為該job設(shè)置了眾多的環(huán)境變量,如安全傳輸層協(xié)議的環(huán)境變量等。最后通過job.Run()運(yùn)行該serveapi的job。
由于在eng中key為”serveapi”的handler,value為apiserver.ServeApi,故該job運(yùn)行時,執(zhí)行apiserver.ServeApi函數(shù),位于./docker/api/server/server.go。ServeApi函數(shù)的作用主要是對于用戶定義的所有支持協(xié)議,Docker Daemon均創(chuàng)建一個goroutine來啟動相應(yīng)的http.Server,分別為不同的協(xié)議服務(wù)。
由于創(chuàng)建并啟動http.Server為Docker架構(gòu)中有關(guān)Docker Server的重要內(nèi)容,《Docker源碼分析》系列會在第五篇專文進(jìn)行分析。
至此,可以認(rèn)為Docker Daemon已經(jīng)完成了serveapi這個job的初始化工作。一旦acceptconnections這個job運(yùn)行完畢,則會通知init進(jìn)程Docker Daemon啟動完畢,可以開始提供API服務(wù)。
6 總結(jié)
本文從源碼的角度分析了Docker Daemon的啟動,著重分析了mainDaemon()的實現(xiàn)。
Docker Daemon作為Docker架構(gòu)中的主干部分,負(fù)責(zé)了Docker內(nèi)部幾乎所有操作的管理。學(xué)習(xí)Docker Daemon的具體實現(xiàn),可以對Docker架構(gòu)有一個較為全面的認(rèn)識。總結(jié)而言,Docker的運(yùn)行,載體為daemon,調(diào)度管理由engine,任務(wù)執(zhí)行靠job。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/9579585.html
總結(jié)
以上是生活随笔為你收集整理的Docker源码分析(三):Docker Daemon启动的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker源码分析(二):Docker
- 下一篇: Docker源码分析(四):Docker