docker run源码分析(一)之Docker client

1、什么是Docker?

Docker Linux 平台上的一款轻量级虚拟化容器的管理引擎。在全球范围内, Docker 还是一个开源项目,整个项目基于 Go 语言开发,代码托管于 GitHub 网站上,并遵从 Apache 2.0 协议。目前, Docker 可以帮助用户在容器内部快速自动化部署应用,并利用 Linux 内核特性命名空间( namespaces) 及控制组( cgroups) 等为容器提供隔离的运行环境。Docker 借助操作系统层的虚拟化实现资源的隔离,因此 Docker 容器在运行时与虚拟机 (VM) 的运行有很大的区别, Docker 容器与宿主机共享同一个操作系统,不会有额外的操作系统开销。

2、Docker总体架构图

查看架构图详解,请参考:https://www.infoq.cn/article/docker-source-code-analysis-part1

3、背景:

了解Docker源码解析Dockerfile的流程,了解docker build以及docker run 在client、daemon和engine端的处理。

4、 源码基于Docker-ce17.09.

5、源码:

Docker Client 是Docker 架构中用户与 Docker Daemon 建立通信的客户端。从main()函数开始,位置点击

5.1、流程:(截图于博友,自己作图太麻烦,跟17.09有点差别,自己对比)

5.2、源码分析:

docker client的入口函数main,在main中处理传入的参数,并把参数转化为cobra的command类型,最后通过cobra调用相应的方法。

5.2.1、func AddCommands()函数

// AddCommands adds all the commands from cli/command to the root command
func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
	cmd.AddCommand(
		// checkpoint
		checkpoint.NewCheckpointCommand(dockerCli),

		// config
		config.NewConfigCommand(dockerCli),

		// container
		container.NewContainerCommand(dockerCli),
               //添加子命令run及其选项
		container.NewRunCommand(dockerCli),

		// image
		image.NewImageCommand(dockerCli),
		image.NewBuildCommand(dockerCli),

		// registry
		registry.NewLoginCommand(dockerCli),
		registry.NewLogoutCommand(dockerCli),
		registry.NewSearchCommand(dockerCli),

		// secret
		secret.NewSecretCommand(dockerCli),

		// service
		service.NewServiceCommand(dockerCli),

		// system
		system.NewSystemCommand(dockerCli),
		system.NewVersionCommand(dockerCli),

               ……………………
	)

}

5.2.2、func NewRunCommand()函数

// NewRunCommand create a new `docker run` command
func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
	var opts runOptions
	var copts *containerOptions

	cmd := &cobra.Command{
		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
		Short: "Run a command in a new container",
		Args:  cli.RequiresMinArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			copts.Image = args[0]
			if len(args) > 1 {
				copts.Args = args[1:]
			}
                        //run对应的客户端方法
			return runRun(dockerCli, cmd.Flags(), &opts, copts)
		},
	}

	flags := cmd.Flags()
	flags.SetInterspersed(false)

	// These are flags not stored in Config/HostConfig
	flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")

	// Add an explicit help that doesn't have a `-h` to prevent the conflict
	// with hostname
	flags.Bool("help", false, "Print usage")

	command.AddTrustVerificationFlags(flags)
	copts = addFlags(flags)
	return cmd
}

5.2.3、func runRun()函数

func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
        //得到代理配置	// ConfigFile ~/.docker/config.json file info
        proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), copts.env.GetAll())
	newEnv := []string{}
	for k, v := range proxyConfig {
		if v == nil {
			newEnv = append(newEnv, k)
		} else {
			newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
		}
	}
	copts.env = *opts.NewListOptsRef(&newEnv, nil)
	containerConfig, err := parse(flags, copts)
	// just in case the parse does not exit
	if err != nil {
		reportError(dockerCli.Err(), "run", err.Error(), true)
		return cli.StatusError{StatusCode: 125}
	}
         //调用runContainer
	return runContainer(dockerCli, ropts, copts, containerConfig)
}

5.2.4、func runContainer()函数

