码迷,mamicode.com
首页 > 其他好文 > 详细

Docker Daemon启动

时间:2015-04-14 23:28:30      阅读:494      评论:0      收藏:0      [点我收藏+]

标签:docker daemon启动

Docker Daemon启动

 

Docker Daemon Docker架构中运行在后台的守护进程,可分为Docker ServerEngineJob三部分。

Docker Daemon 是通过Docker Server模块接受Docker Client的请求,并在Engine中处理请求,然后根据请求类型,创建出指定的Job并运行,运行过程的几种可能:向Docker Registry获取镜像,通过graphdriver执行容器镜像的本地化操作,通过networkdriver执行容器网络环境的配置,通过execdriver执行容器内部运行的执行工作等。

 

Docker Daemon架构示意图:

 技术分享

Docker Daemon启动流程图

技术分享 

 

 

 

 

通过流程图可以看出,有关DockerDaemon的所有工作都在mainDaemon()方法中实现。

 

mainDaemon()方法:

功能:1)创建Docker运行环境;2)服务于Docker Client,接受并处理请求。

细节:

1)  daemon的配置初始化;(在init()函数中实现,即mainDaemon()运行前就执行)

var (
daemonCfg = &daemon.Config{}
)
funcinit() {
daemonCfg.InstallFlags()
}

首先,声明一个为daemon包中Config类型的变量,名为daemonCfg。而Config对象,定义了Docker Daemon所需的配置信息。在Docker Daemon在启动时,daemonCfg变量被传递至Docker Daemon并被使用。

Config对象的定义如下

typeConfig struct {
Pidfile                  string   //Docker Daemon所属进程的PID文件
Root                   string   //Docker运行时所使用的root路径
AutoRestart             bool    //已被启用,转而支持docker run时的重启
Dns                  []string  //Docker使用的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 //是否允许相同host上容器间的通信
GraphDriver             string      //Docker运行时使用的特定存储驱动
GraphOptions            []string   //可设置的存储驱动选项
ExecDriver               string    // Docker运行时使用的特定exec驱动
Mtu                    int      //设置容器网络的MTU
DisableNetwork          bool     //有定义,之后未初始化
EnableSelinuxSupport      bool    //启用SELinux功能的支持
Context                 map[string][]string   //有定义,之后未初始化
}

init()函数实现了daemonCfg变量中各属性的赋值,具体的实现为:daemonCfg.InstallFlags()

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",
"Pathto use as the root of the Docker runtime")
……
opts.IPVar(&config.DefaultIp,[]string{"#ip", "-ip"}, "0.0.0.0", "DefaultIP address to
use whenbinding container ports")
opts.ListVar(&config.GraphOptions,[]string{"-storage-opt"}, "Set storage driver options")
……
}

InstallFlags()函数的实现过程中,主要是定义某种类型的flag参数,并将该参数的值绑定在config变量的指定属性上

2)  命令行flag参数检查;

docker命令经过flag参数解析之后,判断剩余的参数是否为0。若为0,则说明Docker Daemon的启动命令无误,正常运行;若不为0,则说明在启动DockerDaemon的时候,传入了多余的参数,此时会输出错误提示,并退出运行程序。具体代码如下:

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

 

3)  创建engine对象;

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

eng :=engine.New()

EngineDocker架构中的运行引擎,同时也是Docker运行的核心模块。Engine扮演着Docker container存储仓库的角色,并且通过job的形式来管理这些容器。

./docker/engine/engine.go,Engine结构体的定义如下:

type Enginestruct {
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
}

之后,进入New()函数的实现中:

func New()*Engine {
//创建Engine结构体实例eng
eng := &Engine{
        handlers: make(map[string]Handler),
        id:      utils.RandomString(),
        Stdout:  os.Stdout,
        Stderr:  os.Stderr,
        Stdin:   os.Stdin,
        Logging: true,
}
//向eng对象注册名为commands的Handler
eng.Register("commands", func(job*Job) Status {
        for _, name := range eng.commands() {
               job.Printf("%s\n",name)
        }
        return StatusOK
})
// Copy existing global handlers将已定义的变量globalHandlers中的所有的Handler,都
//复制到eng对象的handlers属性中
for k, v := range globalHandlers {
        eng.handlers[k] = v
}
return eng
}

New()函数最终返回一个Engine对象。在代码实现部分,第一个工作即为创建一个Engine结构体实例eng;第二个工作是向eng对象注册名为commandsHandler,其中Handler为临时定义的函数func(job *Job) Status{ } , 该函数的作用是通过job来打印所有已经注册完毕的command名称,最终返回状态StatusOK;第三个工作是:将已定义的变量globalHandlers中的所有的Handler,都复制到eng对象的handlers属性中。最后成功返回eng对象。

 

4)  设置engine的信号捕获及处理方法;

执行后续代码:

signal.Trap(eng.Shutdown)

该部分代码的作用是:在Docker Daemon的运行中,设置Trap特定信号的处理方法,特定信号有SIGINTSIGTERM以及SIGQUIT;当程序捕获到SIGINT或者SIGTERM信号时,执行相应的善后操作,最后保证Docker Daemon程序退出。

Trap()函数的代码如下:

funcTrap(cleanup func()) {
       c:= make(chan os.Signal, 1)
       signals:= []os.Signal{os.Interrupt, syscall.SIGTERM}
       ifos.Getenv("DEBUG") == "" {
               signals= append(signals, syscall.SIGQUIT)
       }
       gosignal.Notify(c,signals...)
       gofunc() {
               interruptCount:= uint32(0)
               forsig := range c {
                      gofunc(sig os.Signal) {
                             log.Printf("Received signal ‘%v‘, starting shutdown ofdocker...\n", sig)
                             switch sig {
                             case os.Interrupt, syscall.SIGTERM:
                                    // If the user really wants to interrupt,let him do so.
                                    if atomic.LoadUint32(&interruptCount)< 3 {
                                           atomic.AddUint32(&interruptCount, 1)
                                           // Initiate the cleanup only once
                                           if atomic.LoadUint32(&interruptCount)== 1 {
                                                  // Call cleanup handler
                                                  cleanup()
                                                  os.Exit(0)
                                           } else {
                                                  return
                                           }
                                    } else {
                                           log.Printf("Force shutdown ofdocker, interrupting cleanup\n")
                                    }
                             case syscall.SIGQUIT:
                             }
                             os.Exit(128 + int(sig.(syscall.Signal)))
                      }(sig)
               }
       }()
}

实现流程分为4个步骤:

1)创建并设置一个channel,用于发送信号通知;

2)定义signals数组变量,初始值为os.SIGINT, os.SIGTERM;若环境变量DEBUG为空的话,则添加os.SIGQUITsignals数组;

3)通过gosignal.Notify(c, signals...)Notify函数来实现将接收到的signal信号传递给c。需要注意的是只有signals中被罗列出的信号才会被传递给c,其余信号会被直接忽略;

4)创建一个goroutine来处理具体的signal信号,当信号类型为os.Interrupt或者syscall.SIGTERM时,执行传入Trap函数的具体执行方法,形参为cleanup(),实参为eng.Shutdown

 

Shutdown()函数的工作是为Docker Daemon的关闭做一些善后工作。

善后工作如下:

1Docker Daemon不再接收任何新的Job

2Docker Daemon等待所有存活的Job执行完毕;

3Docker Daemon调用所有shutdown的处理方法;

4)当所有的handler执行完毕,或者15秒之后,Shutdown()函数返回。

5)  加载builtins;

eng设置完Trap特定信号的处理方法之后,Docker Daemon实现了builtins的加载。代码实现如下:

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

加载builtins的主要工作是为:为engine注册多个Handler,以便后续在执行相应任务时,运行指定的Handler。这些Handler包括:网络初始化、web API服务、事件查询、版本查看、Docker Registry验证与搜索。代码实现位于./docker/builtins/builtins.go,如下:

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

以上代码实现主要有5个部分:daemon(eng)remote(eng)events.New().Install(eng)eng.Register(“version”,dockerVersion)以及registry.NewService().Install(eng)

daemon(eng):注册初始化网络驱动的Handler

remote(eng):注册API服务的Handler

events.New().Install(eng):注册events事件的Handler

eng.Register(“version”,dockerVersion):注册版本的Handler

registry.NewService().Install(eng):注册registryHandler

6)  使用goroutine加载daemon对象并运行;

执行完builtins的加载,回到mainDaemon()的执行,通过一个goroutine来加载daemon对象并开始运行。这一环节的执行,主要包含三个步骤:

1)通过init函数中初始化的daemonCfgeng对象来创建一个daemon对象d

2)通过daemon对象的Install函数,向eng对象中注册众多的Handler

3)在Docker Daemon启动完毕之后,运行名为”acceptconnections”的job,主要工作为向init守护进程发送”READY=1”信号,以便开始正常接受请求。

代码实现如下:

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.MainDaemon(daemonCfg, eng)是创建daemon对象d的核心部分。主要作用为初始化Docker Daemon的基本环境,如处理config参数,验证系统支持度,配置Docker工作目录,设置与加载多种driver,创建graph环境等,验证DNS配置等。

2)通过daemon对象为engine注册Handler:

创建完daemon对象,goroutine执行d.Install(eng),具体实现位于./docker/daemon/daemon.go:

func (daemon*Daemon) Install(eng *engine.Engine) error {
for name, method := rangemap[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
}

以上代码的实现分为三部分:

  • eng对象中注册众多的Handler对象;

  • daemon.Repositories().Install(eng)实现了向eng对象注册多个与image相关的HandlerInstall的实现位于./docker/graph/service.go

  • eng.Hack_SetGlobalVar("httpapi.daemon", daemon)实现向eng对象中map类型的hack对象中添加一条记录,key”httpapi.daemon”valuedaemon

 

3)运行acceptconnectionjob:

goroutine内部最后运行名为”acceptconnections”job,主要作用是通知init守护进程,Docker Daemon可以开始接受请求了。

7)  打印Docker版本信息及驱动信息;

回到mainDaemon()的运行流程中,在goroutine的执行之时,mainDaemon()函数内部其它代码也会并发执行。

显示docker的版本信息,以及ExecDriverGraphDriver这两个驱动的具体信息,代码如下:

logrus.WithFields(logrus.Fields{
               "version":     dockerversion.VERSION,
               "commit":      dockerversion.GITCOMMIT,
               "execdriver":  d.ExecutionDriver().Name(),
               "graphdriver":d.GraphDriver().String(),
        }).Info("Docker daemon")

8)  Job“serveapi”的创建与运行。

打印部分Docker具体信息之后,DockerDaemon立即创建并运行名为”serveapi”job,主要作用为让Docker Daemon提供API访问服务。实现代码位于./docker/docker/daemon.go:

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

实现过程中,首先创建一个名为”serveapi”job,并将flHosts的值赋给job.ArgsflHost的作用主要是为Docker Daemon提供使用的协议与监听的地址。随后,Docker Daemon为该job设置了众多的环境变量,如安全传输层协议的环境变量等。最后通过job.Run()运行该serveapijob

 

至此,可以认为DockerDaemon已经完成了serveapi这个job的初始化工作。一旦acceptconnections这个job运行完毕,则会通知init进程Docker Daemon启动完毕,可以开始提供API服务。本文从源码的角度简单分析了Docker Daemon的启动,着重分析了mainDaemon()的实现。


 

 

 


Docker Daemon启动

标签:docker daemon启动

原文地址:http://9320314.blog.51cto.com/9310314/1632738

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!