- Docker源碼分析
- 孫宏亮
- 1331字
- 2018-12-31 20:27:06
3.3.6 使用goroutine加載daemon對象并運行
Docker執行完builtins的加載之后,再次回到mainDaemon()的執行流程中。此時,Docker通過一個goroutine協程加載daemon對象并開始運行Docker Server。這一環節的執行,主要包含以下三個步驟:
1)通過init函數中初始化的daemonCfg與eng對象,創建一個daemon對象d。
2)通過daemon對象的Install函數,向eng對象中注冊眾多的處理方法。
3)在Docker Daemon啟動完畢之后,運行名為acceptconnections的Job,主要工作為向init守護進程發送READY=1信號,以便Docker Server開始正常接收請求。
源碼實現位于./docker/docker/docker/daemon.go#L43-L56,如下所示:
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) } }()
下面詳細分析三個步驟所做的工作。
1.創建daemon對象
daemon.NewDaemon(daemonCfg,eng)是創建daemon對象d的核心部分,主要作用是初始化Docker Daemon的基本環境,如處理config參數,驗證系統支持度,配置Docker工作目錄,設置與加載多種驅動,創建graph環境,驗證DNS配置等。
由于daemon.MainDaemon(daemonCfg,eng)是加載Docker Daemon的核心部分,且篇幅過長,本書第4章將深入分析NewDaemon的實現。
2.通過daemon對象為engine注冊Handler
Docker創建完daemon對象,goroutine立即執行d.Install(eng),具體實現位于./docker/daemon/daemon.go,代碼如下所示:
func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "attach": daemon.ContainerAttach, "build": daemon.CmdBuild, "commit": daemon.ContainerCommit, "container_changes": daemon.ContainerChanges, "container_copy": daemon.ContainerCopy, "container_inspect": daemon.ContainerInspect, "containers": daemon.Containers, "create": daemon.ContainerCreate, "delete": daemon.ContainerDestroy, "export": daemon.ContainerExport, "info": daemon.CmdInfo, "kill": daemon.ContainerKill, ... "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 }
以上代碼的實現同樣分為三部分:
□ 向eng對象中注冊眾多的處理方法對象。
□ daemon.Repositories().Install(eng)實現了向eng對象注冊多個與Docker鏡像相關的Handler,Install的實現位于./docker/docker/graph/service.go。
□ eng.Hack_SetGlobalVar("httpapi.daemon",daemon)實現向eng對象中類型為map的hack對象中添加一條記錄,鍵為httpapi.daemon,值為daemon。
3.運行名為acceptconnections的Job
Docker在goroutine的最后環節運行名為acceptconnections的Job,主要作用是通知init守護進程,使Docker Daemon開始接受請求。源碼位于./docker/docker/docker/daemon.go#L53-L55,如下所示:
// after the daemon is done setting up we can tell the api to start // accepting connections if err := eng.Job("acceptconnections").Run(); err != nil { log.Fatal(err) }
關于Job的運行流程大同小異,總結而言,都是首先創建特定名稱的Job,其次為Job配置環境參數,最后運行Job對應Handler的函數。作為本書涉及的第一個具體Job,下面將對acceptconnections這個Job的執行進程深入分析。
eng.Job("acceptconnections").Run()的運行包含兩部分:首先執行eng.Job("acceptconnections"),返回一個Job實例,隨后再執行該Job實例的Run()函數。
eng.Job("acceptconnections")的實現位于./docker/docker/engine/engine.go#L115-L137,如下所示:
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 }
通過分析以上創建Job的源碼,我們可以發現Docker首先創建一個類型為Job的job對象,該對象中Eng屬性為函數的調用者eng,該對象的Name屬性為acceptconnections,沒有其他參數傳入。另外在eng對象所有的handlers屬性中尋找key為acceptconnections所對應的value值(即具體的Handler)。由于在加載builtins時,源碼remote(eng)已經向eng注冊過這樣一條記錄,鍵為acceptconnections,值為apiserver.AcceptConnections。因此,Job對象的handler屬性為apiserver.AcceptConnections。最后函數返回已經初始化完畢的對象Job。
創建完Job對象之后,隨即執行該job對象的run()函數。run()函數的源碼實現位于./docker/docker/engine/job.go#L48-L96,該函數執行指定的Job,并在Job執行完成前一直處于阻塞狀態。對于名為acceptconnections的Job對象,運行代碼為job.status=job.handler(job),由于job.handler值為apiserver.AcceptConnections,故真正執行的是job.status=apiserver.AcceptConnections(job)。
AcceptConnections的具體實現屬于Docker Server的范疇,深入研究Docker Server可以發現,這部分源碼位于./docker/docker/api/server/server.go#L1370-L1380,如下所示:
func AcceptConnections(job *engine.Job) engine.Status { // Tell the init daemon we are accepting requests go systemd.SdNotify("READY=1") if activationLock != nil { close(activationLock) } return engine.StatusOK }
AcceptConnections函數的重點是go systemd.SdNotify("READY=1")的實現,位于./docker/docker/pkg/system/sdnotify.go#L12-L33,主要作用是通知init守護進程Docker Daemon的啟動已經全部完成,潛在的功能是要求Docker Daemon開始接收并服務Docker Client發送來的API請求。
至此,通過goroutine來加載daemon對象并運行啟動Docker Server的工作全部完成。