2.2.2 处理flag信息并收集Docker Client的配置信息
理解Go语言解析flag参数的相关知识,可以很大程度上帮助理解Docker的main函数的执行流程。通过总结,首先列出源码中处理的flag信息以及收集Docker Client的配置信息,然后再一一进行分析:
□ 处理的flag参数有:flVersion、flDebug、flDaemon、flTlsVerify以及flTls。
□ 为Docker Client收集的配置信息有:protoAddrParts(通过flHosts参数获得,作用是提供Docker Client与Docker Server的通信协议以及通信地址)、tlsConfig(通过一系列flag参数获得,如*flTls、*flTlsVerify,作用是提供安全传输层协议的保障)。
清楚flag参数以及Docker Client的配置信息之后,我们进入main函数的源码,具体分析如下。
在flag.Parse()之后的源码如下:
if *flVersion { showVersion() return }
以上代码很好理解,解析flag参数后,若Docker发现flag参数flVersion为真,则说明Docker用户希望查看Docker的版本信息。此时,Docker调用showVersion()显示版本信息,并从main函数退出;否则的话,继续往下执行。
if *flDebug { os.Setenv("DEBUG", "1") }
若flDebug参数为真的话,Docker通过os包中的Setenv函数创建一个名为DEBUG的环境变量,并将其值设为"1";继续往下执行。
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) }
以上的源码主要分析内部变量flHosts。flHosts的作用是为Docker Client提供所要连接的host对象,也就是为Docker Server提供所要监听的对象。
在分析过程中,首先判断flHosts变量是否长度为0。若是的话,则说明用户并没有显性传入地址,此时Docker的策略为选用默认值。Docker通过os包获取名为DOCKER_HOST环境变量的值,将其赋值于defaultHost。若defaultHost为空或者flDaemon为真,说明目前还没有一个定义的host对象,则将其默认设置为unix socket,值为api.DEFAULTUNIXSOCKET,该常量位于./docker/api/common.go,值为"/var/run/docker.sock",故defaultHost为"unix:///var/run/docker.sock"。验证该defaultHost的合法性之后,将defaultHost的值追加至flHost的末尾,继续往下执行。当然若flHost的长度不为0,则说明用户已经指定地址,同样继续往下执行。
if *flDaemon { mainDaemon() return }
若flDaemon参数为真,则说明用户的需求是启动Docker Daemon。Docker随即执行mainDaemon函数,实现Docker Daemon的启动;若mainDaemon函数执行完毕,则退出main函数。一般mainDaemon函数不会主动终结,Docker Daemon将作为一个常驻进程运行在宿主机上。本章着重介绍Docker Client的启动,故假设flDaemon参数为假,不执行以上代码块。继续往下执行。
if len(flHosts) > 1 { log.Fatal("Please specify only one -H") } protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
由于不执行Docker Daemon的启动流程,故属于Docker Client的执行逻辑。首先,判断flHosts的长度是否大于1。若flHosts的长度大于1,则说明需要新创建的Docker Client访问不止1个Docker Daemon地址,显然逻辑上行不通,故抛出错误日志,提醒用户只能指定一个Docker Daemon地址。接着,Docker将flHosts这个string数组中的第一个元素进行分割,通过"://"来分割,分割出的两个部分放入变量protoAddrParts数组中。protoAddrParts的作用是:解析出Docker Client与Docker Server建立通信的协议与地址,为Docker Client创建过程中不可或缺的配置信息之一。一般情况下,flHosts[0]的值可以是tcp://0.0.0.0:2375或者unix:///var/run/docker.sock等。
var ( cli *client.DockerCli tlsConfig tls.Config ) tlsConfig.InsecureSkipVerify = true
由于之前已经假设过flDaemon为假,可以认定main函数的运行是为了Docker Client的创建与执行。Docker在这里创建了两个变量:一个为类型是*client.DockerCli的对象cli,另一个为类型是tls.Config的对象tlsConfig。定义完变量之后,Docker将tlsConfig的InsecureSkipVerify属性置为真。tlsConfig对象的创建是为了保障cli在传输数据的时候遵循安全传输层协议(TLS)。安全传输层协议(TLS)用于确保两个通信应用程序之间的保密性与数据完整性。tlsConfig是Docker Client创建过程中可选的配置信息。
// If we should verify the server, we need to load a trusted ca if *flTlsVerify { *flTls = true certPool := x509.NewCertPool() file, err := ioutil.ReadFile(*flCa) if err != nil { log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) } certPool.AppendCertsFromPEM(file) tlsConfig.RootCAs = certPool tlsConfig.InsecureSkipVerify = false }
若flTlsVerify这个flag参数为真,则说明Docker Client需Docker Server一起验证连接的安全性。此时,tlsConfig对象需要加载一个受信的ca文件。该ca文件的路径为*flCA参数的值,最终完成tlsConfig对象中RootCAs属性的赋值,并将InsecureSkipVerify属性置为假。
// If tls is enabled, try to load and send client 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't load X509 key pair: %s. Key encrypted?", err) } tlsConfig.Certificates = []tls.Certificate{cert} } }
如果flTls和flTlsVerify两个flag参数中有一个为真,则说明需要加载并发送客户端的证书。最终将证书内容交给tlsConfig的Certificates属性。
至此,flag参数已经全部处理完毕,DockerClient也已经收集到所需的配置信息。下一节将主要分析如何创建Docker Client。