《容器技术系列》一3.3 mainDaemon()的具体实现

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:

本节书摘来异步社区《容器技术系列》一书中的第3章 ,第3.3节,孙宏亮 著, 更多章节内容可以访问云栖社区“异步社区”公众号查看。

3.3 mainDaemon()的具体实现

Docker Daemon的启动流程图展示了DockerDaemon的从无到有。通过分析流程图,我们可以得出一个这样的结论:区分Docker Daemon与Docker Client的关键在于flag参数flDaemon的值。一旦*flDaemon的值为真,则代表docker二进制需要启动的是Docker Daemon。有关Docker Daemon的所有的工作,都被包含在函数mainDaemon()的具体实现中。
宏观来讲,mainDaemon()的使命是:创建一个守护进程,并保证其正常运行。
从功能的角度来说,mainDaemon()实现了两部分内容:第一,创建Docker运行环境;第二,服务于Docker Client,接收并处理相应请求(完成Docker Server的初始化)。
从实现细节来分析,mainDaemon()的实现流程主要包含以下步骤:
1)daemon的配置初始化。这部分在init()函数中实现,即在mainDaemon()运行前就执行,但由于这部分内容和mainDaemon()的运行息息相关,可以认为是mainDaemon()运行的先决条件。
2)命令行flag参数检查。
3)创建engine对象。
4)设置engine的信号捕获及处理方法。
5)加载builtins。
6)使用goroutine加载daemon对象并运行。
7)打印Docker版本及驱动信息。
8)serveapi的创建与运行。
对于以上内容,本章将一一深入分析。

3.3.1 配置初始化

mainDaemon()的运行位于./docker/docker/docker/daemon.go,深入分析mainDaemon()的实现之前,我们回到Go 语言的特性,即变量与init函数的执行顺序。在daemon.go中,Docker定义了变量daemonCfg,以及init函数,通过Go 语言的特性,变量的定义与init函数均会在mainDaemon()之前运行。两者的定义如下:

var (
     daemonCfg = &daemon.Config{}
)
func init() {
     daemonCfg.InstallFlags()
}
首先,Docker声明一个名为daemonCfg的变量,代表整个Docker Daemon的配置信息。定义完毕之后,init函数的存在,使得daemonCfg变量能获取相应的属性值。在Docker Daemon启动时,daemonCfg变量被传递至Docker Daemon并被使用。
Config对象的定义如下(含部分属性的解释),该对象位于./docker/docker/daemon/config.go:
type Config struct {
     Pidfile                      string  //Docker Daemon所属进程的PID文件
     Root                        string  //Docker运行时所使用的root路径
     AutoRestart                 bool    //是否一直支持创建容器的重启
     Dns                         []string//DockerDaemon为容器准备的DNS Server地址
     DnsSearch                   []string//Docker使用的指定的DNS查找地址
     Mirrors                     []string//指定的Docker Registry镜像地址
     EnableIptables              bool    //是否启用Docker的iptables功能
     EnableIpForward             bool    //是否启用net.ipv4.ip_forward功能
     EnableIpMasq                bool    //启用IP伪装技术
     DefaultIp                   net.IP  //绑定容器端口时使用的默认IP
     BridgeIface                 string  //添加容器网络至已有的网桥接口名
     BridgeIP                    string  //创建网桥的IP地址
     FixedCIDR                   string  //指定IP的IPv4子网,必须被网桥子网包含
     InterContainerCommunication bool    //是否允许宿主机上Docker容器间的通信
     GraphDriver                 string  //Docker Daemon运行时使用的特定存储驱动
     GraphOptions                []string//可设置的存储驱动选项
     ExecDriver                  string  //Docker运行时使用的特定exec驱动
     Mtu                         int     //设置容器网络接口的MTU
     DisableNetwork              bool    //是否支持Docker容器的网络模式
     EnableSelinuxSupport        bool    //是否启用对SELinux功能的支持
     Context                     map[string][]string
}
Docker声明daemonCfg之后,init函数实现了daemonCfg变量中各属性的赋值,具体的实现为:daemonCfg.InstallFlags(),位于./docker/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")
         flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated infavor of --restart policies on docker run")
         flag.BoolVar(&config.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules")
         flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
         flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
         flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
         flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
         flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
         flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
         flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support. SELinux does not presently support the BTRFS storage driver")
         flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU\nif no value is provided: default to the default route MTU or 1500 if no default route is available")
         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")
     // FIXME: why the inconsistency between "hosts" and "sockets"?
         opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
         opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
}

