docker v1.11 源码重构分析

来源:互联网 发布:风险评价矩阵图 编辑:程序博客网 时间:2024/05/23 22:58

基于docker v1.12的源代码,对docker engine v1.11中重构后的源码结构进行分析,涵盖dockerd, containerd, containerd-shim, runC。

docker1.11新特性

docker在v1.11版本进行了重大的重构,对docker engine和container进行了解耦,docker engine运行在containerd上,containerd运行在runC上,通过containerd-shim中间层进行了解耦。之前的docker engine分为四个组件:dockerd、containerd、containerd-shim、runC,之间关系如下图所示:

这里写图片描述

containerd是一个守护进程,它可以使用runC的接口管理容器,使用gRPC暴露容器的其他功能。由于容器运行时是孤立的引擎,引擎最终能够启动和升级而无需重新启动容器。在v1.12版本中,在dockerd的启动参数或者配置文件中,开启live-store 参数即可,具体参考 官方live-store升级dockerd文档。

runC是一个轻量级的工具,它是用来运行容器的,runC实际上就是在libcontainer上进行了接口封装,这是一个独立的二进制文件。

关于runC和containerd的更详细的源码分析,请参考我的相应博文。

docker1.12四个组件的介绍

编译

可按照官网的方式直接编译docker:https://docs.docker.com/v1.5/contributing/devenvironment/

注意,containerd和runc的源码并不直接在docker的源码中,而是已经拆分出来。

在docker编译的时候,会在github上拉取containerd和runc的源码并编译生成containerd和runc的二进制文件,包括docker-containerd、docker-containerd-shim、docker-containerd-ctr这三个可执行文件。代码在docker的Dockerfile文件中:

这里写图片描述

同样,会从github上拉取runc的源码并编译生成runc的二进制文件docker-runc。代码在docker的Dockerfile中:

这里写图片描述

docker本身编译后会生成dockerd、docker、docker-proxy这三个二进制文件。其中,dockerd是docker的daemon,docker是client。

运行

启动dockerd后,会自动启动containerd,如下所示:

这里写图片描述

docker1.12源码结构分析

dockerd和docker的主函数入口分别在cmd/dockerd和cmd/docker。首先来分析dockerd的启动流程。

dockerd启动流程

在cmd/dockerd/docker.go的main函数中,进行一些参数的初始化工作后,会调用到cmd/dockerd/daemon.go中的start()函数:

if !stop {        err = daemonCli.start()        notifyShutdown(err)        if err != nil {            logrus.Fatal(err)        }}

在start()函数中,会通过以下源码,根据启动参数中的tcp、unix socket等,分别创建接收client端请求的api server:

api := apiserver.New(serverConfig)cli.api = apifor i := 0; i < len(cli.Config.Hosts); i++ {    var err error    if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {        return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err)    }    protoAddr := cli.Config.Hosts[i]    protoAddrParts := strings.SplitN(protoAddr, "://", 2)    if len(protoAddrParts) != 2 {        return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)    }    proto := protoAddrParts[0]    addr := protoAddrParts[1]    // It's a bad idea to bind to TCP without tlsverify.    if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) {        logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]")    }    ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)    if err != nil {        return err    }    ls = wrapListeners(proto, ls)    // If we're binding to a TCP port, make sure that a container doesn't try to use it.    if proto == "tcp" {        if err := allocateDaemonPort(addr); err != nil {            return err        }    }    logrus.Debugf("Listener created for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])    api.Accept(protoAddrParts[1], ls...)}

接下来,会通过以下源码调用到libcontainerd/remote_linux.go当中的New()函数,创建containerdRemote,与containerd建立联系:

containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)

在这个New()函数当中,会通过以下代码调用到runContainerdDaemon()函数:

 if r.startDaemon {        if err := r.runContainerdDaemon(); err != nil {            return nil, err        }}

在runContainerdDaemon()函数中,会启动containerd进程:

