docker build源码分析

一.前言:

最近在搞Docker,需要仔细的去了解Docker源码,在网上找来找去都是旧版本的,很头疼,看了众多的有关博客和《docker源码分析》,总结一下。源码基于docker-ce19.03(目前是网上最新的版本)。

 

二.简单了解 docker build 的作用:

用户可以通过一个 自定义的 Dockerfile 文件以及相关内容,从一个基础镜像起步,对于 Dockerfile 中的每一 条命令,都在原先的镜像 layer 之上再额外构建一个新的镜像 layer ,直至构建出用户所需 的镜像。 

由于 docker build 命令由 Docker 用户发起,故 docker build 的流程会贯穿 Docker Client Docker Server 以及 Docker Daemon 这三个重要的 Docker 模块。所以咱也是以这三个 Docker 模块为主题,分析 docker build 命令的执行,其中 Docker Daemon 最为重要。

现在Docker Client和代码Docker Daemon已经分开。

 

 

三.Docker Client代码

git clone https://github.com/moby/moby.git
 
1、Docker Client执行docker build

 

 作为用户请求的人口,自然第一个接收并处理 docker build 命令。主要包括:定义并解析flag参数、获取Dockerfile相关内容。

   入口源码在 cmd\docker\docker.go,其他main函数是入口。Docker Client是每次执行就好运行一次,也就是docker(bin)。

流程:

   runBuild()函数只是从Client解析docker build命令和参数,对于我不怎么需要,先不分析。

   文件路径:cli\command\image\build.go

    

 

四.Docker Daemon启动代码

 

Docker Daemon与Docker Client不同,一般都是作为系统的daemon程序开机启动,所以每次开机基本上只初始化一次,运行在后台。
查看Docker Daemon的启动情况可以使用:
$ systemctl status docker.service
下载源码:
git clone https://github.com/moby/moby.git
 
1.Docker Daemon启动
Docker Daemon的代码入口: cmd\dockerd\docker.go的main函数

 

func main() {
    if reexec.Init() { //因为golang会先执行包内的 init 函数, 执行registeredInitializers[os.Args[0]],也就是driver初始化函数
        return
    }

    cmd, err := newDaemonCommand()
    if err != nil {
        onError(err)
    }
    cmd.SetOutput(stdout)
    if err := cmd.Execute(); err != nil {
        onError(err)
    }
}

 

主要是执行
newDaemonCommand
    defaultDaemonConfigFile, err := getDefaultDaemonConfigFile()
        filepath.Join(dir, "daemon.json"), nil //获取daemon.json配置文件
        installConfigFlags(opts.daemonConfig, flags); //向pflag添加标志。FlagSet用于配置守护进程
runDaemon
    daemonCli := NewDaemonCli()
    return daemonCli.start(opts)
        // //初始化server的socket端口,等待客户端连接
        cli.api = apiserver.New(serverConfig)
        hosts, err := loadListeners(cli, serverConfig)
        d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore) //设置了守护进程能够服务所有来自web服务器的请求。
        initRouter(routerOptions)//路由器初始化
到这里Docker Daemon初始化完了,等待Docker Client连接。
        
 

 

五.Docker Daemon执行docker build命令

1.docker server 负责根据请求类型以及请求的 URL ,路由转发 Docker 请求至相应的处理方法。在处理方法中, Docker Server 会创建相应的 Job ,为 Job 置相应的执行参数并触发该 Job 的运行。在此,将参数信息配置到JSON数据中。

dockerdaemon启动的时候initRouter将build命令的路由器初始化。

 

 

 

2.NewPostRoute()调用 postBuild()函数

 

3、通过参数信息配置(newImageBuildOptions) 构建选项数据(buildOption)

      getAuthConfig()请求header名为X-Registry-Config,值为用户的config信息(用户认证信息)

 

4、配置好选项数据,将其作为参数,调用imgID:= br.backend.Build()

 

5、return newBuilder(ctx, builderOptions).build(source, dockerfile)

     newBuilder()从可选的dockerfile和options创建一个新的Dockerfile构建器

     build()通过解析Dockerfile并执行文件中的指令来运行Dockerfile构建器

 

