资讯专栏INFORMATION COLUMN

Docker 源码走读 - 在运行 Docker run 时发生了什么?

赵连江 / 724人阅读

摘要:源码走读在运行时发生了什么标签空格分隔原文作者是,原文地址是在这篇博文中我将回答一下问题运行一个期间内部发生了什么开始首先从并检查代码。这个发生在和行之间。如果一个错误发生,它会被记录,然后程序退出。这些覆盖了在客户端里面发生了什么。

Docker 源码走读 - 在运行 Docker run 时发生了什么?

标签(空格分隔): Docker


  

原文作者是 Frank Scholten,原文地址是 Docker code walkthrough – What happens during a Docker run?

在这篇博文中我将回答一下问题:运行一个 Docker run 期间 Docker 内部发生了什么?

开始

首先从 Docker Github repo clone 并检查代码。

$ git clone https://github.com/docker/docker
范读

用你喜欢的编辑器打开工程并且范读下 main tree。Docker 是使用 golang 编写并由很多包组成。比如,从顶部到底部开始浏览,你会看到 apibuilderbuiltins, contrib 等等。一些目录包含更多的子目录。

Docker executable 内部

第一件事,就是寻找 func main,当我们运行 Docker executable 时被执行的 Golang 函数。实际上,在代码树中有超过 30 个 main 函数。这些都是工具类的函数,我不会立即就深入其中。让我们继续我们主要寻找的:存在于 docker/docker.go 中的一个 main 函数。让我们更仔细的看。

解析 flags

看以下的代码片段,显示了 main func 的前十几行。当 Docker 启动,它通过 reexec 运行任何的初始化(initializers),如果有,这时它为 Docker executable 解析通过 mflag 包传递的参数。这个包在 pkg/mgflag 下面,别名为 flag。如果需要的话,在这一点它可以打印出版本信息或是开启 debug 模式并记录 debug 日志。

func main() {
  if reexec.Init() {
    return
  }

  flag.Parse()
  // FIXME: validate daemon flags here

  if *flVersion {
    showVersion()
    return
  }

  if *flDebug {
    os.Setenv("DEBUG", "1")
  }

  initLogging(*flDebug)

  ... SNIP

}
Dispatch Docker command & error handling

在选择解析 Docker 捕获的主机设置后,如果有必要,对服务器执行 TLS verification 校验。这个发生在 40 和 107 行之间。看以下的代码片段。flags 解析早于传递给来自于 api/client/cli.goDockerCli 类型的 Cmd 方法。
如果一个错误发生,它会被记录,然后程序退出。

func main() {

  ... SNIP

  if err := cli.Cmd(flag.Args()...); err != nil {
    if sterr, ok := err.(*utils.StatusError); ok {
      if sterr.Status != "" {
        log.Println("%s", sterr.Status)
      }
      os.Exit(sterr.StatusCode)
    }
    log.Fatal(err)
  }
}
cli 包

让我们深入 cli 包看看 Docker 命令被怎样处理的。为了能够运行子命令,我们关注 3 件事:

DockerCli struct

Cmd 方法

GetMethod 方法

DockerCli

DockerCli struct 包含每个 Docker 命令必需的数据结构,比如使用的协议, in-, output- 和 error writers 以及 TLS 指定的数据结构。

type DockerCli struct {
    proto      string
    addr       string
    configFile *registry.ConfigFile
    in         io.ReadCloser
    out        io.Writer
    err        io.Writer
    key        libtrust.PrivateKey
    tlsConfig  *tls.Config
    scheme     string
    // inFd holds file descriptor of the client"s STDIN, if it"s a valid file
    inFd uintptr
    // outFd holds file descriptor of the client"s STDOUT, if it"s a valid file
    outFd uintptr
    // isTerminalIn describes if client"s STDIN is a TTY
    isTerminalIn bool
    // isTerminalOut describes if client"s STDOUT is a TTY
    isTerminalOut bool
    transport     *http.Transport
}
Cmd 方法

Cmd 函数的职责是通过使用 getMethod 函数 把命令参数转换成一个函数。它将来已经支持多个命令,也许是 docker groups create,尽管目前为止我知道还没有这样的命令实现。

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...)
}
getMethod

注意 getMethod 是小写字母。这意味着它无法导出包外,因此它仅仅是在 cli 内可用的。因此这个方法怎样找出正确的函数?看下面的代码片段。它首先建立一个以 Cmd 开始的由大写字母组合的 string。万一 Docker 运行 methodName 变量将被 CmdRun。使用来自于 Golang reflect 包的 MethodByName 函数,它检索到一个函数指针并返回它。

func (cli *DockerCli) 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, "")
    fmt.Println(methodName)
    method := reflect.ValueOf(cli).MethodByName(methodName)
    if !method.IsValid() {
        return nil, false
    }
    return method.Interface().(func(...string) error), true
}
CmdRun