在函数InstallFlags()的实现过程中,Docker主要定义了众多类型不一的flag参数,并将该参数的值绑定在daemonCfg变量的指定属性上,如:

flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file")

以上语句的含义为:
定义一个String类型的flag参数。
该flag的名称为"p"或者"-pidfile"。
该flag的默认值为"/var/run/docker.pid",并将该值绑定在变量config.Pidfile上。
该flag的描述信息为"Path to use for daemon PID file"。
至此,关于Docker Daemon所需要的配置信息均声明并初始化完毕。

3.3.2 flag参数检查

从本小节开始,程序运行真正进入Docker Daemon的mainDaemon(),下面对此流程进行深入分析。
mainDaemon()运行的第一个步骤是命令行flag参数的检查。具体而言,即当docker命令经过flag参数解析之后,Docker判断剩余的参数是否为0。若为0,则说明Docker Daemon的启动命令无误,正常运行;若不为0,则说明在启动Docker Daemon的时候,传入了多余的参数,此时Docker会输出错误提示,并退出运行程序。具体代码如下:

if flag.NArg() != 0 {
  flag.Usage()
  return
}

3.3.3 创建engine对象

在mainDaemon()运行过程中,flag参数检查完毕之后,Docker随即创建engine对象,代码如下:

eng := engine.New()

Engine是Docker架构中的运行引擎,同时也是Docker运行的核心模块。Engine扮演着Docker Container存储仓库的角色,并且通过Job的形式管理Docker运行中涉及的所有任务。
Engine结构体的定义位于./docker/docker/engine/engine.go#L47-L60,具体代码如下:

type Engine struct {
     handlers   map[string]Handler
     catchall   Handler
     hack       Hack // data for temporary hackery (see hack.go)
     id         string
     Stdout     io.Writer
     Stderr     io.Writer
     Stdin      io.Reader
     Logging    bool
     tasks      sync.WaitGroup
     l          sync.RWMutex // lock for shutdown
     shutdown   bool
     onShutdown []func() // shutdown handlers
}

Engine结构体中最为重要的是handlers属性,handlers属性为map类型,key的类型是string,value的类型是Handler。其中Handler类型的定义位于./docker/docker/engine/engine.go#L23,具体代码如下:
type Handler func(*Job) Status
可见,Handler为一个定义的函数。该函数传入的参数为Job指针,返回为Status状态。
了解完Engine以及Handler的基本知识之后,我们真正进入创建Engine实例的部分,即New()函数的实现,具体代码如下:

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 handlers
     for k, v := range globalHandlers {
          eng.handlers[k] = v
     }
     return eng
}

分析以上代码,从返回结果可以发现,New()函数最终返回一个Engine实例对象。而在代码实现部分,大致可以将其分为三个步骤:
1)创建一个Engine结构体实例eng,并初始化部分属性,如handlers、id、标准输出stdout、日志属性Logging等。
2)向eng对象注册名为commands的Handler,其中Handler为临时定义的函数func(job *Job) Status{ },该函数的作用是通过Job来打印所有已经注册完毕的command名称,最终返回状态StatusOK。
3)将变量globalHandlers中定义完毕的所有Handler都复制到eng对象的handlers属性中。
至此,一个基本的Engine对象实例eng已经创建完毕,并实现部分属性的初始化。Docker Daemon启动的后续过程中,仍然会对Engine对象实例进行额外的配置。

3.3.4 设置engine的信号捕获

