Docker Client源码分析

由于Docker代码更新很快,Docker项目更名为moby,然后很多组件又从moby中拆分了出来。其中docker client的源码如下:
https://github.com/docker/cli 目前最新的版本是18.09,可直接下载或通过gitclone下载。将项目放在如下目录

$GOPATH/src/github.com/docker/

开始

  • 入口文件在cmd/docker/docker.go:
func main() {
	// Set terminal emulation based on platform as required.
	stdin, stdout, stderr := term.StdStreams()
	logrus.SetOutput(stderr) //日志输出采用了第三方库logrus

	dockerCli := command.NewDockerCli(stdin, stdout, stderr, contentTrustEnabled(), containerizedengine.NewClient)
	
	//root命令,子命令都在这里面
	cmd := newDockerCommand(dockerCli)

    //执行上面的cmd命令
	if err := cmd.Execute(); err != nil {
		if sterr, ok := err.(cli.StatusError); ok {
			if sterr.Status != "" {
				fmt.Fprintln(stderr, sterr.Status)
			}
			// StatusError should only be used for errors, and all errors should
			// have a non-zero exit status, so never exit with 0
			if sterr.StatusCode == 0 {
				os.Exit(1)
			}
			os.Exit(sterr.StatusCode)
		}
		fmt.Fprintln(stderr, err)
		os.Exit(1)
	}
}

跳到上面的newDockerCommand(dockerCli)函数中:

func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
	opts := cliflags.NewClientOptions()
	var flags *pflag.FlagSet

	cmd := &cobra.Command{
		Use:              "docker [OPTIONS] COMMAND [ARG...]",
		Short:            "A self-sufficient runtime for containers",
		SilenceUsage:     true,
		SilenceErrors:    true,
		TraverseChildren: true,
		Args:             noArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return command.ShowHelp(dockerCli.Err())(cmd, args)
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			// flags must be the top-level command flags, not cmd.Flags()
			opts.Common.SetDefaultOptions(flags)
			dockerPreRun(opts)
			if err := dockerCli.Initialize(opts); err != nil {
				return err
			}
			return isSupported(cmd, dockerCli)
		},
		Version:               fmt.Sprintf("%s, build %s", cli.Version, cli.GitCommit),
		DisableFlagsInUseLine: true,
	}
	//将cmd里的设置为root命令
	cli.SetupRootCommand(cmd)

	flags = cmd.Flags()
	flags.BoolP("version", "v", false, "Print version information and quit")
	flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
	opts.Common.InstallFlags(flags)

	setFlagErrorFunc(dockerCli, cmd, flags, opts)

	setHelpFunc(dockerCli, cmd, flags, opts)

	cmd.SetOutput(dockerCli.Out())
	//添加子命令,cmd里是root命令
	commands.AddCommands(cmd, dockerCli)

	disableFlagsInUseLine(cmd)
	setValidateArgs(dockerCli, cmd, flags, opts)

	return cmd
}

它主要通过调用commands.AddCommands(cmd, dockerCli)这个函数来添加命令。现在跳过去看一下该函数:
发现它是在 cli/command/commands/commands.go文件中:

