//后续补上Docker的架构分析
Docker Client创建及命令执行
Docker架构:
从整体的架构图中,可以看出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源代码运行流程图: