Dockerソース分析2:Docker Client

23468 ワード

Docker Clientは、Dockerアーキテクチャにおいて、ユーザがDocker Daemonと通信を確立するクライアントである.この文書では、Docker 18.02.0-ceのソースコードに基づいてDocker Clientの内容を分析する.主に次の2つのセクションがあります.
  • Docker Clientの作成
  • Docker Clientコマンドの実行
  • 1、Docker Clientの作成
    DockerのようなC/Sアーキテクチャの場合、クライアントの存在はDockerの対応するタスクの開始を意味する.ユーザーはまずDocker Clientを作成し、特定の要求タイプとパラメータをDocker Clientに渡し、最終的にDocker ClientからDocker Serverが認識できる形式に変換し、Docker Serverに送信します.Docker Clientのmain関数はdocker-ce/components/cli/cmd/docker/dockerにあります.go、コードは以下の通りです.
    func main() {
        // Set terminal emulation based on platform as required.
        stdin, stdout, stderr := term.StdStreams()
        logrus.SetOutput(stderr)
    
        //   Cli,    DockerCli  
        dockerCli := command.NewDockerCli(stdin, stdout, stderr)
        //   cmd,               
        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)
        }
    }

    1)NewDockerCliメソッドはdocker-ce/components/cli/cli/command/cliにある.goファイル.DockerCliタイプのオブジェクトインスタンスを返します.コードは次のとおりです.
    // DockerCli is an instance the docker command line client.
    // Instances of the client can be returned from NewDockerCli.
    type DockerCli struct {   
        configFile *configfile.ConfigFile
        in         *InStream
        out        *OutStream
        err        io.Writer
        client     client.APIClient
        serverInfo ServerInfo
        clientInfo ClientInfo  
    }
    
    // ServerInfo stores details about the supported features and platform of the server
    type ServerInfo struct {
        HasExperimental bool
        OSType          string
    }
    
    // ClientInfo stores details about the supported features of the client
    type ClientInfo struct {
        HasExperimental bool
        DefaultVersion  string   //api.defaultVersion or DOCKER_API_VERSION
        Orchestrator    Orchestrator
    }
    
    // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
    func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
        return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}   //       DockerCli       
    }

    2)newDockerCommandはmain関数と同じdocker-ce/components/cli/cmd/docker/docker.goファイルでは、DockerCliタイプのオブジェクトインスタンスに基づいてcobraを返します.Commandオブジェクトインスタンス.まず、いくつかのライブラリについて説明します.
        "github.com/sirupsen/logrus"  //    log 
        "github.com/spf13/cobra"    //     
        "github.com/spf13/pflag"    //    go   flag 

    新DockerCommandのコードは次のとおりです.
    // DockerCli   Command.Cli  
    func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {  
        opts := cliflags.NewClientOptions()   //  client options
        var flags *pflag.FlagSet
    
        cmd := &cobra.Command{  //      cobra.Command       ,    docker 
            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 {  
                if opts.Version {
                    showVersion()
                    return nil
                }
                // docker         ShowHelp,   help  
                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)
            },
        }
        //       usage, help        
        cli.SetupRootCommand(cmd)  //SetupRootCommand sets default usage, help, and error handling for the root command.  
        flags = cmd.Flags()
        flags.BoolVarP(&opts.Version, "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())
        //       ( docker run、docker build )       
        commands.AddCommands(cmd, dockerCli)  // AddCommands adds all the commands from cli/command to the root command  
    
        setValidateArgs(dockerCli, cmd, flags, opts)
    
        return cmd
    }

    NewClientOptionsは、clientのoptionsを構成するオブジェクトを返します.
    // ClientOptions are the options used to configure the client cli
    type ClientOptions struct {
        Common    *CommonOptions
        ConfigDir string
        Version   bool
    }
    
    // NewClientOptions returns a new ClientOptions
    func NewClientOptions() *ClientOptions {
        return &ClientOptions{Common: NewCommonOptions()}
    }
    
    // CommonOptions are options common to both the client and the daemon.
    type CommonOptions struct {
        Debug        bool
        Hosts        []string
        Orchestrator string
        LogLevel     string
        TLS          bool
        TLSVerify    bool
        TLSOptions   *tlsconfig.Options
    }
    
    // NewCommonOptions returns a new CommonOptions
    func NewCommonOptions() *CommonOptions {
        return &CommonOptions{}
    }

    3)cmdを実行し,エラー情報処理を行う.これでDocker Clientが作成されます.
    2、Docker Clientによる命令の実行
    第一小結ではcommands.AddCommands(cmd,dockerCli)はdockerルートコマンドにすべてのサブコマンドを追加します.AddCommandsメソッドはdocker-ce/components/cli/cli/command/commandsにあります.コードは次のとおりです.
    // 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),
            container.NewRunCommand(dockerCli),
    
            // image         
            image.NewImageCommand(dockerCli),
            image.NewBuildCommand(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
            //       DOCKER_HIDE_LEGACY_COMMANDS           , DOCKER_HIDE_LEGACY_COMMANDS=true docker --help       
            hide(system.NewEventsCommand(dockerCli))
            ...
        )
    
    }

    以下、Pullイメージを例にDocker Clientによるコマンドの実行について説明する.image.NewImageCommand(dockerCli)メソッドはdocker-ce/components/cli/cli/command/image/cmdにある.goファイルにあります.コードは次のとおりです.
    // NewImageCommand returns a cobra command for `image` subcommands
    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
    }

    NewPullCommandメソッドを詳しく見てみましょう.docker-ce/components/cli/cli/command/image/pullにあります.goファイル:
    type pullOptions struct {
        remote   string
        all      bool
        platform string
    }
    
    // NewPullCommand creates a new `docker pull` command
    func NewPullCommand(dockerCli command.Cli) *cobra.Command {
        var opts pullOptions
    
        cmd := &cobra.Command{
            Use:   "pull [OPTIONS] NAME[:TAG|@DIGEST]",
            Short: "Pull an image or a repository from a registry",
            Args:  cli.ExactArgs(1),
            RunE: func(cmd *cobra.Command, args []string) error {
                opts.remote = args[0]  //   docker pull      , image  
                return runPull(dockerCli, opts)  //  pull     
            },
        }
    
        flags := cmd.Flags()
    
        flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")
    
        command.AddPlatformFlag(flags, &opts.platform)
        command.AddTrustVerificationFlags(flags)
    
        return cmd
    }
    
    //  pull       
    func runPull(cli command.Cli, opts pullOptions) error {
        //           
        distributionRef, err := reference.ParseNormalizedNamed(opts.remote) 
        switch {
        case err != nil:
            return err
        case opts.all && !reference.IsNameOnly(distributionRef):
            return errors.New("tag can't be used with --all-tags/-a")
        case !opts.all && reference.IsNameOnly(distributionRef):
            //       ,  tag,   latest   tag
            distributionRef = reference.TagNameOnly(distributionRef)   
            if tagged, ok := distributionRef.(reference.Tagged); ok {
                fmt.Fprintf(cli.Out(), "Using default tag: %s
    "
    , tagged.Tag()) } } ctx := context.Background() imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), distributionRef.String()) // retrieves the necessary reference and auth information for an image name as an ImageRefAndAuth struct if err != nil { return err } // Check if reference has a digest _, isCanonical := distributionRef.(reference.Canonical) if command.IsTrusted() && !isCanonical { err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform) // } else { // err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform) } if err != nil { if strings.Contains(err.Error(), "when fetching 'plugin'") { return errors.New(err.Error() + " - Use `docker plugin install`") } return err } return nil }

    ミラー・プルの具体的なコードは次のとおりです.
    // imagePullPrivileged pulls the image and displays it to the output
    func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool, platform string) error {
        ref := reference.FamiliarString(imgRefAndAuth.Reference())
    
        encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig())
        if err != nil {
            return err
        }
        requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
        options := types.ImagePullOptions{
            RegistryAuth:  encodedAuth,
            PrivilegeFunc: requestPrivilege,
            All:           all,
            Platform:      platform,
        }
        //   Client ImagePull    Docker server    
        responseBody, err := cli.Client().ImagePull(ctx, ref, options)
        if err != nil {
            return err
        }
        defer responseBody.Close()
    
        return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
    }

    ImagePullメソッドはdocker-ce/components/engine/client/image_にあります.pull.goファイルでは、次のようになります.
    func (cli *Client) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) {
        ref, err := reference.ParseNormalizedNamed(refStr)
        if err != nil {
            return nil, err
        }
    
        query := url.Values{}
        query.Set("fromImage", reference.FamiliarName(ref))
        if !options.All {
            query.Set("tag", getAPITagFromNamedRef(ref))
        }
        if options.Platform != "" {
            query.Set("platform", strings.ToLower(options.Platform))
        }
    
        resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
        if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
            newAuthHeader, privilegeErr := options.PrivilegeFunc()
            if privilegeErr != nil {
                return nil, privilegeErr
            }
            resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
        }
        if err != nil {
            return nil, err
        }
        return resp.body, nil
    }

    tryImageCreateメソッドはdocker-ce/components/engine/client/image_にあります.create.goファイルでは、次のようになります.
    // ImageCreate creates a new image based in the parent options.
    // It returns the JSON content in the response body.
    func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
        ref, err := reference.ParseNormalizedNamed(parentReference)
        if err != nil {
            return nil, err
        }
    
        query := url.Values{}
        query.Set("fromImage", reference.FamiliarName(ref))
        query.Set("tag", getAPITagFromNamedRef(ref))
        if options.Platform != "" {
            query.Set("platform", strings.ToLower(options.Platform))
        }
        resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
        if err != nil {
            return nil, err
        }
        return resp.body, nil
    }
    
    func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
        //  Docker Server  POST  ,   url /images/create,        header  X-Registry-Auth  
        headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
        return cli.post(ctx, "/images/create", query, nil, headers)  
    }

    以上がpullリクエストのすべてのプロセスであり,他のリクエストは実行プロセスにおいても大きく異なる.要するに、要求実行中は、コマンドラインの要求に関するパラメータを初期処理し、対応する補助情報を追加し、最終的に指定されたプロトコルを介してDockerサーバに対応するAPI要求を送信することが多い.これらのAPIリクエストは、Docker ClientとDocker Serverが事前に約束したものです.要求の具体的な応答、すなわち本当の意味でのミラーダウンロードを実現するには、Dockerソース分析3:Dockerサーバで詳細に説明します.