func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
   cmd.AddCommand(
   	// checkpoint
   	checkpoint.NewCheckpointCommand(dockerCli),

   	// config
   	config.NewConfigCommand(dockerCli),

   	// container
   	container.NewContainerCommand(dockerCli),
   	container.NewRunCommand(dockerCli),

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

   	// builder
   	builder.NewBuilderCommand(dockerCli),

   	// manifest
   	manifest.NewManifestCommand(dockerCli),

   	// network
   	network.NewNetworkCommand(dockerCli),

   	// node
   	node.NewNodeCommand(dockerCli),

   	// plugin
   	plugin.NewPluginCommand(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),

   	// stack
   	stack.NewStackCommand(dockerCli),
   	stack.NewTopLevelDeployCommand(dockerCli),

   	// swarm
   	swarm.NewSwarmCommand(dockerCli),

   	// trust
   	trust.NewTrustCommand(dockerCli),

   	// volume
   	volume.NewVolumeCommand(dockerCli),

   	// legacy commands may be hidden
   	hide(system.NewEventsCommand(dockerCli)),
   	hide(system.NewInfoCommand(dockerCli)),
   	hide(system.NewInspectCommand(dockerCli)),
   	hide(container.NewAttachCommand(dockerCli)),
   	hide(container.NewCommitCommand(dockerCli)),
   	hide(container.NewCopyCommand(dockerCli)),
   	hide(container.NewCreateCommand(dockerCli)),
   	hide(container.NewDiffCommand(dockerCli)),
   	hide(container.NewExecCommand(dockerCli)),
   	hide(container.NewExportCommand(dockerCli)),
   	hide(container.NewKillCommand(dockerCli)),
   	hide(container.NewLogsCommand(dockerCli)),
   	hide(container.NewPauseCommand(dockerCli)),
   	hide(container.NewPortCommand(dockerCli)),
   	hide(container.NewPsCommand(dockerCli)),
   	hide(container.NewRenameCommand(dockerCli)),
   	hide(container.NewRestartCommand(dockerCli)),
   	hide(container.NewRmCommand(dockerCli)),
   	hide(container.NewStartCommand(dockerCli)),
   	hide(container.NewStatsCommand(dockerCli)),
   	hide(container.NewStopCommand(dockerCli)),
   	hide(container.NewTopCommand(dockerCli)),
   	hide(container.NewUnpauseCommand(dockerCli)),
   	hide(container.NewUpdateCommand(dockerCli)),
   	hide(container.NewWaitCommand(dockerCli)),
   	hide(image.NewHistoryCommand(dockerCli)),
   	hide(image.NewImagesCommand(dockerCli)),
   	hide(image.NewImportCommand(dockerCli)),
   	hide(image.NewLoadCommand(dockerCli)),
   	hide(image.NewPullCommand(dockerCli)),
   	hide(image.NewPushCommand(dockerCli)),
   	hide(image.NewRemoveCommand(dockerCli)),
   	hide(image.NewSaveCommand(dockerCli)),
   	hide(image.NewTagCommand(dockerCli)),
   )
   if runtime.GOOS == "linux" {
   	// engine
   	cmd.AddCommand(engine.NewEngineCommand(dockerCli))
   }
}

上面有各种命令的添加,也就是我们所添加的二级命令,即紧跟docker后面的命令。终端输入docker,就会显示上面的二级命令,然后下面hide包裹的是一些旧命令,在新版本中可以使用新的命令来代替。可以通过设置环境变量 DOCKER_HIDE_LEGACY_COMMANDS不为空,来使输入docker后将不会提示这些命令。

下面介绍一下docker image这个,就是上面文件中的如下方法:

	image.NewImageCommand(dockerCli)

跳过去后,实现代码在cli/command/image/cmd.go

    func NewImageCommand(dockerCli command.Cli) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "image",
		Short: "Manage images",
		Args:  cli.NoArgs,
		RunE:  command.ShowHelp(dockerCli.Err()),
	}
	cmd.AddCommand(
		NewBuildCommand(dockerCli),
		NewHistoryCommand(dockerCli),
		NewImportCommand(dockerCli),
		NewLoadCommand(dockerCli),
		NewPullCommand(dockerCli),
		NewPushCommand(dockerCli),
		NewSaveCommand(dockerCli),
		NewTagCommand(dockerCli),
		newListCommand(dockerCli),
		newRemoveCommand(dockerCli),
		newInspectCommand(dockerCli),
		NewPruneCommand(dockerCli),
	)
	return cmd
}    

可以它也有Add.Command(),可以知道它是要添加的三级命令,及docker image 之后要添加的。通过输入docker image也可以看到很上述命令相同的提示。
这里我选这个

newListCommand(dockerCli) 

注:这里它有三个是小写n开头,其他的大写N开头,这个没懂

