设为首页 收藏本站
查看: 1225|回复: 0

[经验分享] Docker Daemon启动

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-4-15 08:39:36 | 显示全部楼层 |阅读模式
Docker Daemon启动

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

Docker Daemon架构示意图:
wKiom1UtFbbALM7fAAByRFTq0b0217.jpg
Docker Daemon启动流程图
wKioL1UtFqywqP4oAAD47oZOX20648.jpg




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

mainDaemon()方法:
功能:1)创建Docker运行环境;2)服务于Docker Client,接受并处理请求。
细节:
1)  daemon的配置初始化;(在init()函数中实现,即mainDaemon()运行前就执行)
1
2
3
4
5
6
var (
daemonCfg = &daemon.Config{}
)
funcinit() {
daemonCfg.InstallFlags()
}



首先,声明一个为daemon包中Config类型的变量,名为daemonCfg。而Config对象,定义了Docker Daemon所需的配置信息。在Docker Daemon在启动时,daemonCfg变量被传递至Docker Daemon并被使用。
Config对象的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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():
1
2
3
4
5
6
7
8
9
10
11
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的时候,传入了多余的参数,此时会输出错误提示,并退出运行程序。具体代码如下:
1
2
3
4
ifflag.NArg() != 0 {
flag.Usage()
return
}




3)  创建engine对象;
mainDaemon()运行过程中,flag参数检查完毕之后,随即创建engine对象,代码如下:
eng :=engine.New()
Engine是Docker架构中的运行引擎,同时也是Docker运行的核心模块。Engine扮演着Docker container存储仓库的角色,并且通过job的形式来管理这些容器。
在./docker/engine/engine.go中,Engine结构体的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()函数的实现中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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对象注册名为commands的Handler,其中Handler为临时定义的函数func(job *Job) Status{ } , 该函数的作用是通过job来打印所有已经注册完毕的command名称,最终返回状态StatusOK;第三个工作是:将已定义的变量globalHandlers中的所有的Handler,都复制到eng对象的handlers属性中。最后成功返回eng对象。

4)  设置engine的信号捕获及处理方法;
执行后续代码:
signal.Trap(eng.Shutdown)
该部分代码的作用是:在Docker Daemon的运行中,设置Trap特定信号的处理方法,特定信号有SIGINT,SIGTERM以及SIGQUIT;当程序捕获到SIGINT或者SIGTERM信号时,执行相应的善后操作,最后保证Docker Daemon程序退出。
Trap()函数的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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.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 Daemon的关闭做一些善后工作。
善后工作如下:
1)Docker Daemon不再接收任何新的Job;
2)Docker Daemon等待所有存活的Job执行完毕;
3)Docker Daemon调用所有shutdown的处理方法;
4)当所有的handler执行完毕,或者15秒之后,Shutdown()函数返回。
5)  加载builtins;
为eng设置完Trap特定信号的处理方法之后,Docker Daemon实现了builtins的加载。代码实现如下:
1
2
3
if err :=builtins.Register(eng); err != nil {
log.Fatal(err)
}



加载builtins的主要工作是为:为engine注册多个Handler,以便后续在执行相应任务时,运行指定的Handler。这些Handler包括:网络初始化、web API服务、事件查询、版本查看、Docker Registry验证与搜索。代码实现位于./docker/builtins/builtins.go,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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):注册registry的Handler
6)  使用goroutine加载daemon对象并运行;
执行完builtins的加载,回到mainDaemon()的执行,通过一个goroutine来加载daemon对象并开始运行。这一环节的执行,主要包含三个步骤:
1)通过init函数中初始化的daemonCfg与eng对象来创建一个daemon对象d;
2)通过daemon对象的Install函数,向eng对象中注册众多的Handler;
3)在Docker Daemon启动完毕之后,运行名为”acceptconnections”的job,主要工作为向init守护进程发送”READY=1”信号,以便开始正常接受请求。
代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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相关的Handler,Install的实现位于./docker/graph/service.go;
  • eng.Hack_SetGlobalVar("httpapi.daemon", daemon)实现向eng对象中map类型的hack对象中添加一条记录,key为”httpapi.daemon”,value为daemon。


3)运行acceptconnection的job:
在goroutine内部最后运行名为”acceptconnections”的job,主要作用是通知init守护进程,Docker Daemon可以开始接受请求了。
7)  打印Docker版本信息及驱动信息;
回到mainDaemon()的运行流程中,在goroutine的执行之时,mainDaemon()函数内部其它代码也会并发执行。
显示docker的版本信息,以及ExecDriver和GraphDriver这两个驱动的具体信息,代码如下:
1
2
3
4
5
6
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.Args。flHost的作用主要是为Docker Daemon提供使用的协议与监听的地址。随后,Docker Daemon为该job设置了众多的环境变量,如安全传输层协议的环境变量等。最后通过job.Run()运行该serveapi的job。


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





运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-57238-1-1.html 上篇帖子: Docker Client创建和命令执行 下篇帖子: Docker image的工作原理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表