docker-deamon
brief
这篇文章主要讨论docker deamon的源码,docker deamon在docker中是较重要的一部分,对理解docker源码很重要。主要分为三部分:docker server、engine、job,三部分。架构如下所示:
http://static.scloud.letv.com/res/1b88b892-6e99-4241-b325-8f9e8b3a4b85.jpg” alt=”image” title=”” />)
启动流程
上一篇文章说过:在docker/docker.go的main函数中,line53:
if *flDaemon {
mainDaemon()
return
}
注意:在运行此函数之前,docker其实初始化了docker deamon的flag参数,代码在docker/deamon/config中,感兴趣的同学可以看看。
在看看mainDaemon()代码,docker/deamon.go:line28-line81
以后针对稍微长一点的函数,咱一块一块的粘。
Line28-38
func mainDaemon() {
if flag.NArg() != 0 {
flag.Usage()
return
}
eng := engine.New()
signal.Trap(eng.Shutdown)
// Load builtins
if err := builtins.Register(eng); err != nil {
log.Fatal(err)
}
以上代码主要是:
1). 看看是否传入了多余参数
2). 创建了Engine对象。Engine是docker运行的引擎,其创建各种任务管理docker运行的各种任务。
接下来看一下Engine的定义:docker/engine/engine.go,Line44-60
Engine定义
// The Engine is the core of Docker.
// It acts as a store for *containers*, and allows manipulation of these
// containers by executing *jobs*.
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
}
其中Handler的定义:
type Handler func(*Job) Status
Engine初始化
再具体的看看engine.New()究竟干了啥:docker/engine/engine.go,Line75-96
// New initializes a new engine.
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
}
从以上代码中可以看到:
1.对Engine的部分属性进行了初始化,如:handlers、id、标准输入输出、日志标志灯;2.注册了一个临时函数commands来打印查看已经注册的命令;
3.把globalHandlers中已经注册的handler添加上。
对比Engine发现还有部分属性没进行初始化,在docker deamon启动过程中其它属性也会陆续初始化。
signal trap捕获信号
signal.Trap(eng.Shutdown)是信号处理函数,实现代码在docker/signal/trap.go
// Trap sets up a simplified signal "trap", appropriate for common
// behavior expected from a vanilla unix command-line tool in general
// (and the Docker engine in particular).
//
// * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated.
// * If SIGINT or SIGTERM are repeated 3 times before cleanup is complete, then cleanup is
// skipped and the process terminated directly.
// * If "DEBUG" is set in the environment, SIGQUIT causes an exit without cleanup.
//
func Trap(cleanup func()) {
c := make(chan os.Signal, 1)
signals := []os.Signal{os.Interrupt, syscall.SIGTERM}
if os.Getenv("DEBUG") == "" {
signals = append(signals, syscall.SIGQUIT)
}
gosignal.Notify(c, signals...)
go func() {
interruptCount := uint32(0)
for sig := range c {
go func(sig os.Signal) {
log.Printf("Received signal '%v', starting shutdown of docker...\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 of docker, interrupting cleanup\n")
}
case syscall.SIGQUIT:
}
os.Exit(128 + int(sig.(syscall.Signal)))
}(sig)
}
}()
}
trap函数的目的在于处理docker管理员发给docker deamon的信号, Docker Daemon 关闭时,做一些必要的善后工作。trap的形参是cleanup,实参是eng.Shutdown。
可以看一下eng.Shutdown,在docker/engine/engine.go Line153-199。善后工作主要包括:
1.不再接受新的job;
2.等待存活的job执行完毕;
3.调用所有任务shutdown方法;
4.15秒内还有未执行完的shutdown方法,强制停止返回。
Load builtins
Docker Daemon 运行过程中,
注册的一些任务(Job),这部分任务一般与容器的运行无关,与 Docker Daemon 的运行时信
息有关。
看代码:
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)
}
下面依次看daemon(eng)、 remote(eng)、
events.New().Install(eng)、 eng.Register(“version”,dockerVersion) 以及 registry.NewService().
Install(eng)。
daemon(eng)
func daemon(eng *engine.Engine) error {
return eng.Register("init_networkdriver", bridge.InitDriver)
}
注册网络初始化处理方法,看到Register方法如下:docker/engine/engine.go Line62-69
func (eng *Engine) Register(name string, handler Handler) error {
_, exists := eng.handlers[name]
if exists {
return fmt.Errorf("Can't overwrite handler for command %s", name)
}
eng.handlers[name] = handler
return nil
}
上面的代码可以看到Register只是注册了handler,并没有执行函数。只有init_networkdriver 的 Job 的执行请求时, bridge.InitDriver 才被调用执行。
remote(eng)
// remote: a RESTful api for cross-docker communication
func remote(eng *engine.Engine) error {
if err := eng.Register("serveapi", apiserver.ServeApi); err != nil {
return err
}
return eng.Register("acceptconnections", apiserver.AcceptConnections)
}
创建出apiserver.ServeApi和apiserver.AcceptConnections,这两个handler在后续学习文章中详细介绍。
events.New().Install(eng)
给Docker 用户提供 API,使得用户可以通过这些 API 查看 Docker 内部的 events 信息, log 信息以及 subscribers_count 信息。
// Install installs events public api in docker engine
func (e *Events) Install(eng *engine.Engine) error {
// Here you should describe public interface
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
}
registry.NewService().Install(eng)
注册事件在公有 registry 上搜索指定的镜像。为用户提供API
Ok,现在又回到docker deamon启动主流程,docker/deamon/deamon.go Line40-56
// load the daemon in the background so we can immediately start
// the http api so that connections don't fail while the daemon
// is booting
go func() {
d, err := daemon.NewDaemon(daemonCfg, eng)
if err != nil {
log.Fatal(err)
}
if err := d.Install(eng); err != nil {
log.Fatal(err)
}
// 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)
}
}()
以上代码启动一个goroutine来启动docker server。主要包括:
1.创建Daemon类型对象d;–这部分由于内容比较多,下一篇文章将详细;
2.d.Install(eng)注册一些handler;
3.运行Job acceptconnections。
下面将具体看下acceptconnections:
eng.Job(“acceptconnections”) 的实现位于 docker/engine/engine.go Line115-L137
// Job creates a new job which can later be executed.
// This function mimics `Command` from the standard os/exec package.
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))
}
// Catchall is shadowed by specific Register.
if handler, exists := eng.handlers[name]; exists {
job.handler = handler
} else if eng.catchall != nil && name != "" {
// empty job names are illegal, catchall or not.
job.handler = eng.catchall
}
return job
}
在加载 builtins 时,源码 remote(eng) 已经向 eng 注册过这样一条记录,键为 acceptconnections,值为 apiserver.AcceptConnections。故job.status =apiserver.AcceptConnections(job)。
AcceptConnections 的具体实现在docker/api/server/server.go Line1370-1380:
func AcceptConnections(job *engine.Job) engine.Status {
// Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1")
// close the lock so the listeners start accepting connections
if activationLock != nil {
close(activationLock)
}
return engine.StatusOK
}
Job启动大同小异,最后我们看到启动名为serveapi的任务,主要作用是为docker clinet提供api服务。
代码如下:
// Serve api
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 server,将在下一篇文章中具体讨论,docker server的创建。
路漫漫其修远兮,吾将…