热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[dockerce源码分析系列]创建(create)容器简析

1概述:1.1环境版本信息如下:a、操作系统:centos7.6,amd64b、服务器docker版本:v

1 概述:


1.1 环境

版本信息如下:
a、操作系统: centos 7.6,amd64
b、服务器docker版本:v18.09.2
c、docker的存储驱动: overlay2


2 源码简析:

用户docker run,client至多会向docker daemon发起三次远程调用,分别是创建(拉取)镜像、创建容器、启动容器。本篇文章分析服务端创建容器的过程。


2.1 服务端注册路由initRoutes()

func (r *containerRouter) initRoutes() {r.routes = []router.Route{/*其他接口*/router.NewPostRoute("/containers/create", r.postContainersCreate),/*其他接口*/}
}



2.1 postContainersCreate(…)方法

func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {/*检查请求对象的数据,对输入数据进行校验是非常有必要的*/// 获取容器名name := r.Form.Get("name")// 从http body中解析出几个对象// networkingConfig这个map一般为空map,因为一般不在命令行中设置容器ip等网络信息。// config是容器的配置,包括镜像、容器名称、环境变量、启动命令entrypoint、是否挂载终端、容器端口映射等。// hostConfig是主机相关的配置,包括挂载目录映射关系、网络模式、重启策略、cgroup设置,是否privileged、DNS设置、日志配置等。config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)if err != nil {return err}// 获取api版本version := httputils.VersionFromContext(ctx)adjustCPUShares := versions.LessThan(version, "1.19")// When using API 1.24 and under, the client is responsible for removing the containerif hostConfig != nil && versions.LessThan(version, "1.25") {hostConfig.AutoRemove = false}// 创建容器ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{Name: name,Config: config,HostConfig: hostConfig,NetworkingConfig: networkingConfig,AdjustCPUShares: adjustCPUShares,})if err != nil {return err}// 创建容器成功,给客户端返回响应return httputils.WriteJSON(w, http.StatusCreated, ccr)
}

s.backend的实现如下:

// daemon/create.go文件
// 创建一个普通容器
func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) {return daemon.containerCreate(params, false)
}



2.2 容器config对象

config是容器的配置,包括镜像、容器名称、环境变量、启动命令entrypoint、是否挂载终端、容器端口映射等。
在这里插入图片描述




2.3 容器hostConfig对象

hostConfig是主机层次的配置,包括挂载目录映射关系、网络模式、重启策略、cgroup,是否privileged、DNS设置、日志配置、OOM分数的调整数等。
在这里插入图片描述




2.4 containerCreate(…)方法

校验入参,最终调用daemon.create(…)创建容器。

// daemon/create.go文件
func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) {start := time.Now()// 校验1,客户端的参数是不合法的if params.Config == nil {return containertypes.ContainerCreateCreatedBody{}, errdefs.InvalidParameter(errors.New("Config cannot be empty in order to create a container"))}os := runtime.GOOSif params.Config.Image != "" {// 获取镜像img, err := daemon.imageService.GetImage(params.Config.Image)if err == nil {os = img.OS}} else {// This mean scratch. On Windows, we can safely assume that this is a linux// container. On other platforms, it's the host OS (which it already is)if runtime.GOOS == "windows" && system.LCOWSupported() {os = "linux"}}// 校验2,客户端的参数是不合法的warnings, err := daemon.verifyContainerSettings(os, params.HostConfig, params.Config, false)if err != nil {return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)}// 校验3,客户端的参数是不合法的err = verifyNetworkingConfig(params.NetworkingConfig)if err != nil {return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)}// 如果需要,稍微调整一下params对象的内容if params.HostConfig == nil {params.HostConfig = &containertypes.HostConfig{}}err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)if err != nil {return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)}// 调用核心方法创建容器对象// 在内存中创建了container对象,并在宿主机上创建一些目录和文件。container, err := daemon.create(params, managed)if err != nil {return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err}// prometheus指标containerActions.WithValues("create").UpdateSince(start)// 创建成功,将容器ID返回return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil
}



2.5 daemon.create(…)方法

真正创建容器的方法。业务逻辑主要是在内存中创建container对象,创建索引,以及在宿主机上创建一些目录和文件。这些目录和文件包括:
1)/var/lib/docker/overlay2/{ID}/目录下的子目录(diff、work)和文件(link和lower)。
2&#xff09;/var/lib/docker/image/overlay2/layerdb/mounts/<容器ID>/{init-id,mount-id,parent}文件
3&#xff09;/var/lib/docker/containers/<容器ID>目录下创建文本文件&#xff1a;config.v2.json和hostconfig.json

// daemon/create.go文件
func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) {var (container *container.Containerimg *image.ImageimgID image.IDerr error)os :&#61; runtime.GOOSif params.Config.Image !&#61; "" {img, err &#61; daemon.imageService.GetImage(params.Config.Image)if err !&#61; nil {return nil, err}if img.OS !&#61; "" {os &#61; img.OS} else {// default to the host OS except on Windows with LCOWif runtime.GOOS &#61;&#61; "windows" && system.LCOWSupported() {os &#61; "linux"}}imgID &#61; img.ID()if runtime.GOOS &#61;&#61; "windows" && img.OS &#61;&#61; "linux" && !system.LCOWSupported() {return nil, errors.New("operating system on which parent image was created is not Windows")}} else {if runtime.GOOS &#61;&#61; "windows" {os &#61; "linux" // &#39;scratch&#39; case.}}// 合并指的是&#xff1a;params.Config有些内容为空&#xff0c;则用img中的值来进行赋值// 校验指的是&#xff1a;检查cmd和entrypoint是否都为空&#xff0c;如果都是空则返回错误if err :&#61; daemon.mergeAndVerifyConfig(params.Config, img); err !&#61; nil {return nil, errdefs.InvalidParameter(err)}// 合并指的是&#xff1a;container级别的日志配置项为空&#xff0c;则用daemon的日志配置项来进行赋值// 校验指的是&#xff1a;检查日志驱动名称、日志模式&#xff0c;max-buffer-size等等与日志相关的配置项if err :&#61; daemon.mergeAndVerifyLogConfig(¶ms.HostConfig.LogConfig); err !&#61; nil {return nil, errdefs.InvalidParameter(err)}// 创建一个container结构体&#xff0c;此时它的属性RWLayer还是为空&#xff08;那应该在后续操作中会进行赋值&#xff0c;确实如此&#xff09;。if container, err &#61; daemon.newContainer(params.Name, os, params.Config, params.HostConfig, imgID, managed); err !&#61; nil {return nil, err}defer func() {if retErr !&#61; nil {if err :&#61; daemon.cleanupContainer(container, true, true); err !&#61; nil {logrus.Errorf("failed to cleanup container on create error: %v", err)}}}()if err :&#61; daemon.setSecurityOptions(container, params.HostConfig); err !&#61; nil {return nil, err}container.HostConfig.StorageOpt &#61; params.HostConfig.StorageOpt/*如果是windows操作系统&#xff0c;进行一些操作&#xff1a;if runtime.GOOS &#61;&#61; "windows" {修改container.HostConfig.StorageOpt}/*daemon.imageService.CreateLayer(...)主要做的事情&#xff1a;1&#xff09;创建/var/lib/docker/overlay2/{ID-init}/目录下的子目录&#xff08;diff、work&#xff09;和文件&#xff08;link和lower&#xff09;2&#xff09;创建/var/lib/docker/overlay2/{ID}/目录下的子目录&#xff08;diff、work&#xff09;和文件&#xff08;link和lower&#xff09;3&#xff09;创建 /var/lib/docker/image/overlay2/layerdb/mounts/<容器ID>/{init-id,mount-id,parent}文件 */rwLayer, err :&#61; daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMapping))if err !&#61; nil {return nil, errdefs.System(err)}// container对象的属性RWLayer进行赋值container.RWLayer &#61; rwLayer// rootIDs是一个结构体&#xff0c;里面包括了UID、GID和SID&#xff0c;一般这三者都是0。rootIDs :&#61; daemon.idMapping.RootPair()// 创建/var/lib/docker/containers/{容器ID}目录&#xff0c;并设置相应的用户ID、组ID、权限等属性if err :&#61; idtools.MkdirAndChown(container.Root, 0700, rootIDs); err !&#61; nil {return nil, err}// 创建/var/lib/docker/containers/{容器ID}/checkpoints目录&#xff0c;并设置相应的用户ID、组ID、权限等属性if err :&#61; idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err !&#61; nil {return nil, err}// daemon.setHostConfig(...)做的两件事&#xff1a;// 1&#xff09;设置入参container对象的属性MountPoints和属性HostConfig// 2&#xff09;在/var/lib/docker/containers/<容器ID>目录下创建文本文件&#xff1a;config.v2.json和hostconfig.jsonif err :&#61; daemon.setHostConfig(container, params.HostConfig); err !&#61; nil {return nil, err}// 挂载又卸载容器的merged目录&#xff0c;以及如果使用docker volume&#xff0c;则发生数据复制。if err :&#61; daemon.createContainerOSSpecificSettings(container, params.Config, params.HostConfig); err !&#61; nil {return nil, err}var endpointsConfigs map[string]*networktypes.EndpointSettingsif params.NetworkingConfig !&#61; nil {// params.NetworkingConfig.EndpointsConfig往往是一个空mapendpointsConfigs &#61; params.NetworkingConfig.EndpointsConfig}runconfig.SetDefaultNetModeIfBlank(container.HostConfig)// 在非用户自定义网络模式下&#xff0c;做的事情很简单&#xff1a;为container对象的属性NetworkSettings的属性Networks添加一个key&#xff0c;key就是"bridge"daemon.updateContainerNetworkSettings(container, endpointsConfigs)// container对象注册到内存中&#xff0c;并使用前缀树来索引// 将此时的container对象持久化至磁盘&#xff1a;/var/lib/docker/containers/{容器ID}/config.v2.jsonif err :&#61; daemon.Register(container); err !&#61; nil {return nil, err}stateCtr.set(container.ID, "stopped")daemon.LogContainerEvent(container, "create")return container, nil
}