Docker在包engine中执行完Engine对象的创建与初始化之后,回到mainDaemon()函数的运行,紧接着执行的代码为:
signal.Trap(eng.Shutdown)
Docker Daemon作为Linux操作系统上的一个后台进程,原则上应该具备处理信号的能力。信号处理能力的存在,能保障Docker管理员可以通过向Docker Daemon发送信号的方式,管理Docker Daemon的运行。
再来看以上代码则不难理解其中的含义:在Docker Daemon的运行中,设置捕获特定信号后的处理方法,特定信号有SIGINT、SIGTERM以及SIGQUIT;当程序捕获SIGINT或者SIGTERM信号时,执行相应的善后操作,最后保证Docker Daemon程序退出。
该部分代码的实现位于./docker/docker/pkg/signal/trap.go。实现的流程分为以下4个步骤:
1)创建并设置一个channel,用于发送信号通知。
2)定义signals数组变量,初始值为os.SIGINT, os.SIGTERM;若环境变量DEBUG为空,则添加os.SIGQUIT至signals数组。
3)通过gosignal.Notify(c, signals...)中Notify函数来实现将接收到的signal信号传递给c。需要注意的是只有signals中被罗列出的信号才会被传递给c,其余信号会被直接忽略。
4)创建一个goroutine来处理具体的signal信号,当信号类型为os.Interrupt或者syscall.SIGTERM时,执行传入Trap函数的具体执行方法,形参为cleanup(),实参为eng.Shutdown。
Shutdown()函数的定义位于./docker/docker/engine/engine.go#L153-L199,主要完成的任务是:Docker Daemon关闭时,做一些必要的善后工作。
善后工作主要有以下4项:

Docker Daemon不再接受任何新的Job。
Docker Daemon等待所有存活的Job执行完毕。
Docker Daemon调用所有shutdown的处理方法。

在15秒时间内,若所有的handler执行完毕,则 Shutdown()函数返回,否则强制返回。
由于在signal.Trap( eng.Shutdown )函数的具体实现中,一旦程序接收到相应的信号,则会执行eng.Shutdown这个函数,在执行完eng.Shutdown之后,随即执行os.Exit(0),完成当前整个Docker Daemon程序的退出。源码实现位于./docker/docker/pkg/signal/trap.go#L33-L47。

3.3.5 加载builtins

DockerDaemon设置完Trap特定信号的处理方法(即eng.shutdown()函数)之后,Docker Daemon实现了builtins的加载。Docker的builtins可以理解为:Docker Daemon运行过程中,注册的一些任务(Job),这部分任务一般与容器的运行无关,与Docker Daemon的运行时信息有关。加载builtins的源码实现如下:

if err := builtins.Register(eng); err != nil {
     log.Fatal(err)
}

加载builtins完成的具体工作是:向engine注册多个Handler,以便后续在执行相应任务时,运行指定的Handler。这些Handler包括:Docker Daemon宿主机的网络初始化、Web API服务、事件查询、版本查看、Docker Registry的验证与搜索等。源码实现位于./docker/docker/builtins/builtins.go#L16-L30,如下:

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)
}

下面分析Register函数实现过程中最为主要的5个部分:daemon(eng)、remote(eng)、events.New().Install(eng)、eng.Register("version",dockerVersion)以及registry.NewService().Install(eng)。

  1. 注册网络初始化处理方法
    daemon(eng)的实现过程,主要为eng对象注册了一个键为"init_networkdriver"的处理方法,此处理方法的值为bridge.InitDriver函数,源码如下:
func daemon(eng *engine.Engine) error {
     return eng.Register("init_networkdriver", bridge.InitDriver)
}

需要注意的是,向eng对象注册处理方法,并不代表处理方法的值函数会被立即调用执行,如注册init_networkdrive时bridge.InitDriver并不会直接运行,而是将bridge.InitDriver的函数入口作为init_networkdriver的值,写入eng的handlers属性中。当Docker Daemon接收到名为init_networkdriver的Job的执行请求时,bridge.InitDriver才被Docker Daemon调用执行。
Bridge.InitDriver的具体实现位于./docker/docker/daemon/networkdriver/bridge/driver.go#79-L175,主要作用为:
获取为Docker服务的网络设备地址。
创建指定IP地址的网桥。
配置网络iptables规则。
另外还为eng对象注册了多个Handler,如allocate_interface、release_interface、allocate_port以及link等。
本书将在第6章详细分析Docker Daemon如何初始化宿主机的网络环境。

  1. 注册API服务处理方法
    remote(eng)的实现过程,主要为eng对象注册了两个Handler,分别为serveapi与acceptconnections,源码实现如下:
func remote(eng *engine.Engine) error {
     if err := eng.Register("serveapi", apiserver.ServeApi); err != nil {
          return err
     }
     return eng.Register("acceptconnections", apiserver.AcceptConnections)
}

