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的工作全部完成。