閱讀187 返回首頁    go 技術社區[雲棲]


《容器技術係列》一3.3 mainDaemon()的具體實現

本節書摘來異步社區《容器技術係列》一書中的第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如何初始化宿主機的網絡環境。
2. 注冊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請求。
3. 注冊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的源碼實現如下: ```javascript 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 }
5. 注冊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的具體實現如下:
```javascript
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的實現。
2. 通過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。
3. 運行名為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,如下所示:
```javascript
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。
至此,我們可以認為Docker Daemon已經完成了serveapi這個Job的初始化工作。一旦acceptconnections這個Job運行完畢,Docker Daemon則會通知init進程Docker Daemon啟動完畢,可以開始提供API服務,兩個Job通過activationLock進行同步。

最後更新:2017-07-21 15:32:34

  上一篇:go  開發者論壇一周精粹(第十二期):如何通過快照的瘦身和刪除來節省儲存費用
  下一篇:go  雲辦公軟件相比傳統辦公軟件的優越性