Docker源码分析
上QQ阅读APP看书,第一时间看更新

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。