Docker源码分析
上QQ阅读APP看书,第一时间看更新

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