Docker源码学习笔记二、Docker Client

Docker源码学习笔记二、Docker Client

  作为一种轻量级虚拟化容器管理引擎,docker给开发者提供了一种新颖、便捷的软件集成测试和部署的方式。Docker采用C/S架构进行设计,用户与Docker交互时,通过Docker Client发送一系列命令,因此使用Docker时最先感受到的存在就是Docker Client。

1. 创建Docker Client

  对于C/S架构的应用,Client一般扮演着命令发送者的角色,Docker Client也是一样。用户需要先创建一个Docker Client,随后将特定的请求类型与参数传递至Docker Client,最终由Docker Client转义成Docker Server能识别的形式,并发送至Docker Server。另外,DockerClient的创建需要依据docker二进制文件。

1.1. Docker命令的flag参数解析

  Docker Server和Dcker Client都是由可执行文件docker来创建并启动,docker -d命令是启动Docker Server,docker psdocker pull等命令是启动Docker Client。

  Docker请求中的参数可以归为两类:第一类为命令行参数,如docker -d;第二类为docker发送给Docker Server的实际请求参数,如docker psdocker pull。对于第一类参数,通常称为flag参数,利用Golang提供的专门的flag包进行解析。

  源码中./docker/docker/docker.go,包含了整个Docker的main函数,即整个Docker(不论是Docker Client还是Docker Daemon)的运行入口。事实上,fdocker定义的flag参数并不多(11个,4个在main函数启动前自动初始化),下面列出5个常用的。

var (
	...
	
	// 定义多个flag参数,在main函数执行之前,这些参数未被初始化
	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 mode\nuse '' (the empty string) to disable setting of a group")
	flTls         = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by tls-verify flags")
	...
)

  假设我现在要执行docker -d -v ps命令,flag参数解析会完成两个工作:首先完成命令行flag参数的解析,拿到flDaemon和flVersion;其次,遇到第一个非定义的flag参数ps时,flag包会将ps及其之后所有参数放入flag.Args(),为之后执行Docker Client具体请求使用。

1.2. 处理flag信息并收集Docker Client的配置信息

1.3. 创建DockerClient

  Docker Client的创建其实就是在已有配置参数信息的情况下,通过Client包中的NewDockerCli方法创建一个Docker Client示例cli。

	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)
	}

2. Docker命令执行

  创建完Docker Client,现在拿到了一个DockerCli实例以及docker中请求参数(flag.Args())。接下来程序需要使用Docker Client解析出Docker命令中的请求参数,转义为Docker Server可以识别的请求。

2.1. Docker Client解析请求

  Docker Client首先解析存放于flag.Args()中的具体请求参数。

	// 解析存放于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(flag.Args()...)函数执行具体的指令。如docker -d -v pull nginx命令执行步骤如下:

  1. 解析flag参数,Docker将请求参数pullnginx放在flag.Args()
  2. 创建好的Docker Client为cli,cli执行cli.Cmd(flag.Args()…)。
  3. Cmd函数中,通过args[0]即pull,执行cli.getMethod(args[0]),获取method的名称。
  4. 在getMethod()方法中,Docker通过处理最终返回的method值为CmdPull
  5. 最终执行method(args[1:]...)也就是CmdPull(args[1:]...)

2.2. Docker Client执行请求命令

  上一小节中docker pull的例子通过反射将命令的最终执行任务放到CmdPull方法中。其中的pull函数实现实际意义上的下载请求发送。总之,请求执行过程中,大多是命令行中关于请求的参数进行初步处理,最终根据约定好的API,通过指定协议向Docker Server发送请求。

func (cli *DockerCli) CmdPull(args ...string) error {
	cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
	tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in a repository")
	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 to hostname + name
	hostname, _, err := registry.ResolveRepositoryName(remote)
	if err != nil {
		return err
	}
	
	// 通过cli 对象获取与Docker Server 通信所需的认证配置信息
	cli.LoadConfigFile()

	// Resolve the Auth config relevant for this server
	authConfig := cli.configFile.ResolveAuthConfig(hostname)
	
	// 定义一个名为pull的函数,,传人的参数类型为registry.AuthConfig ,返回类型为error。
	pull := func(authConfig registry.AuthConfig) error {
		buf, err := json.Marshal(authConfig)
		if err != nil {
			return err
		}
		registryAuthHeader := []string{
			base64.URLEncoding.EncodeToString(buf),
		}

		return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
			"X-Registry-Auth": registryAuthHeader,
		})
	}
	
	// 这一步骤具体调用执行pull 函数,实现实际意义上的下载请求发送
	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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值