//後續補上Docker的架構分析
Docker Client創建及命令執行
Docker架構:http://www.infoq.com/cn/articles/docker-source-code-analysis-part1
從整體的架構圖中,可以看出Docker client在架構中的位置,Docker client是用戶用來和Docker Daemon(主體部分)建立通信的客戶端。用戶使用Docker可執行文件發起管理container的請求。
Go語言:Go是Google開發的一種編譯型,可平行化,並具有垃圾回收功能的編程語言。
每個Go程序都是由包組成的,程序運行的入口是包main。Docker是用Go語言開發的。
1、 Docker Client創建
Docker Client創建其實是用戶通過docker可執行文件與DockerServer創建聯繫的客戶端。
1) flag參數解析
參數包含兩類:一類是命令行參數,另一類是請求參數。
Docker Client創建的源碼位於./docker/docker/docker.go,該文件包含整個Docker的main函數,是整個Docker運行的入口。
https://github.com/docker/docker/blob/v1.2.0/docker/docker.go
func main() { if reexec.Init() { return } flag.Parse() …… }
reexec.Init()判斷是否已經初始化註冊,flag.Parse()解析命令行參數。
在./docker/docker/flag.go中定義了多個flag參數,並通過init函數進行初始化。
https://github.com/docker/docker/blob/v1.2.0/docker/flags.go
var ( flVersion = flag.Bool([]string{"v","-version"}, false, "Print version information and quit") flDaemon = flag.Bool([]string{"d","-daemon"}, false, "Enable daemon mode") flDebug = flag.Bool([]string{"D","-debug"}, false, "Enable debug mode") flSocketGroup =flag.String([]string{"G", "-group"}, "docker","Group to assign the unix socket specified by -H when running in daemonmode\nuse '' (the empty string) to disable setting of a group") flEnableCors =flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"},false, "Enable CORS headers in the remote API") flTls =flag.Bool([]string{"-tls"}, false, "Use TLS; implied bytls-verify flags") flTlsVerify =flag.Bool([]string{"-tlsverify"}, false, "Use TLS and verify theremote (daemon: verify client, client: verify daemon)") // these are initialized in init() belowsince their default values depend on dockerCertPath which isn't fullyinitialized until init() runs flCa *string flCert *string flKey *string flHosts []string )
func init() { flCa =flag.String([]string{"-tlscacert"}, filepath.Join(dockerCertPath,defaultCaFile), "Trust only remotes providing a certificate signed by theCA given here") flCert =flag.String([]string{"-tlscert"}, filepath.Join(dockerCertPath,defaultCertFile), "Path to TLS certificate file") flKey = flag.String([]string{"-tlskey"},filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS keyfile") opts.HostListVar(&flHosts,[]string{"H", "-host"}, "The socket(s) to bind to indaemon mode\nspecified using one or more tcp://host:port,unix:///path/to/socket, fd://* or fd://socketfd.") }
看出Docker定義了參數:flVersion、flDaemon、flDebug、flSocketGroup、flEnableCors、flTls、flTlsVerify、flCa、flCert、flKey等。並對大部分參數初始化。
flag參數解析完成兩個任務:解析命令行flag參數獲得相應的值;非命令行flag參數存入flag.Args()。
2) 處理flag信息並收集Docker Client配置信息
處理flag參數:flVersion、flDebug、flDaemon、flTlsVerify以及flTls;
收集Docker Client配置信息:protoAddrParts(通過flHosts參數獲得,作用爲提供Docker Client與Server的通信協議以及通信地址)、tlsConfig(通過一系列flag參數獲得,如*flTls、*flTlsVerify,作用爲提供安全傳輸層協議的保障)。
flag.Parse()之後代碼如下,對解析後的flag參數判斷:
//判斷flVersion,顯示版本信息 if *flVersion { showVersion() return } //判斷flDebug,創建DEBUG系統環境變量並初始化爲“1” if *flDebug { os.Setenv("DEBUG","1") } //分析flHosts,flHosts是爲Docker Client提供連接的host對象,爲Docker //Server提供需監聽的對象。 if len(flHosts) == 0 { defaultHost :=os.Getenv("DOCKER_HOST") if defaultHost == "" ||*flDaemon { // If we do not have a host,default to unix socket defaultHost =fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET) } if _, err :=api.ValidateHost(defaultHost); err != nil { log.Fatal(err) } flHosts = append(flHosts,defaultHost) } //判斷flDaemon,啓動Docker Daemon if *flDaemon { mainDaemon() return } //檢驗flHosts,通過分割解析出與Docker Server通信的協議與地址//(protoAddParts),爲Docker Client創建的配置信息。 if len(flHosts) > 1 { log.Fatal("Please specify onlyone -H") } protoAddrParts :=strings.SplitN(flHosts[0], "://", 2) //創建兩個變量:一個爲類型是client.DockerCli指針的對象cli,另一個爲類型//是tls.Config的對象tlsConfig。tlsConfig對象的創建是爲了保障cli在傳輸數//據的時候,遵循安全傳輸層協議(TLS)。tlsConfig是可選的配置信息。 var ( cli *client.DockerCli tlsConfig tls.Config ) tlsConfig.InsecureSkipVerify = true //判斷flTlsVerify,確保server段的安全性。 // If we should verify the server, we needto load a trusted ca if *flTlsVerify { *flTls = true certPool := x509.NewCertPool() file, err := ioutil.ReadFile(*flCa) if err != nil { log.Fatalf("Couldn'tread ca cert %s: %s", *flCa, err) } certPool.AppendCertsFromPEM(file) tlsConfig.RootCAs = certPool tlsConfig.InsecureSkipVerify =false } //判斷flTls和flTlsVerify,加載及發送client端的證書。 // If tls is enabled, try to load and sendclient certificates if *flTls || *flTlsVerify { _, errCert := os.Stat(*flCert) _, errKey := os.Stat(*flKey) if errCert == nil && errKey== nil { *flTls = true cert, err :=tls.LoadX509KeyPair(*flCert, *flKey) if err != nil { log.Fatalf("Couldn'tload X509 key pair: %s. Key encrypted?", err) } tlsConfig.Certificates =[]tls.Certificate{cert} } }
main函數執行到這裏,flag命令行參數處理完畢並收集了DockerClient的配置信息。
3) Docker Client的創建
通過上述獲得的Docker Client配置信息,
if *flTls ||*flTlsVerify { cli = client.NewDockerCli(os.Stdin,os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], &tlsConfig) } else { cli = client.NewDockerCli(os.Stdin,os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], nil) }
判斷flag參數flTls和flTlsVerify,確定是否使用TLS協議保障傳輸安全性。
創建函數是Client包中的NewDockerCli函數,具體見./docker/api/client/cli.go:
funcNewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string,tlsConfig *tls.Config) *DockerCli { var ( isTerminal = false terminalFd uintptr scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { if file, ok := out.(*os.File); ok { terminalFd = file.Fd() isTerminal =term.IsTerminal(terminalFd) } } if err == nil { err = out } return &DockerCli{ proto: proto, //傳輸協議 addr: addr, //host的目標地址 in: in, out: out, err: err, isTerminal: isTerminal, terminalFd: terminalFd, tlsConfig: tlsConfig, //安全傳輸層協議的配置 scheme: scheme, } }
通過調用NewDockerCli函數,程序最終完成了創建Docker Client,並返回main函數繼續執行。
2、 命令執行
Docker Client創建完畢,然後分析解析放入flag.Args()的請求參數,最後發送給Docker Server。
1) Docker Client解析請求命令
解析並處理完flag信息之後,main函數將解析存入flag.Args()中的請求參數
if err :=cli.Cmd(flag.Args()...); err != nil { if sterr, ok :=err.(*utils.StatusError); ok { if sterr.Status !="" { log.Println(sterr.Status) } os.Exit(sterr.StatusCode) } log.Fatal(err) }
解析請求參數的函數是cli對象的Cmd函數。./docker/api/client/cli.go的Cmd函數:
// Cmdexecutes the specified command func (cli*DockerCli) Cmd(args ...string) error { if len(args) > 0 { method, exists :=cli.getMethod(args[0]) if !exists { fmt.Println("Error:Command not found:", args[0]) returncli.CmdHelp(args[1:]...) } return method(args[1:]...) } return cli.CmdHelp(args...) }
首先判斷請求參數長度,然後通過args[0]獲取具體method,如果存在則調用該方法處理後面的請求參數。
2) Docker Client執行請求命令
不同的請求參數的執行方法不同,但流程大致相同,以”docker pull ubuntu”爲例講解。
func (cli*DockerCli) CmdPull(args ...string) error { cmd := cli.Subcmd("pull","NAME[:TAG]", "Pull an image or a repository from theregistry") tag := cmd.String([]string{"#t","#-tag"}, "", "Download tagged image in arepository") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() != 1 { cmd.Usage() return nil } var ( v = url.Values{} remote = cmd.Arg(0) ) v.Set("fromImage", remote) if *tag == "" { v.Set("tag", *tag) } remote, _ =parsers.ParseRepositoryTag(remote) // Resolve the Repository name from fqn tohostname + name hostname, _, err :=registry.ResolveRepositoryName(remote) if err != nil { return err } cli.LoadConfigFile() // Resolve the Auth config relevant forthis server authConfig := cli.configFile.ResolveAuthConfig(hostname) pull := func(authConfigregistry.AuthConfig) error { buf, err :=json.Marshal(authConfig) if err != nil { return err } registryAuthHeader := []string{ base64.URLEncoding.EncodeToString(buf), } return cli.stream("POST","/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ "X-Registry-Auth":registryAuthHeader, }) } if err := pull(authConfig); err != nil { if strings.Contains(err.Error(),"Status 401") { fmt.Fprintln(cli.out,"\nPlease login prior to pull:") if err :=cli.CmdLogin(hostname); err != nil { return err } authConfig :=cli.configFile.ResolveAuthConfig(hostname) return pull(authConfig) } return err } return nil }
整個Docker源代碼運行流程圖: