docker deamon源碼學習

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的創建。

路漫漫其修遠兮,吾將…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章