//后续补上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 p_w_picpath or a repository from theregistry") tag := cmd.String([]string{"#t","#-tag"}, "", "Download tagged p_w_picpath 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","/p_w_picpaths/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源代码运行流程图: