docker 源碼分析 四(基於1.8.2版本),Docker鏡像的獲取和存儲

前段時間一直忙些其餘事情,docker源碼分析的事情耽擱了,今天接着寫,上一章瞭解了docker client 和 docker daemon(會啓動一個http server)是C/S的結構,client端發出的命令由docker daemon接收並處理。docker

咱們在運行docker的時候,可能會使用到docker run命令(固然經過Dockerfile運行docker build命令也是同樣的)時,若是本地沒有你須要的鏡像,docker daemon首先會去下載你須要的docker鏡像,而後存儲在本地;另外docker 鏡像實際上是一個很神奇的東西,它有多個層(layer)構成,每個層的上一層是本層的父親層(parent layer)。最上層(top layer)是可讀可寫層,用戶對鏡像的更新在這一層起做用,top layer之下的層都是隻讀層;這種實現方式其實也是一種文件系統,UnionFS。json

本文就從上一章的結尾,分析一下docker pull 命令的實現,就是docker 怎樣下載鏡像並怎樣存儲的;ubuntu

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

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

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

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

    addTrustedFlags(cmd, true)session

    cmd.Require(flag.Exact, 1)數據結構

 

    cmd.ParseFlags(args, true)app

    remote := cmd.Arg(0)

 

    taglessRemote, tag := parsers.ParseRepositoryTag(remote)  

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

        tag = tags.DefaultTag

        fmt.Fprintf(cli.out, "Using default tag: %s\n", 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的祕密;

相關文章
相關標籤/搜索