6、build()函数调用dispatchFromDockerfile(),dispatchFromDockerfile()调用dispatch()函数。

      dispatch()函数找到每一种Dockerfile命令对应的handler处理函数并执行。

 

 

 

    

7,dispatchRun是非常重要的,下面详细分析:

 

// RUN some command yo
//
// run a command and commit the image. Args are automatically prepended with
// the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
// Windows, in the event there is only one argument The difference in processing:
//
// RUN echo hi          # sh -c echo hi       (Linux and LCOW)
// RUN echo hi          # cmd /S /C echo hi   (Windows)
// RUN [ "echo", "hi" ] # echo hi
//

func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
    if !system.IsOSSupported(d.state.operatingSystem) {
        return system.ErrNotSupportedOperatingSystem
    }
    //将最底层的Config结构体传入
    stateRunConfig := d.state.runConfig
    cmdFromArgs, argsEscaped := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.state.operatingSystem, c.Name(), c.String())
    buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)

    saveCmd := cmdFromArgs
    if len(buildArgs) > 0 {
        saveCmd = prependEnvOnCmd(d.state.buildArgs, buildArgs, cmdFromArgs)
    }

    runConfigForCacheProbe := copyRunConfig(stateRunConfig,
        withCmd(saveCmd),
        withArgsEscaped(argsEscaped),
        withEntrypointOverride(saveCmd, nil))
    if hit, err := d.builder.probeCache(d.state, runConfigForCacheProbe); err != nil || hit {
        return err
    }

    runConfig := copyRunConfig(stateRunConfig,
        withCmd(cmdFromArgs),
        withArgsEscaped(argsEscaped),
        withEnv(append(stateRunConfig.Env, buildArgs...)),
        withEntrypointOverride(saveCmd, strslice.StrSlice{""}),
        withoutHealthcheck())
	//create根据基础镜像ID以及运行容器时所需的runconfig信息,来创建Container对象
	//进入create,调用Create,再调用ContainerCreate
    cID, err := d.builder.create(runConfig)
    if err != nil {
        return err
    }
	//Run运行Docker容器,其中包括创建容器文件系统、创建容器的命名空间进行资源隔离、为容器配置cgroups参数进行资源控制,还有运行用户指定的程序。
    if err := d.builder.containerManager.Run(d.builder.clientCtx, cID, d.builder.Stdout, d.builder.Stderr); err != nil {
        if err, ok := err.(*statusCodeError); ok {
            // TODO: change error type, because jsonmessage.JSONError assumes HTTP
            msg := fmt.Sprintf(
                "The command '%s' returned a non-zero code: %d",
                strings.Join(runConfig.Cmd, " "), err.StatusCode())
            if err.Error() != "" {
                msg = fmt.Sprintf("%s: %s", msg, err.Error())
            }

            return &jsonmessage.JSONError{
                Message: msg,
                Code:    err.StatusCode(),
            }
        }
        return err
    }

    // Don't persist the argsEscaped value in the committed image. Use the original
    // from previous build steps (only CMD and ENTRYPOINT persist this).
    if d.state.operatingSystem == "windows" {
        runConfigForCacheProbe.ArgsEscaped = stateRunConfig.ArgsEscaped
    }
	//对运行后的容器进行commit操作,将运行的结果保存在一个新的镜像中。
	return d.builder.commitContainer(d.state, cID, runConfigForCacheProbe)
}

 

回到run()函数上来:

(1)、create()函数执行创建Container对象操作;

(2)、Run()函数执行运行容器操作;

(3)、commitContainer()函数执行提交新镜像操作。

注意:commitContainer()调用的b.docker.Commit(),一定是/daemon/build.go下面的

 

参考:《Docker源码分析.pdf》

             https://guanjunjian.github.io/2017/09/26/study-1-docker-1-client-excuting-flow-for-run/

             https://jimmysong.io/posts/docker-source-code-analysis-code-structure/

             https://blog.csdn.net/warrior_0319/article/details/79931987

 

posted @ 2020-12-25 10:34  luoyuna  阅读(1062)  评论(0编辑  收藏  举报