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

2.2.1 Docker命令的flag参数解析

众所周知,在Docker的具体实现中,Docker Server与Docker Client均由可执行文件docker来完成创建并启动。那么,了解docker可执行文件通过何种方式来区分到底是Docker Server还是Docker Client,就显得尤为重要。

首先通过docker命令举例说明其中的区别。Docker Server的启动,命令为docker-d或docker--daemon=true;而Docker Client的启动则体现为docker--daemon=false ps、docker pull NAME等。

其实,对于Docker请求中的参数,我们可以将其分为两类:第一类为命令行参数,即docker程序运行时所需提供的参数,如:-D、--daemon=true、--daemon=false等;第二类为docker发送给Docker Server的实际请求参数,如:ps、pull NAME等。

对于第一类,我们习惯将其称为flag参数,在Go语言的标准库中,专门为该类参数提供了一个flag包,方便进行命令行参数的解析。

清楚docker二进制文件的使用以及基本的命令行flag参数之后,我们可以进入实现Docker Client创建的源码中,位于./docker/docker/docker.go。这个go文件包含了整个Docker的main函数,也就是整个Docker(不论Docker Daemon还是Docker Client)的运行入口。部分main函数代码如下:

func main() {
     if reexec.Init() {
          return
     }
     flag.Parse()
     // FIXME: validate daemon flags here
     ...
}

以上源码实现中,首先判断reexec.Init()方法的返回值,若为真,则直接退出运行,否则将继续执行。reexec.Init()函数的定义位于./docker/reexec/reexec.go,可以发现由于在docker运行之前没有任何Initializer注册,故该代码段执行的返回值为假。reexec存在的作用是:协调execdriver与容器创建时dockerinit这两者的关系。第13章在分析dockerinit的启动时,将详细讲解reexec的作用。

判断reexec.Init()之后,Docker的main函数通过调用flag.Parse()函数,解析命令行中的flag参数。如果熟悉Go语言中的flag参数,一定知道解析flag参数的值之前,程序必须先定义相应的flag参数。进一步查看Docker的源码,我们可以发现Docker在./docker/docker/flag.go中定义了多个flag参数,并通过init函数进行部分flag参数的初始化。代码如下:

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 daemon modeuse '' (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 by tls-verify flags")
         flTlsVerify = flag.Bool([]string{"-tlsverify"}, false, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)")

         // these are initialized in init() below since their default values depend on dockerCertPath which isn't fully initialized 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 the CA 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 key file")
         opts.HostListVar(&flHosts, []string{"H", "-host"}, "The socket(s) to bind to in daemon mode\nspecified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.")
}

以上源码展示了Docker如何定义flag参数,以及在init函数中实现部分flag参数的初始化。Docker的main函数执行前,这些变量创建以及初始化工作已经全部完成。这里涉及了Go语言的一个特性,即init函数的执行。Go语言中引入其他包(import package)、变量的定义、init函数以及main函数这四者的执行顺序如图2-2所示。

关于Golang中的init函数,深入分析可以得出以下特性:

□ init函数用于程序执行前包的初始化工作,比如初始化变量等;

□ 每个包可以有多个init函数;

□ 包的每一个源文件也可以有多个init函数;

□ 同一个包内的init函数的执行顺序没有明确的定义;

□ 不同包的init函数按照包导入的依赖关系决定初始化的顺序;

□ init函数不能被调用,而是在main函数调用前自动被调用。

图2-2 Go语言程序加载顺序图

清楚Go语言一些基本的特性之后,回到Docker中来。Docker的main函数执行之前,Docker已经定义了诸多flag参数,并对很多flag参数进行初始化。定义并初始化的命令行flag参数有:flVersion、flDaemon、flDebug、flSocketGroup、flEnableCors、flTls、flTlsVerify、flCa、flCert、flKey、flHosts等。

以下具体分析flDaemon:

□ 定义:flDaemon=flag.Bool([]string{"d","-daemon"},false,"Enable daemon mode");

□ flDaemon的类型为Bool类型;

□ flDaemon名称为"d"或者"-daemon",该名称会出现在docker命令中,如docker–d;

□ flDaemon的默认值为false;

□ flDaemon的用途信息为"Enable daemon mode";

□ 访问flDaemon的值时,使用指针*flDaemon解引用访问。

在解析命令行flag参数时,以下语句为合法的(以flDaemon为例):

□ -d,--daemon

□ -d=true,--daemon=true

□ -d="true",--daemon="true"

□ -d='true',--daemon='true'

当解析到第一个非定义的flag参数时,命令行flag参数解析工作结束。举例说明,当执行docker命令docker--daemon=false--version=false ps时,flag参数解析主要完成两个工作:

□ 完成命令行flag参数的解析,根据flag的名称-daemon和-version,得知具体的flag参数为flDaemon和flVersion,并获得相应的值,均为false。

□ 遇到第一个非定义的flag参数ps时,flag包会将ps及其之后所有的参数存入flag.Args(),以便之后执行Docker Client具体的请求时使用。

如需深入学习flag的实现,可以参见Docker源码./docker/pkg/mflag/flag.go。