• docker 源码分析 四(基于1.8.2版本),Docker镜像的获取和存储


    前段时间一直忙些其他事情,docker源码分析的事情耽搁了,今天接着写,上一章了解了docker client 和 docker daemon(会启动一个http server)是C/S的结构,client端发出的命令由docker daemon接收并处理。

    我们在运行docker的时候,可能会使用到docker run命令(当然通过Dockerfile运行docker build命令也是一样的)时,如果本地没有你需要的镜像,docker daemon首先会去下载你需要的docker镜像,然后存储在本地;另外docker 镜像其实是一个很神奇的东西,它有多个层(layer)构成,每一个层的上一层是本层的父亲层(parent layer)。最上层(top layer)是可读可写层,用户对镜像的更新在这一层起作用,top layer之下的层都是只读层;这种实现方式其实也是一种文件系统,UnionFS。

    本文就从上一章的结尾,分析一下docker pull 命令的实现,就是docker 怎样下载镜像并怎样存储的;

    docker run的客户端命令所在文件是api/client/pull.go 下:

    func (cli *DockerCli) CmdPull(args ...string) error {

        cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true)

        allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")

        addTrustedFlags(cmd, true)

        cmd.Require(flag.Exact, 1)

        cmd.ParseFlags(args, true)

        remote := cmd.Arg(0)

        taglessRemote, tag := parsers.ParseRepositoryTag(remote)  

        if tag == "" && !*allTags {

            tag = tags.DefaultTag

            fmt.Fprintf(cli.out, "Using default tag: %s ", tag)

        } else if tag != "" && *allTags {

            return fmt.Errorf("tag can't be used with --all-tags/-a")

        }

        ref := registry.ParseReference(tag)

        // Resolve the Repository name from fqn to RepositoryInfo

        repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)

        if err != nil {

            return err

        }

        if isTrusted() && !ref.HasDigest() {

            // Check if tag is digest

            authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)

            return cli.trustedPull(repoInfo, ref, authConfig)

        }

        v := url.Values{}

        v.Set("fromImage", ref.ImageName(taglessRemote))

        _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")

        return err

    }

    ParseRepositoryTag(pkg/parsers/parsers.go)的作用就是从输入的pull 后面的字符串中提取出tag名字和剩下的部分,叫taglessRemote,

    举个例子:

    (a) api.example.com/ubuntu:10.04 会被拆分成 api.example.com/ubuntu 和 10.04 两个部分;

    (b) ubuntu:10.04 会被拆分成 ubunu 和 10.04 两个部分;

    (c) api.example.com@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb 还有一种digest(我理解就是对镜像生成的摘要)的形式,sha256是生成digest的方法;

    这种包含@ 的形式会 分割成@左右两个部分,就是api.example.com 和 sha256:xxx...

    如果分离出的tag为空 并且 alltags flag的值也为空的话(两者不能同时不为空),那么tag就会取默认值,默认值是latest;

    ref := registry.ParseReference(tag) (registry/reference.go)的作用就是将分离出的tag 转成成内部的tagReference或者digestReference的形式;

    repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) (registry/config.go)的作用就是将taglessRemote转成RepositoryInfo的struct;

    RepositoryInfo (registry/types.go)的结构如下,是用来描述一个镜像除了tag之外的部分,可能包括url路径:

    type RepositoryInfo struct {

        Index *IndexInfo             //registry 信息 

        RemoteName string         //"library/ubuntu-12.04-base"

        LocalName string             //"ubuntu-12.04-base"

        CanonicalName string      //"docker.io/library/ubuntu-12.04-base"

        Official bool                    //像ubuntu的名字就是true,像xxx/ubuntu这种名字就是false;

    }

    这三种name之间的区别就如代码的注释中的一样,Index 也是一个表示registry信息的struct (registry/types.go),里面主要包括的name(registry的名字,例如官方docker.io),mirrors表示这个registry的镜像,表现为就是一个url的list;

    源码中有几个repositoryInfo的例子也许能更直观点:

    // RepositoryInfo Examples:

    // {

    //   "Index" : {

    //     "Name" : "docker.io",

    //     "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"],

    //     "Secure" : true,

    //     "Official" : true,

    //   },

    //   "RemoteName" : "library/debian",

    //   "LocalName" : "debian",

    //   "CanonicalName" : "docker.io/debian"

    //   "Official" : true,

    // }

    //

    // {

    //   "Index" : {

    //     "Name" : "127.0.0.1:5000",

    //     "Mirrors" : [],

    //     "Secure" : false,

    //     "Official" : false,

    //   },

    //   "RemoteName" : "user/repo",

    //   "LocalName" : "127.0.0.1:5000/user/repo",

    //   "CanonicalName" : "127.0.0.1:5000/user/repo",

    //   "Official" : false,

    // }

     如果稍后docker daemon要访问的registry 需要验证,则通过 repo.Index 和 cli.configFile (api/client/cli.go) 取出对应registry的认证信息 authConfig,autoConfig在cliconfig/config.go文件中:

    type AuthConfig struct {

        Username      string `json:"username,omitempty"`

        Password      string `json:"password,omitempty"`

        Auth          string `json:"auth"`

        Email         string `json:"email"`

        ServerAddress string `json:"serveraddress,omitempty"`

    }

    接着调用trustedPull (api/client/trust.go)方法,最终trustPull方法也会通过restful API来调用 

    _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") 方法来将pull image的请求发送给docker server进行处理;

    本系列文章的前两章中有介绍,docker server对应pull请求的handler是postImagesCreate (api/server/image.go)。

    // Creates an image from Pull or from Import

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

       .......

    if image != "" { //pull

            if tag == "" {

                image, tag = parsers.ParseRepositoryTag(image)

            }

            metaHeaders := map[string][]string{}

            for k, v := range r.Header {

                if strings.HasPrefix(k, "X-Meta-") {

                    metaHeaders[k] = v

                }

            }

            imagePullConfig := &graph.ImagePullConfig{

                MetaHeaders: metaHeaders,

                AuthConfig:  authConfig,

                OutStream:   output,

            }

            err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)

        } else { //import

            if tag == "" {

                repo, tag = parsers.ParseRepositoryTag(repo)

            }

            src := r.Form.Get("fromSrc")

            // 'err' MUST NOT be defined within this block, we need any error

            // generated from the download to be available to the output

            // stream processing below

            var newConfig *runconfig.Config

            newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"])

            if err != nil {

                return err

            }

            err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig)

        }

        if err != nil {

            if !output.Flushed() {

                return err

            }

            sf := streamformatter.NewJSONStreamFormatter()

            output.Write(sf.FormatError(err))

        }

        return nil

    }

    postImagesCreate函数只截取重要的部分,这里省略号的部分主要是从http request中提取出image名称等参数,当image不为空的时候,由于docker server也需要与 docker registry 通过http交互来下载docker网络镜像,所以首先封装了 imagePullConfig 参数,在与registry通信的时候使用。接下来调用

    err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)

    s.daemon.Repositories() (daemon/daemon.go) 是*graph.TagStore (graph/tags.go)类型, TagStore是一个比较重要的类型: 它保存着Graph用来完成对镜像的存储,管理着各种repository,同时pullingPool 和 pushingPool 保证同一个时间段只能有一个相同的镜像被下载和上传;

    type TagStore struct {

        path  string

        graph *Graph

        // Repositories is a map of repositories, indexed by name.

        Repositories map[string]Repository

        trustKey     libtrust.PrivateKey

        sync.Mutex

        // FIXME: move push/pull-related fields

        // to a helper type

        pullingPool     map[string]chan struct{}

        pushingPool     map[string]chan struct{}

        registryService *registry.Service

        eventsService   *events.Events

        trustService    *trust.Store

    }

    接着是 TagStore的Pull()方法,

    func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error { 

    ...... 

        for _, endpoint := range endpoints {

            logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version)

            if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {

                if repoInfo.Official {

                    s.trustService.UpdateBase()

                }

            }

            puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)

            if err != nil {

                lastErr = err

                continue

            }

            if fallback, err := puller.Pull(tag); err != nil {

                if fallback {

                    if _, ok := err.(registry.ErrNoSupport); !ok {

                        // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.

                        discardNoSupportErrors = true

                        // save the current error

                        lastErr = err

                    } else if !discardNoSupportErrors {

                        // Save the ErrNoSupport error, because it's either the first error or all encountered errors

                        // were also ErrNoSupport errors.

                        lastErr = err

                 }

                    continue

                }

                logrus.Debugf("Not continuing with error: %v", err)

                return err

            }

            s.eventsService.Log("pull", logName, "")

            return nil

        }

    ...... 

    }

    还是截取主要的部分,函数最开始的部分主要是通过TagStore中的registryService,通过传入的image找到repositoryInfo,然后通过repositoryInfo找到下载镜像的endpoint;

    接下来针对每一个endpoint,建立一个Puller:puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf) 开始拉取镜像;sf就是个jsonformatter;

    NewPuller会根据endpoint的形式(endpoint应该遵循restful api的设计,url中含有版本号),决定采用version1还是version2版本,我主要分析v2的版本,在graph/pull_v2.go中:

    func (p *v2Puller) Pull(tag string) (fallback bool, err error) {

        // TODO(tiborvass): was ReceiveTimeout

        p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig)

        if err != nil {

            logrus.Debugf("Error getting v2 registry: %v", err)

            return true, err

        }

        p.sessionID = stringid.GenerateRandomID()

        if err := p.pullV2Repository(tag); err != nil {

            if registry.ContinueOnError(err) {

                logrus.Debugf("Error trying v2 registry: %v", err)

                return true, err

            }

            return false, err

        }

        return false, nil

    }

    最主要的函数是pullV2Repository()函数,同样在graph/pull_v2.go目录下:

    func (p *v2Puller) pullV2Repository(tag string) (err error) {

        var tags []string

        taggedName := p.repoInfo.LocalName

        if len(tag) > 0 {

            tags = []string{tag}

            taggedName = utils.ImageReference(p.repoInfo.LocalName, tag)

        } else {

            var err error

            manSvc, err := p.repo.Manifests(context.Background())

            if err != nil {

                return err

            }

            tags, err = manSvc.Tags()

            if err != nil {

                return err

            }

        }

        c, err := p.poolAdd("pull", taggedName)

        if err != nil {

            if c != nil {

                // Another pull of the same repository is already taking place; just wait for it to finish

                p.config.OutStream.Write(p.sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", p.repoInfo.CanonicalName))

                <-c

                return nil

            }

            return err

        }

        defer p.poolRemove("pull", taggedName)

        var layersDownloaded bool

        for _, tag := range tags {

            // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged

            // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus?

            pulledNew, err := p.pullV2Tag(tag, taggedName)

            if err != nil {

                return err

            }

            layersDownloaded = layersDownloaded || pulledNew

        }

        writeStatus(taggedName, p.config.OutStream, p.sf, layersDownloaded)

        return nil

    }

    首先获得tagName,通过manifest获得tags清单,一个repository可能对应着多个tag,docker的镜像呈现的是树形关系,比如ubuntu是一个repository,实际的存储情况是可能会有一个基础镜像base,这个基础镜像上,增加一些新的内容(实际上就是增加一个新的读写层,写入东西进去)就会形成新的镜像,比如:ubuntu:12.12是一个镜像,那么ubuntu:14.01是在前者基础上进行若干修改操作而形成的新的镜像;所以要下载ubuntu:14.01这个镜像的话,必须要将其父镜像完全下载下来,这样下载之后的镜像才是完整的;

    看一下 c, err := p.poolAdd("pull", taggedName)  (graph/tags.go文件)这个函数:

    func (store *TagStore) poolAdd(kind, key string) (chan struct{}, error) {

        store.Lock()

        defer store.Unlock()

        if c, exists := store.pullingPool[key]; exists {

            return c, fmt.Errorf("pull %s is already in progress", key)

        }

        if c, exists := store.pushingPool[key]; exists {

            return c, fmt.Errorf("push %s is already in progress", key)

        }

        c := make(chan struct{})

        switch kind {

        case "pull":

            store.pullingPool[key] = c

        case "push":

            store.pushingPool[key] = c

        default:

            return nil, fmt.Errorf("Unknown pool type")

        }

        return c, nil

    }

    这个tagStore的函数之前提到过,就是保证同一时刻,只能有一个tag在上传或者下载;当下载完成后,会调用 defer p.poolRemove("pull", taggedName) 将这个限制打开;接下来就是实际下载的函数 pullV2Tag 了,是一段很长的代码:

    func (p *v2Puller) pullV2Tag(tag, taggedName string) (verified bool, err error) {

        logrus.Debugf("Pulling tag from V2 registry: %q", tag)

        //

        out := p.config.OutStream

        manSvc, err := p.repo.Manifests(context.Background())

        if err != nil {

            return false, err

        }

        manifest, err := manSvc.GetByTag(tag)

        if err != nil {

            return false, err

        }

        verified, err = p.validateManifest(manifest, tag)

        if err != nil {

            return false, err

        }

        if verified {

            logrus.Printf("Image manifest for %s has been verified", taggedName)

        }

        pipeReader, pipeWriter := io.Pipe()

        go func() {

            if _, err := io.Copy(out, pipeReader); err != nil {

                logrus.Errorf("error copying from layer download progress reader: %s", err)

                if err := pipeReader.CloseWithError(err); err != nil {

                    logrus.Errorf("error closing the progress reader: %s", err)

                }

            }

        }()

        defer func() {

            if err != nil {

                // All operations on the pipe are synchronous. This call will wait

                // until all current readers/writers are done using the pipe then

                // set the error. All successive reads/writes will return with this

                // error.

                pipeWriter.CloseWithError(errors.New("download canceled"))

            }

        }()

        out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name()))

        var downloads []*downloadInfo

        var layerIDs []string

        defer func() {

            p.graph.Release(p.sessionID, layerIDs...)

        }()

        for i := len(manifest.FSLayers) - 1; i >= 0; i-- {

            img, err := image.NewImgJSON([]byte(manifest.History[i].V1Compatibility))

            if err != nil {

                logrus.Debugf("error getting image v1 json: %v", err)

                return false, err

            }

            p.graph.Retain(p.sessionID, img.ID)

            layerIDs = append(layerIDs, img.ID)

            // Check if exists

            if p.graph.Exists(img.ID) {

                logrus.Debugf("Image already exists: %s", img.ID)

                out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Already exists", nil))

                continue

            }

            out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil))

            d := &downloadInfo{

                img:    img,

                digest: manifest.FSLayers[i].BlobSum,

                // TODO: seems like this chan buffer solved hanging problem in go1.5,

                // this can indicate some deeper problem that somehow we never take

                // error from channel in loop below

                err: make(chan error, 1),

                out: pipeWriter,

            }

            downloads = append(downloads, d)

            go p.download(d)

        }

        // run clean for all downloads to prevent leftovers

        for _, d := range downloads {

            defer func(d *downloadInfo) {

                if d.tmpFile != nil {

                    d.tmpFile.Close()

                    if err := os.RemoveAll(d.tmpFile.Name()); err != nil {

                        logrus.Errorf("Failed to remove temp file: %s", d.tmpFile.Name())

                    }

                }

            }(d)

        }

        var tagUpdated bool

        for _, d := range downloads {

            if err := <-d.err; err != nil {

                return false, err

            }

            if d.layer == nil {

                continue

            }

            // if tmpFile is empty assume download and extracted elsewhere

            d.tmpFile.Seek(0, 0)

            reader := progressreader.New(progressreader.Config{

                In:        d.tmpFile,

                Out:       out,

                Formatter: p.sf,

                Size:      d.size,

                NewLines:  false,

                ID:        stringid.TruncateID(d.img.ID),

                Action:    "Extracting",

            })

            err = p.graph.Register(d.img, reader)

            if err != nil {

                return false, err

            }

            // FIXME: Pool release here for parallel tag pull (ensures any downloads block until fully extracted)

            out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.ID), "Pull complete", nil))

            tagUpdated = true

        }

        manifestDigest, _, err := digestFromManifest(manifest, p.repoInfo.LocalName)

        if err != nil {

            return false, err

        }

        // Check for new tag if no layers downloaded

        if !tagUpdated {

            repo, err := p.Get(p.repoInfo.LocalName)

            if err != nil {

                return false, err

            }

            if repo != nil {

                if _, exists := repo[tag]; !exists {

                    tagUpdated = true

                }

            } else {

                tagUpdated = true

            }

        }

        if verified && tagUpdated {

            out.Write(p.sf.FormatStatus(p.repo.Name()+":"+tag, "The image you are pulling has been verified. Important: image verification is a tech preview feature and should  not be relied on to provide security."))

        }

        firstID := layerIDs[len(layerIDs)-1]

        if utils.DigestReference(tag) {

            // TODO(stevvooe): Ideally, we should always set the digest so we can

            // use the digest whether we pull by it or not. Unfortunately, the tag

            // store treats the digest as a separate tag, meaning there may be an

            // untagged digest image that would seem to be dangling by a user.

            if err = p.SetDigest(p.repoInfo.LocalName, tag, firstID); err != nil {

                return false, err

            }

        } else {

            // only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)

            if err = p.Tag(p.repoInfo.LocalName, tag, firstID, true); err != nil {

                return false, err

            }

        }

        if manifestDigest != "" {

            out.Write(p.sf.FormatStatus("", "Digest: %s", manifestDigest))

        }

        return tagUpdated, nil

    }

    讲重点部分,先从manifest中获取(这个过程也是通过http去endpoint那里获取)出这个tag对应的所有image,我理解就是image和其所有父镜像,然后for循环进行遍历:

     for i := len(manifest.FSLayers) - 1; i >= 0; i-- {

            img, err := image.NewImgJSON([]byte(manifest.History[i].V1Compatibility))

            if err != nil {

                logrus.Debugf("error getting image v1 json: %v", err)

                return false, err

            }

            p.graph.Retain(p.sessionID, img.ID)

            layerIDs = append(layerIDs, img.ID)

    ......

    另外附上Manifest的结构:

    type Manifest struct {

        Versioned

        // Name is the name of the image's repository

        Name string `json:"name"`

        // Tag is the tag of the image specified by this manifest

        Tag string `json:"tag"`

        // Architecture is the host architecture on which this image is intended to

        // run

        Architecture string `json:"architecture"`

        // FSLayers is a list of filesystem layer blobSums contained in this image

        FSLayers []FSLayer `json:"fsLayers"`

        // History is a list of unstructured historical data for v1 compatibility

        History []History `json:"history"`

    }

    docker中的每一个镜像,由两部分组成,一部分是镜像的实际的数据内容,另一部分是镜像对应的json文件,json文件中有镜像的ID,同时json数据的"config"这个key中还会记录这个镜像的一些动态信息,例如:设置的环境变量,运行的命令等等。image的信息就是在image/image.go中。

    接着会调用p.graph (graph/graph.go),graph维持着不同版本的镜像文件和他们之间的关系,这里面的driver默认是aufs.go   (daemon/graphdriver/aufs/aufs.go)

    type Graph struct {

        root             string

        idIndex          *truncindex.TruncIndex

        driver           graphdriver.Driver

        imageMutex       imageMutex // protect images in driver.

        retained         *retainedLayers

        tarSplitDisabled bool

    下载之前先将

    p.graph.Retain(p.sessionID, img.ID)

    将sessionID 和 img.ID加入到 graph的数据结构

    type retainedLayers struct {

        layerHolders map[string]map[string]struct{} // map[layerID]map[sessionID]

        sync.Mutex

    }

    这个结构维护着哪些imageId已经被下载过;如果if p.graph.Exists(img.ID)  为true,说明这个镜像被下载过,直接continue,否则将这个镜像加入下载的downloadInfo里面去去;然后  go p.download(d) 开始下载镜像,下载镜像的过程首先根据之前说到的TagStore判断是不是有同样的镜像在下载过程中,如果没有调用ioutil.TempFile()将镜像内容下载到临时文件;函数结束后,会defer的函数对tempfile进行清理;

    最后一个主要步骤是在graph注册image的id和内容;

     err = p.graph.Register(d.img, reader)   (graph/graph.go)

    func (graph *Graph) Register(img *image.Image, layerData io.Reader) (err error) {

        if err := image.ValidateID(img.ID); err != nil {

            return err

        }

        graph.imageMutex.Lock(img.ID)

        defer graph.imageMutex.Unlock(img.ID)

        // Skip register if image is already registered

        if graph.Exists(img.ID) {

            return nil

        }

        defer func() {

            // If any error occurs, remove the new dir from the driver.

            // Don't check for errors since the dir might not have been created.

            if err != nil {

                graph.driver.Remove(img.ID)

            }

        }()

        if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {

            return err

        }

        graph.driver.Remove(img.ID)

        tmp, err := graph.mktemp("")

        defer os.RemoveAll(tmp)

        if err != nil {

            return fmt.Errorf("mktemp failed: %s", err)

        }

        // Create root filesystem in the driver

        if err := createRootFilesystemInDriver(graph, img, layerData); err != nil {

            return err

        }

        // Apply the diff/layer

        if err := graph.storeImage(img, layerData, tmp); err != nil {

            return err

        }

        // Commit

        if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {

            return err

        }

        graph.idIndex.Add(img.ID)

        return nil

    }

    首先是验证graph是否已经注册过image,如果已经注册过image,那么直接返回nil 退出;接着删除已有的路径,稍后会说,docker存储镜像的时候会新建几个目录,

    graph.imageRoot(img.ID) 这个目录是 /var/lib/docker/graph/imageID, 这个路径下每一个文件夹名称是一个imageID,在docker daemon 初始化的时候,会生成新的graph 实例,graph实例会通过restore()方法(graph/graph.go)根据目录下的内容来加载已有的镜像;

     graph.driver.Remove(img.ID) graph包含driver,这里用aufs举例,文件存储在/var/lib/docker/aufs目录下,这个目录下会有三个文件夹 mnt, layers, diff。每一个目录下都会有一个以镜像ID为名称的文件,mnt下面存放的是以这个镜像为可读写层的挂载点;layers存储这以这个镜像的所有的祖先镜像的ID列表,diff存储这个镜像的实际的文件系统中的内容;

    在删除了可能残留的目录后,开始建立新的目录, createRootFilesystemInDriver(graph, img, layerData),调用driver的Create函数(daemon/graphdriver/aufs/aufs.go),

    func (a *Driver) Create(id, parent string) error {

        if err := a.createDirsFor(id); err != nil {

            return err

        }

        // Write the layers metadata

        f, err := os.Create(path.Join(a.rootPath(), "layers", id))

        if err != nil {

            return err

        }

        defer f.Close()

        if parent != "" {

            ids, err := getParentIds(a.rootPath(), parent)

            if err != nil {

                return err

            }

            if _, err := fmt.Fprintln(f, parent); err != nil {

                return err

            }

            for _, i := range ids {

                if _, err := fmt.Fprintln(f, i); err != nil {

                    return err

                }

            }

        }

        return nil

    }

    通过createDirsFor创建mnt/imageID和diff/imageID两个文件夹,然后建立layers/imageID的文件,然后将parentID和parent的祖先ID列表写入到这个文件;

    接下来对实际的镜像的实际内容进行存储,graph.storeImage(img, layerData, tmp),storeImage函数(graph/graph.go): 

    func (graph *Graph) storeImage(img *image.Image, layerData io.Reader, root string) (err error) {

        // Store the layer. If layerData is not nil, unpack it into the new layer

        if layerData != nil {

            if err := graph.disassembleAndApplyTarLayer(img, layerData, root); err != nil {

                return err

            }

        }

        if err := graph.saveSize(root, img.Size); err != nil {

            return err

        }

        f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))

        if err != nil {

            return err

        }

        defer f.Close()

        return json.NewEncoder(f).Encode(img)

    }

    disassembleAndApplyTarLayer将下载下来的img解压到/var/lib/docker/aufs/diff/imageID中,接下来将镜像的大小也存储为一个文件,存储的地点是通过这句函数tmp, err := graph.mktemp("")建立的临时目录/var/lib/docker/graph/tmp_xxxxx中,

    文件名是layersize,接下来存储image的json数据,存储的位置也是在临时目录中的文件名为json的文件;

    再这些数据都存储完之后,调用os.Rename(tmp, graph.imageRoot(img.ID)) 将之前的临时目录/var/lib/docker/graph/tmp_xxxxx 改成 /var/lib/docker/graph/imageID

    Register函数的最后一步是 graph.idIndex.Add(img.ID) ,将ID加入idIndex,idIndex是一个trie结构,为了方便用户根据镜像的前缀来方便的查找镜像;

    docker的镜像pull就先写到这儿,下一篇趁热打铁,分析一个docker run的秘密;

  • 相关阅读:
    【SQLServer 】SQLServer Alwayson环境在主库节点移除数据库
    Docker安装PostgreSQL
    基于海康监控的图像识别设计
    光线补偿算法的实现
    基于SURF特征的图像与视频拼接技术的研究和实现(一)
    opencv透视变换
    反人脸识别的思路和实现
    自动对比度的opencv实现
    单向信息传输系统设计实现
    集成利用tesseract.exe进行ocr
  • 原文地址:https://www.cnblogs.com/yuhan-TB/p/5053370.html
Copyright © 2020-2023  润新知