继续跳进newListCommand方法, 实现代码在cli/command/image/list.go文件里:

// NewImagesCommand creates a new `docker images` command
func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
	options := imagesOptions{filter: opts.NewFilterOpt()}

	cmd := &cobra.Command{
		Use:   "images [OPTIONS] [REPOSITORY[:TAG]]",
		Short: "List images",
		Args:  cli.RequiresMaxArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) > 0 {
				options.matchName = args[0]
			}
			return runImages(dockerCli, options)
		},
	}

	flags := cmd.Flags()

	flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs")
	flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)")
	flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
	flags.BoolVar(&options.showDigests, "digests", false, "Show digests")
	flags.StringVar(&options.format, "format", "", "Pretty-print images using a Go template")
	flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")

	return cmd
}

func newListCommand(dockerCli command.Cli) *cobra.Command {
	cmd := *NewImagesCommand(dockerCli)
	cmd.Aliases = []string{"images", "list"}
	cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
	return &cmd
}

可以看出NewImagesCommand对应的是docker image,newListCommand对应的是docker image ls (该命令也可以直接用docker images表示)
然后其中的那段flags可以当做是第四级选项参数,该选项参数被放在了imagesOptions这个结构体中,最终传给runImages函数。参数提示可通过docker image ls -h查看。具体作用如下:

  • -q:只显示镜像ID
  • -a:列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层)
  • –no-trunc:显示完整的镜像信息
  • –digests:显示镜像的摘要信息
  • –format:指定返回值的模板文件
  • -f:显示满足条件的镜像

具体runImages函数如下:

    func runImages(dockerCli command.Cli, options imagesOptions) error {
	ctx := context.Background()

	filters := options.filter.Value()
	if options.matchName != "" {
		filters.Add("reference", options.matchName)
	}

	listOptions := types.ImageListOptions{
		All:     options.all,
		Filters: filters,
	}

	images, err := dockerCli.Client().ImageList(ctx, listOptions)
	if err != nil {
		return err
	}

	format := options.format
	if len(format) == 0 {
		if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !options.quiet {
			format = dockerCli.ConfigFile().ImagesFormat
		} else {
			format = formatter.TableFormatKey
		}
	}

	imageCtx := formatter.ImageContext{
		Context: formatter.Context{
			Output: dockerCli.Out(),
			Format: formatter.NewImageFormat(format, options.quiet, options.showDigests),
			Trunc:  !options.noTrunc,
		},
		Digest: options.showDigests,
	}
	return formatter.ImageWrite(imageCtx, images)
}

前面几行是对选项进行了处理,重点是
images, err := dockerCli.Client().ImageList(ctx, listOptions)这个函数。跳过去发现它是一个interface接口,实现实在vendor/github.com/docker/docker/client/image_list.go

func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
	var images []types.ImageSummary
	query := url.Values{}

	optionFilters := options.Filters
	referenceFilters := optionFilters.Get("reference")
	if versions.LessThan(cli.version, "1.25") && len(referenceFilters) > 0 {
		query.Set("filter", referenceFilters[0])
		for _, filterValue := range referenceFilters {
			optionFilters.Del("reference", filterValue)
		}
	}
	if optionFilters.Len() > 0 {
		filterJSON, err := filters.ToParamWithVersion(cli.version, optionFilters)
		if err != nil {
			return images, err
		}
		query.Set("filters", filterJSON)
	}
	if options.All {
		query.Set("all", "1")
	}

	serverResp, err := cli.get(ctx, "/images/json", query, nil)
	if err != nil {
		return images, err
	}

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

前面是在拼请求的参数,通过serverResp, err := cli.get(ctx, “/images/json”, query, nil)发起请求,返回image的列表。我们也可以通过curl来获取原始数据

curl -XGET --unix-socket /var/run/docker.sock http://localhost/images/json

至此docker image的大致流程就这样了,其他命令也类似。

你可能感兴趣的:(容器)