1. 程式人生 > >docker run命令解析(一)之Docker client

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