前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >docker v1.11 源码重构分析

docker v1.11 源码重构分析

作者头像
Walton
发布2018-04-13 16:43:11
1.5K0
发布2018-04-13 16:43:11
举报
文章被收录于专栏:KubernetesKubernetes

基于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()函数:

代码语言:javascript
复制
if !stop {
        err = daemonCli.start()
        notifyShutdown(err)
        if err != nil {
            logrus.Fatal(err)
        }
}

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

代码语言:javascript
复制
api := apiserver.New(serverConfig)
cli.api = api
 
for 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建立联系:

代码语言:javascript
复制
containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)

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

代码语言:javascript
复制
 if r.startDaemon {
        if err := r.runContainerdDaemon(); err != nil {
            return nil, err
        }
}

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

代码语言:javascript
复制
// Start a new instance
args := []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:

代码语言:javascript
复制
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)

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

代码语言:javascript
复制
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启动参数中指定的,或者是系统推荐的:

代码语言:javascript
复制
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函数。

代码语言:javascript
复制
//create the container
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)

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

代码语言:javascript
复制
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:

代码语言:javascript
复制
……
// POST
router.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:

代码语言:javascript
复制
// ContainerCreate creates a regular container
func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error) {
    return daemon.containerCreate(params, false, validateHostname)
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器镜像服务
容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档