• docker 源码分析 五(基于1.8.2版本),Docker容器的创建


    前面讲到了docker容器得镜像,镜像其实是docker容器的静态部分,而docker容器则是docker镜像的动态部分,即启动了一个进程来运行,本篇最要来分析一下怎样创建并运行一个容器。

    创建一个容器在客户端实现是在api/client/create.go,其中得CmdCreate()方法,这个函数的作用是通过一个给定的image来启动一个container;其中的createContainer()函数是最主要的实现部分;

        //create the container

        serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)

        //if image not found try to pull it

        if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {

            fmt.Fprintf(cli.err, "Unable to find image '%s' locally ", ref.ImageName(repo))

            // 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

            }

            if trustedRef != nil && !ref.HasDigest() {

                repoInfo, err := registry.ParseRepositoryInfo(repo)

                if err != nil {

                    return nil, err

                }

                if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil {

                    return nil, err

                }

            }

            // Retry

            if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err !=   nil {

                return nil, err

            }

        } else if err != nil {

            return nil, err

        }

        defer serverResp.body.Close()

        var response types.ContainerCreateResponse

        if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {

            return nil, err

        }

        for _, warning := range response.Warnings {

            fmt.Fprintf(cli.err, "WARNING: %s ", warning)

        }

        if containerIDFile != nil {

            if err = containerIDFile.Write(response.ID); err != nil {

                return nil, err

            }

        }

        return &response, nil

    }

    大体的实现逻辑如上,首先调用createContainer来创建一个container;docker启动的时候是需要镜像得,根据前一篇得内容知道,如果镜像已经下载到本地,那么直接从本地获取镜像就好,如果镜像不在本地,那么打印出来打印出 在本地找不到镜像,然后调用 cli.pullImageCustomOut 去获取需要的镜像(其实就是上一篇讲的内容);在pull完镜像之后,接着在retry一下 createContainer函数;

    创建容器的操作对应在server端得实现在api/server/container.go中的postContainersCreate()函数;

    func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars                map[string]string) error {

        if err := parseForm(r); err != nil {

            return err

        }

        if err := checkForJSON(r); err != nil {

            return err

        }

        var (

            warnings []string

            name     = r.Form.Get("name")

        )

        config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)

        if err != nil {

            return err

        }

        adjustCPUShares := version.LessThan("1.19")

        container, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)

        if err != nil {

            return err

        }

        return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{

            ID:       container.ID,

            Warnings: warnings,

        })

    }

    最主要的是 s.daemon.ContainerCreate() 方法:

    func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig, adjustCPUShares bool) (*Container, []string, error) {

        if config == nil {

            return nil, nil, fmt.Errorf("Config cannot be empty in order to create a container")

        }

        warnings, err := daemon.verifyContainerSettings(hostConfig, config)

        daemon.adaptContainerSettings(hostConfig, adjustCPUShares)

        if err != nil {

            return nil, warnings, err

        }

        container, buildWarnings, err := daemon.Create(config, hostConfig, name)

        if err != nil {

            if daemon.Graph().IsNotExist(err, config.Image) {

                _, tag := parsers.ParseRepositoryTag(config.Image)

                if tag == "" {

                    tag = tags.DefaultTag

                }

                return nil, warnings, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)

            }

            return nil, warnings, err

        }

        warnings = append(warnings, buildWarnings...)

        return container, warnings, nil

    }

    最终调用的是daemon.Create(config, hostConfig, name)方法,在同一个文件中:

    func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.HostConfig, name string) (retC *Container, retS []string, retErr error) {

        var (

            container *Container

            warnings  []string

            img       *image.Image

            imgID     string

            err       error

        )

        if config.Image != "" {

            img, err = daemon.repositories.LookupImage(config.Image)

            if err != nil {

                return nil, nil, err

            }

            if err = daemon.graph.CheckDepth(img); err != nil {

                return nil, nil, err

            }

            imgID = img.ID

        }

        if err := daemon.mergeAndVerifyConfig(config, img); err != nil {

            return nil, nil, err

        }

        if hostConfig == nil {

            hostConfig = &runconfig.HostConfig{}

        }

        if hostConfig.SecurityOpt == nil {

            hostConfig.SecurityOpt, err = daemon.GenerateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode)

            if err != nil {

                return nil, nil, err

            }

        }

        if container, err = daemon.newContainer(name, config, imgID); err != nil {

            return nil, nil, err

        }

        defer func() {

            if retErr != nil {

                if err := daemon.rm(container, false); err != nil {

                    logrus.Errorf("Clean up Error! Cannot destroy container %s: %v", container.ID, err)

                }

            }

        }()

        if err := daemon.Register(container); err != nil {

            return nil, nil, err

        }

        if err := daemon.createRootfs(container); err != nil {

            return nil, nil, err

        }

        if err := daemon.setHostConfig(container, hostConfig); err != nil {

            return nil, nil, err

        }

        if err := container.Mount(); err != nil {

            return nil, nil, err

        }

        defer container.Unmount()

        if err := createContainerPlatformSpecificSettings(container, config, img); err != nil {

            return nil, nil, err

        }

        if err := container.ToDisk(); err != nil {

            logrus.Errorf("Error saving new container to disk: %v", err)

            return nil, nil, err

        }

        container.LogEvent("create")

        return container, warnings, nil

    }

    一开始是一些获取imgID,校验镜像层数(镜像得层数不能过大,有MaxImageDepth参数用来控制,默认取值127),校验config参数等操作;

    daemon.newContainer(name, config, imgID)是来实例化一个新的container;接下来的一些操作就是为启动一个容器所做的操作:

    首先是daemon.Register(container):(daemon/daemon.go)

    在daemon中注册container,通过daemon.containers 结构,使得daemon可以通过map[containerId]*Container的结构来记录container;还有为container中自带的mountPoint建立volume;

    然后是daemon.createRootfs(container) 

    func (daemon *Daemon) createRootfs(container *Container) error {

        // Step 1: create the container directory.

        // This doubles as a barrier to avoid race conditions.

        if err := os.Mkdir(container.root, 0700); err != nil {

            return err

        }

        initID := fmt.Sprintf("%s-init", container.ID)

        if err := daemon.driver.Create(initID, container.ImageID); err != nil {

            return err

        }

        initPath, err := daemon.driver.Get(initID, "")

        if err != nil {

            return err

        }

        if err := setupInitLayer(initPath); err != nil {

            daemon.driver.Put(initID)

            return err

        }

        // We want to unmount init layer before we take snapshot of it

        // for the actual container.

        daemon.driver.Put(initID)

        if err := daemon.driver.Create(container.ID, initID); err != nil {

            return err

        }

        return nil

    }

    建立container.root文件夹,路径是/var/lib/docker/containers/containerId;

    创建InitID:容器ID-init

    根据InitID和ImageID(此时imageID是InitID的父层)调用aufs.go的Create函数(在上一篇中分析过),在/var/lib/docker/aufs/mnt,/var/lib/docker/aufs/layers,/var/lib/docker/aufs/diff下建立InitID为名称的子文件夹,然后将ImageID和ImageID的所有父亲ImageID写入到layers中;init层是docker在所有镜像之上建立的一层:主要作用是存放一些关于容器的配置信息;

    initPath, err := daemon.driver.Get(initID, "") 同样是调用aufs.go中的Get函数;这个函数比较重要,Get函数将所有祖先镜像的数据都挂在到/var/lib/docker/aufs/mnt/initID目录下,所有祖先镜像的实际数据分别在 /var/lib/docker/aufs/diff/imgID目录下;返回的initPath就是/var/lib/docker/aufs/mnt/initID。

    setupInitLayer(initPath);创建初始化层,就是创建一个容器需要的基本目录和文件,包括的内容有:

    "/dev/pts":         "dir",

    "/dev/shm":         "dir",

    "/proc":            "dir",

    "/sys":             "dir",

    "/.dockerinit":     "file",

    "/.dockerenv":      "file",

    "/etc/resolv.conf": "file",

    "/etc/hosts":       "file",

    "/etc/hostname":    "file",

    "/dev/console":     "file",

    "/etc/mtab":        "/proc/mounts"

    接着 在初始层之上建立容器ID层,同时通过Driver的Put函数减少初始层的引用次数,但此时容器ID层并没有进行mount操作;

    daemon.driver.Put(initID)

    daemon.driver.Create(container.ID, initID);

    到此createRootfs操作结束;

    回到Create函数接下来是setHostConfig()(daemon/daemon.go)

    func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error {

        container.Lock()

        if err := parseSecurityOpt(container, hostConfig); err != nil {

            container.Unlock()

            return err

        }

        container.Unlock()

        // Do not lock while creating volumes since this could be calling out to external plugins

        // Don't want to block other actions, like `docker ps` because we're waiting on an external plugin

        if err := daemon.registerMountPoints(container, hostConfig); err != nil {

            return err

        }

        container.Lock()

        defer container.Unlock()

        // Register any links from the host config before starting the container

        if err := daemon.RegisterLinks(container, hostConfig); err != nil {

            return err

        }

        container.hostConfig = hostConfig

        container.toDisk()

        return nil

    }

    parseSecurityOpt(container, hostConfig); 关于安全的一些参数得解析;

    daemon.registerMountPoints(container, hostConfig); 注册所有挂载到容器的数据卷,主要有三种方式和来源:

    (1)容器本身原有自带的挂载的数据卷,应该是容器的json镜像文件中 "Volumes"这个key对应得内容;

    (2)通过其他数据卷容器(通过--volumes-from)挂载的数据卷;

    (3)通过命令行参数(-v参数)挂载的与主机绑定的数据卷,与主机绑定得数据卷在docker中叫做bind-mounts,这种数据卷与一般的正常得数据卷是有些细微区别的;

    daemon.RegisterLinks(container, hostConfig) (daemon/daemon_unix.go)  注册互联的容器,容器之间除了可以通过 ip:端口 相互访问,容器之间还可以互联(通过--link 容器名字 的方式),例如一个web容器可以通过这种方式与一个数据库容器互联;互联的容器之间可以相互访问,可以通过环境变量和/etc/hosts 来公开连接信息。容器之间的互联关系要注册到sqlite3数据库中,也就是daemon.containerGraph中,deamon.containerGraph是一个graphdb.Database类型,这个小型的数据库默认的实现方式是sqllite,里面存储这容器之间的互联关系; 

    然后继续回到daemon.Create()函数,在setHostConfig()之后:

    container.Mount()

    因为上面提到了,在创建initLayer的时候,虽然建立了containerID的有关文件夹,但是并没有进行对containerID及其父亲镜像进行挂载操作,这里就是将containerID及其父镜像进行挂载操作;

    createContainerPlatformSpecificSettings(container, config, img) 

    这个函数的是处理config中得Volumes的,通过-v参数挂载的数据卷,但不是bind-volumes,bind-volumes是放置在hostConfig中的。config与hostConfig都是配置的结构,区别是config是只与container相关的配置,hostConfig属于与宿主机相关的配置选项;

    回到daemon.Create()函数,接下来是container.ToDisk()

    func (container *Container) toDisk() error {

        data, err := json.Marshal(container)

        if err != nil {

            return err

        }

        pth, err := container.jsonPath()

        if err != nil {

            return err

        }

        if err := ioutil.WriteFile(pth, data, 0666); err != nil {

            return err

        }

        return container.WriteHostConfig()

    }

    这段代码就是将container中的config和hostconfig结构体存储到磁盘上,存储的路径是/var/lib/docker/container/containerId/config.json 和 /var/lib/docker/container/containerId/hostConfig.json

    到目前为止,一个container需要的文件系统视角已经完全建立起来,但这个container还没有启动,还没有运行container里面的命令,下一篇的内容讨论container是怎样运行起来的。

  • 相关阅读:
    CentOS /RHEL系统怎么更新安全补丁
    使用yum查询系统安装的软件及可以更新的软件并单独指定升级某一个软件
    惠普服务器通过ILO系统远程安装系统
    django进行数据库迁移的错误处理方法:You are trying to change the nullable field 'title' on book to non-nullable without a d
    Django数据库增删改查
    linux用户
    linux基础
    mysql小白系列_01 原理
    mysql运维入门6:MySQL读写分离
    mysql运维入门5:MySQL+kepalived高可用架构
  • 原文地址:https://www.cnblogs.com/yuhan-TB/p/5129575.html
Copyright © 2020-2023  润新知