// Start a new instanceargs := []string{    "-l", fmt.Sprintf("unix://%s", r.rpcAddr),    "--shim", "docker-containerd-shim",    "--metrics-interval=0",    "--start-timeout", "2m",    "--state-dir", filepath.Join(r.stateDir, containerdStateDir),}if r.runtime != "" {    args = append(args, "--runtime")    args = append(args, r.runtime)}if r.debugLog {    args = append(args, "--debug")}if len(r.runtimeArgs) > 0 {    for _, v := range r.runtimeArgs {        args = append(args, "--runtime-args")        args = append(args, v)    }    logrus.Debugf("libcontainerd: runContainerdDaemon: runtimeArgs: %s", args)}cmd := exec.Command(containerdBinary, args...)

回到cmd/dockerd/daemon.go中的start()函数中,通过以下代码调用daemon/daemon.go中的NewDaemon()函数,创建daemon:

d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)

在NewDaemon()函数中,处理一些基本配置后,通过以下代码调用layer/layer_store.go中的NewStoreFromOptions(options StoreOptions)函数。

d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{       StorePath:                 config.Root,       MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),       GraphDriver:               driverName,       GraphDriverOptions:        config.GraphOptions,       UIDMaps:                   uidMaps,       GIDMaps:                   gidMaps,   })

在NewStoreFromOptions(options StoreOptions)函数中,会调用graphdriver/driver.go中的New函数,在New函数中,会通过以下代码获取到rootfs driver。rootfs driver或者是dockerd启动参数中指定的,或者是系统推荐的:

func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {   if name != "" {       logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver       return GetDriver(name, root, options, uidMaps, gidMaps)   }   // Guess for prior driver   driversMap := scanPriorDrivers(root)   logrus.Debugf("driversMap is v%", driversMap)   for _, name := range priority {       if name == "vfs" {           // don't use vfs even if there is state present.           continue       }       if _, prior := driversMap[name]; prior {           // of the state found from prior drivers, check in order of our priority           // which we would prefer           driver, err := getBuiltinDriver(name, root, options, uidMaps, gidMaps)           if err != nil {               // unlike below, we will return error here, because there is prior               // state, and now it is no longer supported/prereq/compatible, so               // something changed and needs attention. Otherwise the daemon's               // images would just "disappear".               logrus.Errorf("[graphdriver] prior storage driver %q failed: %s", name, err)               return nil, err           }           // abort starting when there are other prior configured drivers           // to ensure the user explicitly selects the driver to load           if len(driversMap)-1 > 0 {               var driversSlice []string               for name := range driversMap {                   driversSlice = append(driversSlice, name)               }               return nil, fmt.Errorf("%q contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", root, strings.Join(driversSlice, ", "))           }           logrus.Infof("[graphdriver] using prior storage driver %q", name)           return driver, nil       }

docker命令行执行分析

docker client端docker的入口函数在cmd/docker/docker.go当中,在main函数中,会对命令行参数进行处理,以docker create为例,最终会调用到api/client/container/create.go中的NewCreateCommand(dockerCli *client.DockerCli)函数,然后继续调用到runCreate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions)函数,进而调用到createContainer函数。然后通过以下源码调用到vendor/src/github.com/docker/engine-api/client/container_create.go中的ContainerCreate函数。

//create the containerresponse, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)

在ContainerCreate函数中,会向api server端发起post请求:

serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)if err != nil {    if serverResp != nil && serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {        return response, imageNotFoundError{config.Image}    }    return response, err}

在api/server/router/container/container.go的initRoutes()函数中,定义了以下http router:

……// POSTrouter.NewPostRoute("/containers/create", r.postContainersCreate),router.NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),router.NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),……

因而,server端会把由api/server/router/container/container_routes.go中的postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string)函数处理该post请求。
在postContainersCreate函数中,会通过以下代码调用到daemon/create.go中的ContainerCreate(params types.ContainerCreateConfig, validateHostname bool)函数,最后由docker daemon来创建container:

// ContainerCreate creates a regular containerfunc (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error) {    return daemon.containerCreate(params, false, validateHostname)}
1 0