func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
	config := containerConfig.Config
	hostConfig := containerConfig.HostConfig
	stdout, stderr := dockerCli.Out(), dockerCli.Err()
	//实例化一个客户端,用于发送run命令请求
	client := dockerCli.Client()

	// TODO: pass this as an argument
	cmdPath := "run"

	warnOnOomKillDisable(*hostConfig, stderr)
	warnOnLocalhostDNS(*hostConfig, stderr)

	config.ArgsEscaped = false

	if !opts.detach {
		if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
			return err
		}
	} else {

         ……省略
	ctx, cancelFun := context.WithCancel(context.Background())
	//client向engine发送create容器请求
	createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name)
         ……省略
	//start the container
	client向engine发送start容器请求
	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
		
}

5.2.5、func createContainer()函数

func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string) (*container.ContainerCreateCreatedBody, error) {
	config := containerConfig.Config
	hostConfig := containerConfig.HostConfig
	networkingConfig := containerConfig.NetworkingConfig
	stderr := dockerCli.Err()

	var (
		trustedRef reference.Canonical
		namedRef   reference.Named
	)

	containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
	if err != nil {
		return nil, err
	}
	defer containerIDFile.Close()

	ref, err := reference.ParseAnyReference(config.Image)
	if err != nil {
		return nil, err
	}
	if named, ok := ref.(reference.Named); ok {
		namedRef = reference.TagNameOnly(named)

		if taggedRef, ok := namedRef.(reference.NamedTagged); ok && command.IsTrusted() {
			var err error
			trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil)
			if err != nil {
				return nil, err
			}
			config.Image = reference.FamiliarString(trustedRef)
		}
	}

	//create the container
	response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)

	//if image not found try to pull it
	if err != nil {
		if apiclient.IsErrImageNotFound(err) && namedRef != nil {
			fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))

			// we don't want to write to stdout anything apart from container.ID
			if err := pullImage(ctx, dockerCli, config.Image, stderr); err != nil {
				return nil, err
			}
			if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
				if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil {
					return nil, err
				}
			}
			// Retry
			var retryErr error
			response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
			if retryErr != nil {
				return nil, retryErr
			}
		} else {
			return nil, err
		}
	}

	for _, warning := range response.Warnings {
		fmt.Fprintf(stderr, "WARNING: %s\n", warning)
	}
	err = containerIDFile.Write(response.ID)
	return &response, err
}

5.2.6、ContainerCreate()


// ContainerAPIClient defines API client methods for the containers
type ContainerAPIClient interface {
	//API create方法
        ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
	
        //API start方法
	ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
	
}
//ContainerCreate基于给定配置创建新容器。 它可以与名称相关联,但不是强制性的。
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
	var response container.ContainerCreateCreatedBody

	if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
		return response, err
	}

	// When using API 1.24 and under, the client is responsible for removing the container
	if hostConfig != nil && versions.LessThan(cli.ClientVersion(), "1.25") {
		hostConfig.AutoRemove = false
	}

	query := url.Values{}
	if containerName != "" {
		query.Set("name", containerName)
	}

	body := configWrapper{
		Config:           config,
		HostConfig:       hostConfig,
		NetworkingConfig: networkingConfig,
	}
	//发送post请求
	serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
	if err != nil {
		if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {
			return response, imageNotFoundError{config.Image}
		}
		return response, err
	}

	err = json.NewDecoder(serverResp.body).Decode(&response)
	ensureReaderClosed(serverResp)
	return response, err
}

5.3、总结

在第5.2.4节上,startContainer()函数就不介绍了,跟createContainer类似,后面有需要,咱再分析。

下一篇章就分析docker run命令在daemon中的处理流程了。

参考:

           https://www.infoq.cn/article/docker-source-code-analysis-part1

https://guanjunjian.github.io/2017/09/26/study-1-docker-1-client-excuting-flow-for-run/

https://blog.csdn.net/warrior_0319/article/details/79931987

《docker源码分析》

百度网盘:链接: https://pan.baidu.com/s/1m5AqgfZKvPRu2BcmWz-WxA 提取码: wjrj 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值