注册的两个处理方法名称分别为serveapi与acceptconnections,相应的执行方法分别为apiserver.ServeApi与apiserver.AcceptConnections,具体实现位于./docker/docker/api/server/server.go。其中,ServeApi执行时,通过循环多种指定协议,创建出goroutine协调来配置指定的http.Server,最终为不同协议的请求服务;而AcceptConnections的作用主要是:通知宿主机上init守护进程Docker Daemon已经启动完毕,可以让Docker Daemon开始服务API请求。

  1. 注册events事件处理方法
    events.New().Install(eng)的实现过程,为Docker注册了多个event事件,功能是给Docker用户提供API,使得用户可以通过这些API查看Docker内部的events信息,log信息以及subscribers_count信息。具体的源码位于./docker/docker/events/events.go#L29-L42,如下所示:
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
}
  1. 注册版本处理方法
    eng.Register("version",dockerVersion)的实现过程,向eng对象注册key为version,value为dockerVersion执行方法的Handler。dockerVersion的执行过程中,会向名为version的Job的标准输出中写入Docker的版本、Docker API的版本、git版本、Go语言运行时版本,以及操作系统版本等信息。dockerVersion的源码实现如下:
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
}
  1. 注册registry处理方法
    registry.NewService().Install(eng)的实现过程位于./docker/docker/registry/service.go,功能是:在eng对象对外暴露的API信息中添加docker registry的信息。若registry.NewService()被成功安装,则会有两个相应的处理方法注册至eng,Docker Daemon通过Docker Client提供的认证信息向registry发起认证请求;search,在公有registry上搜索指定的镜像,目前公有的registry只支持Docker Hub。

Install的具体实现如下:

func (s *Service) Install(eng *engine.Engine) error {
     eng.Register("auth", s.Auth)
     eng.Register("search", s.Search)
     return nil
}

