Docker Daemon啓動

Docker Daemon啓動

 

Docker Daemon Docker架構中運行在後臺的守護進程,可分爲Docker ServerEngineJob三部分。

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()運行前就執行)

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()的實現。


 

 

 


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