最后我们到达一个负责容器运行的函数:在 api/client/commands.goCmdRun。这个文件包含了所有的 Docker 命令。它自己运行的参数被解析,比如 image,command 和其他参数。因为我们已经通读,我不会展示那些代码,我会以展示更有趣的事代替:运行命令启动一个新的容器。

创建容器

以下代码片段展示了容器是怎样被创建的。容器的配置被合并到 run config 和 host config。

实际创建一个容器是调用一个 HTTP POST 给 Docker 服务器。

func (cli *DockerCli) CmdRun(args ...string) error {

  SNIP ...

  runResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
    if err != nil {
      return err
    }
  SNIP ...

}

func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) {
    containerValues := url.Values{}
    if name != "" {
        containerValues.Set("name", name)
    }

    mergedConfig := runconfig.MergeConfigs(config, hostConfig)

    var containerIDFile *cidFile
    if cidfile != "" {
        var err error
        if containerIDFile, err = newCIDFile(cidfile); err != nil {
            return nil, err
        }
        defer containerIDFile.Close()
    }

    //create the container
    stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
    //if image not found try to pull it
    if statusCode == 404 {
        fmt.Fprintf(cli.err, "Unable to find image "%s" locally
", config.Image)

        // we don"t want to write to stdout anything apart from container.ID
        if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
            return nil, err
        }
        // Retry
        if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
            return nil, err
        }
    } else if err != nil {
        return nil, err
    }

    var result engine.Env
    if err := result.Decode(stream); err != nil {
        return nil, err
    }

    for _, warning := range result.GetList("Warnings") {
        fmt.Fprintf(cli.err, "WARNING: %s
", warning)
    }

    if containerIDFile != nil {
        if err = containerIDFile.Write(result.Get("Id")); err != nil {
            return nil, err
        }
    }

    return result, nil
}

这些覆盖了在 Docker 客户端里面发生了什么。当然在 Docker 服务器和 libcontainer 还有更多的代码等待探索,但这将会留给以后的博文。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26346.html

相关文章

  • Docker 资源管理

    摘要:标签空格分隔资源管理内存磁盘注该文作者是,原文是。如果你运行命令,你自己可以看到这个结构当我们想管理资源的时候,这个方法提供了很大的灵活性,因为我们可以分别的管理每个组。 标签(空格分隔): Docker 资源管理 内存 CPU 磁盘 I/O 注:该文作者是 Marek Goldmann,原文是 Resource management in Docker。 在这篇博客文章...

    VioletJack 评论0 收藏0
  • 学习使用DockerDocker-Compose和Rancher搭建部署Pipeline(一)

    摘要:工程师选择了环境中的一台当前没有在负载均衡器中被激活的主机。工程师登陆到这台主机并从注册表中获取新的版本。在生产维护窗口中,更新负载均衡器使其指向更新过的主机。然而将部署代码化的问题仍然存在。 这篇文章是一系列文章的第一篇,在这一系列文章中,我们想要分享我们如何使用Docker、Docker-Compose和Rancher完成容器部署工作流的故事。我们想带你从头开始走过pipeline...

    mikyou 评论0 收藏0
  • VLIS实验室云计算组张磊:关于Docker、开源,以及教育的尝试

    摘要:年我们开始专注于开源云计算技术,当时开源的力量正在逐渐浮现。问你现在在实验室的工作是什么我主要负责实验室云计算团队的技术工作,以及与技术相关的其他事宜,包括开源以及一些商业上的技术合作。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/203520 张磊,浙江大学计算机学院博士生,科研人员,VLIS实验室云计算组技...

    Mike617 评论0 收藏0
  • VLIS实验室云计算组张磊:关于Docker、开源,以及教育的尝试

    摘要:年我们开始专注于开源云计算技术,当时开源的力量正在逐渐浮现。问你现在在实验室的工作是什么我主要负责实验室云计算团队的技术工作,以及与技术相关的其他事宜,包括开源以及一些商业上的技术合作。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/203520 张磊,浙江大学计算机学院博士生,科研人员,VLIS实验室云计算组技...

    Jonathan Shieber 评论0 收藏0
  • VLIS实验室云计算组张磊:关于Docker、开源,以及教育的尝试

    摘要:年我们开始专注于开源云计算技术,当时开源的力量正在逐渐浮现。问你现在在实验室的工作是什么我主要负责实验室云计算团队的技术工作,以及与技术相关的其他事宜,包括开源以及一些商业上的技术合作。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/203520 张磊,浙江大学计算机学院博士生,科研人员,VLIS实验室云计算组技...

    Steve_Wang_ 评论0 收藏0

发表评论

0条评论

赵连江

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<