至此,Docker Daemon所有builtins的加载全部完成,实现了向eng对象注册特定的处理方法。

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的实现。

  1. 通过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。

  1. 运行名为acceptconnections的Job
    Docker在goroutine的最后环节运行名为acceptconnections的Job,主要作用是通知init守护进程,使Docker Daemon开始接受请求。源码位于./docke`javascript

r/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的工作全部完成。
###3.3.7 打印Docker版本及驱动信息
Docker再次回到mainDaemon()的运行流程,由于Go语言goroutine的性质,在goroutine执行之时,mainDaemon()函数内部其他代码也会并发执行。
第一个执行的即为显示Docker的版本信息、GitCommit信息、ExecDriver和GraphDriver这两个驱动的具体信息,源码如下:

log.Printf("docker daemon: %s %s; execdriver: %s; graphdriver: %s",

 dockerversion.VERSION,
 dockerversion.GITCOMMIT,
 daemonCfg.ExecDriver,
 daemonCfg.GraphDriver,

)

###3.3.8 serveapi的创建与运行
打印Docker的部分具体信息之后,Docker Daemon立即创建并运行名为serveapi的Job,主要作用为让Docker Daemon提供Docker Client发起的API服务。实现代码位于./docker/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)

}

以上代码标志着Docker Daemon真正进入状态。实现过程中,Docker首先创建一个名为serveapi的Job,并将flHosts的值赋给job.Args。flHosts的作用主要是:为Docker Daemon提供使用的协议与监听的地址。随后,Docker Daemon为该Job设置了众多的环境变量,如安全传输层协议的环境变量等。最后通过job.Run()运行该serveapi的Job。
由于在eng中key为serveapi的handler,value为apiserver.ServeApi,故该Job运行时,执行apiserver.ServeApi函数,位于./docker/docker/api/server/server.go。ServeApi函数的作用是:对于所有用户定义支持协议,Docker Daemon均创建一个goroutine来启动相应的http.Server,并为每一种协议服务。
由于创建并启动http.Server为Docker架构中有关Docker Server的重要内容,本书将在第5章深入介绍Docker Server。
相关文章
|
20天前
|
Kubernetes 调度 Docker
深入探讨容器编排技术:从Docker到Kubernetes
容器编排在现代IT中日益关键,从Docker到Kubernetes,助力应用的自动化部署和扩展。Docker提供容器技术,打包应用及环境为镜像,通过引擎、镜像、容器、网络和存储组件实现隔离运行。Kubernetes作为高级容器编排平台,管理Pod(多容器集合),其核心包括API服务器、控制器管理器、调度器和Kubelet。Kubernetes集群由Master和Worker节点构成,实现Pod的高效调度和运行。
|
21天前
|
运维 Kubernetes 监控
构建高效自动化运维系统:基于容器技术的策略与实践
【4月更文挑战第19天】随着云计算和微服务架构的兴起,传统的运维模式正逐渐向自动化、智能化转型。本文将探讨如何利用容器技术构建一个高效、可靠的自动化运维系统,涵盖系统设计原则、关键技术选型以及实践经验分享。通过引入容器技术,我们可以实现应用的快速部署、弹性伸缩和故障自愈,从而提高运维效率,降低系统维护成本。
|
29天前
|
持续交付 开发者 Docker
深入了解 Docker:革命性的容器化技术
Docker,自2013年起,革新了软件开发和部署,解决了环境差异问题。这个轻量级容器技术基于Linux容器(LXC),提供应用程序隔离和环境一致性。Docker通过容器化实现快速部署、CI/CD集成和微服务架构。相比虚拟机,Docker更轻便,不需模拟完整操作系统。它简化流程,提升效率,促进了容器化技术的普及。
71 0
|
13天前
|
Cloud Native Linux 开发者
【Docker】Docker:解析容器化技术的利器与在Linux中的关键作用
【Docker】Docker:解析容器化技术的利器与在Linux中的关键作用
|
2天前
|
Kubernetes Java 调度
Java容器技术:Docker与Kubernetes
Java容器技术:Docker与Kubernetes
12 0
|
10天前
|
敏捷开发 运维 测试技术
构建高效自动化运维体系:基于容器技术的持续集成与持续部署实践
【4月更文挑战第30天】在数字化转型的浪潮中,企业对软件交付速度和质量的要求日益提高。自动化运维作为提升效率、确保稳定性的关键手段,其重要性不言而喻。本文将探讨如何利用容器技术构建一个高效的自动化运维体系,实现从代码提交到产品上线的持续集成(CI)与持续部署(CD)。通过分析现代容器技术与传统虚拟化的差异,阐述容器化带来的轻量化、快速部署及易于管理的优势,并结合实例讲解如何在实际环境中搭建起一套完善的CI/CD流程。
|
10天前
|
Linux 开发者 Docker
Docker容器化技术详解
【4月更文挑战第30天】Docker,一个开源的容器化平台,助力开发者通过轻量级容器打包应用及依赖,实现跨平台快速部署。核心概念包括:容器(可执行的软件包)、镜像(只读模板)、Dockerfile(构建镜像的指令文件)和仓库(存储镜像的地方)。Docker利用Linux内核功能隔离容器,采用联合文件系统构建镜像。广泛应用包括开发测试一致性、微服务部署、CI/CD以及本地到远程部署。通过安装Docker,编写Dockerfile,构建&运行容器,可实现高效灵活的应用管理。随着容器技术进步,Docker在云计算和DevOps中的角色日益重要。
|
10天前
|
运维 Kubernetes 持续交付
构建高效自动化运维系统:基于容器技术的持续集成与持续部署实践
【4月更文挑战第30天】 在快速发展的云计算时代,传统的运维模式已无法满足敏捷开发和快速迭代的需求。本文将介绍如何利用容器技术搭建一套高效自动化运维系统,实现软件的持续集成(CI)与持续部署(CD)。文章首先探讨了现代运维面临的挑战,接着详细阐述了容器技术的核心组件和工作原理,最后通过实际案例展示了如何整合这些组件来构建一个可靠、可扩展的自动化运维平台。
|
10天前
|
Kubernetes 安全 持续交付
现代软件开发中的容器化技术探究
本文探讨了现代软件开发中的容器化技术,重点介绍了Docker和Kubernetes这两个主流容器化平台的原理和应用。通过分析容器化技术的优势和挑战,以及在不同开发环境下的应用场景,读者可以更好地理解容器化技术在提升软件开发效率和部署灵活性方面的价值。
|
11天前
|
Linux Shell 虚拟化
【Linux学习】Linux 的虚拟化和容器化技术
【Linux学习】Linux 的虚拟化和容器化技术