2.6 createContainerOSSpecificSettings方法

挂载又卸载merged目录&#xff0c;以及如果使用docker volume&#xff0c;则发生数据复制。

// daemon/create.go文件
func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {// merged目录是在此处创建&#xff0c;并进行绑定挂载/*mountTarget :&#61; merged目录mountdata的内容类似: index&#61;off,lowerdir&#61;/var/lib/docker/overlay2/l/3EWF6KYE4B65XPHTQIH5PJAVEQ:/var/lib/docker/overlay2/l/3SIMO6NI4MYP7HAZCQHFISBBV3,upperdir&#61;/var/lib/docker/overlay2/9b30aee99a63b6f5b06a13fdbe78970ac034f5a6e292fdc62620d669cd9715dd/diff,workdir&#61;/var/lib/docker/overlay2/9b30aee99a63b6f5b06a13fdbe78970ac034f5a6e292fdc62620d669cd9715dd/workmount("overlay", mountTarget, "overlay", 0, mountData);*/if err :&#61; daemon.Mount(container); err !&#61; nil {return err}// 在函数返回时卸载merged目录defer daemon.Unmount(container)rootIDs :&#61; daemon.idMapping.RootPair()if err :&#61; container.SetupWorkingDirectory(rootIDs); err !&#61; nil {return err}// 有默认的一些路径是masked和只读// 例如/proc/acpi是masked path&#xff0c;/proc/bus是readonly path。if hostConfig.MaskedPaths &#61;&#61; nil && !hostConfig.Privileged {hostConfig.MaskedPaths &#61; oci.DefaultSpec().Linux.MaskedPaths // Set it to the default if nilcontainer.HostConfig.MaskedPaths &#61; hostConfig.MaskedPaths}if hostConfig.ReadonlyPaths &#61;&#61; nil && !hostConfig.Privileged {hostConfig.ReadonlyPaths &#61; oci.DefaultSpec().Linux.ReadonlyPaths // Set it to the default if nilcontainer.HostConfig.ReadonlyPaths &#61; hostConfig.ReadonlyPaths}// 有机会发生数据复制// docker run -v同时指定宿主机目录和容器目录时&#xff0c;不会发生数据复制&#xff0c;这种mountPoint对象的voloume字段是nil&#xff0c;因此被跳过// docker run -v 不指定宿主目录时&#xff0c;就使用docker volume&#xff08;即/var/lib/docker/volumes目录下的子目录&#xff09;&#xff0c;此时如果容器中的挂载点已有数据&#xff0c;则把容器中挂载点中的数据复制到docker volume中。return daemon.populateVolumes(container)
}



2.7 容器中的masked path和readonly path

在这里插入图片描述

在这里插入图片描述




2.8 setHostConfig(…)方法

1&#xff09;设置container对象的属性MountPoints和属性HostConfig。
2&#xff09;在/var/lib/docker/containers/<容器ID>目录下创建文本文件&#xff1a;config.v2.json和hostconfig.json

// daemon/create.go文件
// 设置入参container对象的属性MountPoints和属性HostConfig
func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *containertypes.HostConfig) error {/*registerMountPoints()本质是设置入参container的属性MountPoints。挂载点包括镜像Dockerfile中指定的挂载点、用户命令行指定的来自其他容器的volume和命令中指定的绑定挂载。*/if err :&#61; daemon.registerMountPoints(container, hostConfig); err !&#61; nil {return err}container.Lock()defer container.Unlock()// 1&#xff09;hostConfig使用了link机制的话&#xff0c;则进行相应的操作。// 2&#xff09;将hostConfig对象写到磁盘&#xff1a;/var/lib/docker/containers/{容器ID}/hostconfig.jsonif err :&#61; daemon.registerLinks(container, hostConfig); err !&#61; nil {return err}// 如果入参hostConfig的NetworkMode为""&#xff0c;则设置为"default"runconfig.SetDefaultNetModeIfBlank(hostConfig)// container对象的属性HostConfig的内容是一堆空值&#xff0c;因此将它直接设置为入参hostConfigcontainer.HostConfig &#61; hostConfig// 把container对象持久化到磁盘中&#xff1a;/var/lib/docker/containers/{容器ID}/config.v2.jsonreturn container.CheckpointTo(daemon.containersReplica)
}



3 总结&#xff1a;

创建容器的过程比较简单&#xff0c;本质就是在内存中创建容器对象和在宿主机上创建目录和文件&#xff0c;以及发生一些挂载操作和可能发生的数据复制&#xff08;docker volume&#xff09;。


推荐阅读
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
author-avatar
赵晓伟
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有