前段時間一直忙些其餘事情,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的祕密;