docker源码学习-main

众所周知,docker client和docker server共用一个可执行文件,通过命令行参数来区分是client还是server。
喵一眼main函数源码:
docker/docker.go


func main() {
    // 为了exedriver
    if reexec.Init() {
        return
    }
    // 命令行参数解析
    flag.Parse()
    // FIXME: validate daemon flags here

    // 在docker编译时实现
    if *flVersion {
        showVersion()
        return
    }
    if *flDebug {
        os.Setenv("DEBUG", "1")
    }

    // flHosts是docker server监听,docker连接的地址
    if len(flHosts) == 0 {
        defaultHost := os.Getenv("DOCKER_HOST")
        if defaultHost == "" || *flDaemon {
            // If we do not have a host, default to unix socket
            defaultHost = fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET)
        }
        defaultHost, err := api.ValidateHost(defaultHost)
        if err != nil {
            log.Fatal(err)
        }
        flHosts = append(flHosts, defaultHost)
    }

以上代码四个要点:
0.reexec.Init是为了exedriver,分析到driver部分再说;
1.命令行参数用到flag表解析,不赘述;
2.flVersion代码编译时实现的版本控制;
3.flHosts是docker server监听,docker连接的地址。

好继续往下看:
docker/docker.go:

    // 说明启动的是docer daemon,也就是docker server,也就是说后边代码不在执行
    if *flDaemon {
        mainDaemon()
        return
    }


    if len(flHosts) > 1 {
        log.Fatal("Please specify only one -H")
    }
    // protoAddrParts解析出 Docker Client 与 Docker Server建立通信的协议与地址
    protoAddrParts := strings.SplitN(flHosts[0], "://", 2)

    var (
        cli       *client.DockerCli // client对象
        tlsConfig tls.Config        // tls协议配置
    )
    tlsConfig.InsecureSkipVerify = true//默认不启用

    // If we should verify the server, we need to load a trusted ca
    // 如果启用TLS,则读ca文件
    if *flTlsVerify {
        *flTls = true
        certPool := x509.NewCertPool()
        file, err := ioutil.ReadFile(*flCa)
        if err != nil {
            log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err)
        }
        certPool.AppendCertsFromPEM(file)
        tlsConfig.RootCAs = certPool
        tlsConfig.InsecureSkipVerify = false
    }

        // If tls is enabled, try to load and send client certificates
    if *flTls || *flTlsVerify {
        _, errCert := os.Stat(*flCert)
        _, errKey := os.Stat(*flKey)
        if errCert == nil && errKey == nil {
            *flTls = true
            cert, err := tls.LoadX509KeyPair(*flCert, *flKey)
            if err != nil {
                log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err)
            }
            tlsConfig.Certificates = []tls.Certificate{cert}
        }
        // Avoid fallback to SSL protocols < TLS1.0
        tlsConfig.MinVersion = tls.VersionTLS10
    }

以上代码可以看出:
1.docker中如何实现客户端服务端同文件;
2.docker可选支持TLS安全传输协议。

okay看到这里以及看完了docker命令行解析,接下来看客户端启动和服务端启动吧。
客户端启动:

    // 启动客户端
    if *flTls || *flTlsVerify {
        cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
    } else {
        cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], nil)
    }

    // 把命令行参数传过去
    if err := cli.Cmd(flag.Args()...); err != nil {
        if sterr, ok := err.(*utils.StatusError); ok {
            if sterr.Status != "" {
                log.Infof("%s", sterr.Status)
            }
            os.Exit(sterr.StatusCode)
        }
        log.Fatal(err)
    }

再看看client包中的NewDockerCli代码:

func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli {
    var (
        inFd          uintptr
        outFd         uintptr
        isTerminalIn  = false
        isTerminalOut = false
        scheme        = "http"
    )
    // 这样就走https了
    if tlsConfig != nil {
        scheme = "https"
    }

    if in != nil {
        if file, ok := in.(*os.File); ok {
            inFd = file.Fd()
            isTerminalIn = term.IsTerminal(inFd)
        }
    }

    if out != nil {
        if file, ok := out.(*os.File); ok {
            outFd = file.Fd()
            isTerminalOut = term.IsTerminal(outFd)
        }
    }

    if err == nil {
        err = out
    }

    // The transport is created here for reuse during the client session
    tr := &http.Transport{
        TLSClientConfig: tlsConfig,
        Dial: func(dial_network, dial_addr string) (net.Conn, error) {
            // Why 32? See issue 8035
            return net.DialTimeout(proto, addr, 32*time.Second)
        },
    }
    if proto == "unix" {
        // no need in compressing for local communications
        tr.DisableCompression = true
    }

    return &DockerCli{
        proto:         proto,
        addr:          addr,
        in:            in,
        out:           out,
        err:           err,
        key:           key,
        inFd:          inFd,
        outFd:         outFd,
        isTerminalIn:  isTerminalIn,
        isTerminalOut: isTerminalOut,
        tlsConfig:     tlsConfig,
        scheme:        scheme,
        transport:     tr,
    }
}

以上代码有一个要点:proto是DockerClient 与 Docker Server 的传输协议
具体的DockerCli类实现,稍后继续

下面看看 cli.Cmd(flag.Args()…),究竟是咋个情况。撸一下

// Cmd executes the specified command
func (cli *DockerCli) Cmd(args ...string) error {
    if len(args) > 1 {
        method, exists := cli.getMethod(args[:2]...)
        if exists {
            return method(args[2:]...)
        }
    }
    if len(args) > 0 {
        method, exists := cli.getMethod(args[0])
        if !exists {
            fmt.Println("Error: Command not found:", args[0])
            return cli.CmdHelp(args[1:]...)
        }
        return method(args[1:]...)
    }
    // 说明没传入参数,那就返回其帮助信息
    return cli.CmdHelp(args...)
}

其实就是用命令行参数拼出方法名,然后反射把方法返回,然后传参给返回的方法来执行。很优雅,以后做命令行的程序可以借鉴这种方法。为方便理解我把这段代码拉出来改了一下,如下所示:

package main

import (
    "fmt"
    "reflect"
    "strings"
)

type Client struct {
    name string
}

func (cli *Client) CmdHelp(args ...string) error {
    fmt.Println("func CmdHelp")
    return nil
}

func (cli *Client) CmdPull(args ...string) error {
    fmt.Println("func CmdPull")
    fmt.Println(args)
    return nil
}

func (cli *Client) getMethod(args ...string) (func(...string) error, bool) {
    camelArgs := make([]string, len(args))
    for i, s := range args {
        if len(s) == 0 {
            return nil, false
        }
        camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
    }
    methodName := "Cmd" + strings.Join(camelArgs, "")
    method := reflect.ValueOf(cli).MethodByName(methodName)
    if !method.IsValid() {
        return nil, false
    }
    return method.Interface().(func(...string) error), true
}

func (cli *Client) Cmd(args ...string) error {
    if len(args) > 1 {
        method, exists := cli.getMethod(args[:2]...)
        if exists {
            return method(args[2:]...)
        }
    }
    if len(args) > 0 {
        method, exists := cli.getMethod(args[0])
        if !exists {
            fmt.Println("Error: Command not found:", args[0])
            return cli.CmdHelp(args[1:]...)
        }
        return method(args[1:]...)
    }
    return cli.CmdHelp(args...)
}

func main() {
    var cli *Client
    cli.Cmd("pull", "1", "2", "3")
}

以上代码打印:

func CmdPull
[1 2 3]

可以看到调用了CmdPull,并且参数成功传入了函数CmdPull。docker源码中也是一样,看下CmdPull源码:

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中
    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.LoadConfigFile()

    // Resolve the Auth config relevant for this server
    authConfig := cli.configFile.ResolveAuthConfig(hostname)
    // 创建pull函数,作用是向docker server 发送Post命令,其中/images/create?"+v.Encode()为url部分,map[string][]string{
            "X-Registry-Auth": registryAuthHeader,为认证信息
    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,
        })
    }

    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
    }

ok,至此docker clinet已经启动成功,下一篇我写一下学习docker deamon的笔记

路漫漫,其修远兮

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值