docker鏡像導(dǎo)入的實現(xiàn)方法
鏡像導(dǎo)入是由image/tarexport/load.go#tarexporter.Load()完成的
以下代碼參考github.com/docker/docker版本v0.0.0-20181129155816-baab736a3649
主要是注冊鏡像信息以及解包鏡像tar流到新root
導(dǎo)出和保存的區(qū)別在于
- 導(dǎo)出(export): 僅導(dǎo)出文件結(jié)構(gòu)
- 保存(save): 保存鏡像歷史和元數(shù)據(jù)
這意味著導(dǎo)出將不會包含USER、EXPOSE等Dockerfile里面的命令,也就無法轉(zhuǎn)移鏡像到另一臺機器上了
func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) error { var progressOutput progress.Output if !quiet { progressOutput = streamformatter.NewJSONProgressOutput(outStream, false) } outStream = streamformatter.NewStdoutWriter(outStream) // 1. 創(chuàng)建docker-import的臨時目錄 tmpDir, err := ioutil.TempDir("", "docker-import-") if err != nil { return err } defer os.RemoveAll(tmpDir) // 2. 解包tar流到臨時目錄 if err := chrootarchive.Untar(inTar, tmpDir, nil); err != nil { return err } // 3. 打開manifest文件,并解析 manifestPath, err := safePath(tmpDir, manifestFileName) if err != nil { return err } manifestFile, err := os.Open(manifestPath) if err != nil { if os.IsNotExist(err) { return l.legacyLoad(tmpDir, outStream, progressOutput) } return err } defer manifestFile.Close() var manifest []manifestItem if err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil { return err } var parentLinks []parentLink var imageIDsStr string var imageRefCount int // 4. 從manifest中讀取并解析到image for _, m := range manifest { configPath, err := safePath(tmpDir, m.Config) if err != nil { return err } config, err := ioutil.ReadFile(configPath) if err != nil { return err } img, err := image.NewFromJSON(config) if err != nil { return err } if err := checkCompatibleOS(img.OS); err != nil { return err } rootFS := *img.RootFS rootFS.DiffIDs = nil // 若image rootFS diffID數(shù)量與manifest中記錄的層數(shù)不一致,則報錯 if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual { return fmt.Errorf("invalid manifest, layers length mismatch: expected %d, got %d", expected, actual) } // On Windows, validate the platform, defaulting to windows if not present. os := img.OS if os == "" { os = runtime.GOOS } if runtime.GOOS == "windows" { if (os != "windows") && (os != "linux") { return fmt.Errorf("configuration for this image has an unsupported operating system: %s", os) } } // 5. 注冊層 for i, diffID := range img.RootFS.DiffIDs { layerPath, err := safePath(tmpDir, m.Layers[i]) if err != nil { return err } r := rootFS r.Append(diffID) newLayer, err := l.lss[os].Get(r.ChainID()) if err != nil { // 如果沒有注冊,那就注冊layer newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), os, m.LayerSources[diffID], progressOutput) if err != nil { return err } } defer layer.ReleaseAndLog(l.lss[os], newLayer) // 若manifest與緩存中l(wèi)ayer diffID不一致,則報錯 if expected, actual := diffID, newLayer.DiffID(); expected != actual { return fmt.Errorf("invalid diffID for layer %d: expected %q, got %q", i, expected, actual) } rootFS.Append(diffID) } // 6. 緩存該層鏡像配置 imgID, err := l.is.Create(config) if err != nil { return err } imageIDsStr += fmt.Sprintf("Loaded image ID: %s\n", imgID) imageRefCount = 0 for _, repoTag := range m.RepoTags { named, err := reference.ParseNormalizedNamed(repoTag) if err != nil { return err } ref, ok := named.(reference.NamedTagged) if !ok { return fmt.Errorf("invalid tag %q", repoTag) } // 設(shè)置已加載的id、reference l.setLoadedTag(ref, imgID.Digest(), outStream) outStream.Write([]byte(fmt.Sprintf("Loaded image: %s\n", reference.FamiliarString(ref)))) imageRefCount++ } parentLinks = append(parentLinks, parentLink{imgID, m.Parent}) l.loggerImgEvent.LogImageEvent(imgID.String(), imgID.String(), "load") } for _, p := range validatedParentLinks(parentLinks) { if p.parentID != "" { if err := l.setParentID(p.id, p.parentID); err != nil { return err } } } if imageRefCount == 0 { outStream.Write([]byte(imageIDsStr)) } return nil }
Untar
主要過程是將tar流解包到新root
untar操作實際由chrootarchive/archive_unix.go untar()執(zhí)行
// untar is the entry-point for docker-untar on re-exec. This is not used on // Windows as it does not support chroot, hence no point sandboxing through // chroot and rexec. func untar() { runtime.LockOSThread() flag.Parse() var options *archive.TarOptions //read the options from the pipe "ExtraFiles" if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil { fatal(err) } // Linux上的Chroot使用pivot_root,而不是Chroot。 pivot_root需要一個新根和一個舊根。舊根必須是新根的子目錄,它是調(diào)用pivot_root后當(dāng)前rootfs駐留的位置。New root是新rootfs設(shè)置的位置。在調(diào)用pivot_root之后,舊根會被移除,因此在新根下不再可用。這類似于libcontainer設(shè)置容器rootfs的方式 // 在這里是以前面創(chuàng)建的臨時目錄作為新root,并在其下創(chuàng)建privot_root作為老root,最后切換到新root if err := chroot(flag.Arg(0)); err != nil { fatal(err) } // 將tar流解包到新root if err := archive.Unpack(os.Stdin, "/", options); err != nil { fatal(err) } // fully consume stdin in case it is zero padded if _, err := flush(os.Stdin); err != nil { fatal(err) } os.Exit(0) }
loadLayer
注冊鏡像層以及加載層tar流到對應(yīng)目錄下
image/tarexport/load.go#tarexpoter.loadLayer()
func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, os string, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) { // We use system.OpenSequential to use sequential file access on Windows, avoiding // depleting the standby list. On Linux, this equates to a regular os.Open. rawTar, err := system.OpenSequential(filename) if err != nil { logrus.Debugf("Error reading embedded tar: %v", err) return nil, err } defer rawTar.Close() var r io.Reader if progressOutput != nil { fileInfo, err := rawTar.Stat() if err != nil { logrus.Debugf("Error statting file: %v", err) return nil, err } r = progress.NewProgressReader(rawTar, progressOutput, fileInfo.Size(), stringid.TruncateID(id), "Loading layer") } else { r = rawTar } inflatedLayerData, err := archive.DecompressStream(r) if err != nil { return nil, err } defer inflatedLayerData.Close() if ds, ok := l.lss[os].(layer.DescribableStore); ok { return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc) } // 到這里是去注冊層tar流和本層鏡像的chainID return l.lss[os].Register(inflatedLayerData, rootFS.ChainID()) }
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) { // err is used to hold the error which will always trigger // cleanup of creates sources but may not be an error returned // to the caller (already exists). var err error var pid string var p *roLayer // 1. 從緩存中獲取到給定chainID的層信息 if string(parent) != "" { p = ls.get(parent) if p == nil { return nil, ErrLayerDoesNotExist } pid = p.cacheID // Release parent chain if error defer func() { if err != nil { ls.layerL.Lock() ls.releaseLayer(p) ls.layerL.Unlock() } }() if p.depth() >= maxLayerDepth { err = ErrMaxDepthExceeded return nil, err } } // 2. 創(chuàng)建新的只讀層 layer := &roLayer{ parent: p, cacheID: stringid.GenerateRandomID(), referenceCount: 1, layerStore: ls, references: map[Layer]struct{}{}, descriptor: descriptor, } // 3. 準備文件系統(tǒng)(overlay2)文件目錄結(jié)構(gòu) if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil { return nil, err } tx, err := ls.store.StartTransaction() if err != nil { return nil, err } defer func() { if err != nil { logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, err) if err := ls.driver.Remove(layer.cacheID); err != nil { logrus.Errorf("Error cleaning up cache layer %s: %v", layer.cacheID, err) } if err := tx.Cancel(); err != nil { logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err) } } }() // 4. 從給定讀寫層流中提取變化的內(nèi)容到鏡像層掛載點 if err = ls.applyTar(tx, ts, pid, layer); err != nil { return nil, err } // 5. 若本層無父層,那么chainID就是自己的diffID。否則從parent和自己的diffID中生成 if layer.parent == nil { layer.chainID = ChainID(layer.diffID) } else { layer.chainID = createChainIDFromParent(layer.parent.chainID, layer.diffID) } // 6. 儲存層diffID、size、cacheID、descriptor、parent、os等信息 if err = storeLayer(tx, layer); err != nil { return nil, err } ls.layerL.Lock() defer ls.layerL.Unlock() if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil { // Set error for cleanup, but do not return the error err = errors.New("layer already exists") return existingLayer.getReference(), nil } if err = tx.Commit(layer.chainID); err != nil { return nil, err } ls.layerMap[layer.chainID] = layer return layer.getReference(), nil }
driver.Create
為鏡像層創(chuàng)建diff、work、lower目錄,并寫入鏡像層tar流lower內(nèi)容到對應(yīng)lower目錄
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) { dir := d.dir(id) // 1. 獲取當(dāng)前用戶在宿主機對應(yīng)的userID、groupID rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { return err } root := idtools.Identity{UID: rootUID, GID: rootGID} // 2. 為當(dāng)前用戶創(chuàng)建鏡像目錄 if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil { return err } if err := idtools.MkdirAndChown(dir, 0700, root); err != nil { return err } defer func() { // Clean up on failure if retErr != nil { os.RemoveAll(dir) } }() // 3. 解析儲存選項 if opts != nil && len(opts.StorageOpt) > 0 { driver := &Driver{} if err := d.parseStorageOpt(opts.StorageOpt, driver); err != nil { return err } // 4. 設(shè)置儲存配額 if driver.options.quota.Size > 0 { // Set container disk quota limit if err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil { return err } } } // 5. 創(chuàng)建鏡像diff目錄 if err := idtools.MkdirAndChown(path.Join(dir, "diff"), 0755, root); err != nil { return err } // 6. 創(chuàng)建指向diff目錄的鏈接 lid := generateID(idLength) if err := os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, lid)); err != nil { return err } // 7. 將鏈接id寫入鏈接文件 if err := ioutil.WriteFile(path.Join(dir, "link"), []byte(lid), 0644); err != nil { return err } // 8. 父層不存在就直接返回 if parent == "" { return nil } // 9. 創(chuàng)建鏡像work目錄作為overlay2內(nèi)部使用 if err := idtools.MkdirAndChown(path.Join(dir, "work"), 0700, root); err != nil { return err } // 10. 找到父層(也就是tar中的鏡像層)lower文件,并寫入到當(dāng)前層lower文件中 lower, err := d.getLower(parent) if err != nil { return err } if lower != "" { if err := ioutil.WriteFile(path.Join(dir, lowerFile), []byte(lower), 0666); err != nil { return err } } return nil }
applyTar
將層tar流解包到層掛載點
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) { driver := gdw.ProtoDriver // Mount the root filesystem so we can apply the diff/layer. // 返回由id引用的分層文件系統(tǒng)的掛載點 layerRootFs, err := driver.Get(id, "") if err != nil { return } defer driver.Put(id) layerFs := layerRootFs.Path() options := &archive.TarOptions{UIDMaps: gdw.uidMaps, GIDMaps: gdw.gidMaps} start := time.Now().UTC() logrus.WithField("id", id).Debug("Start untar layer") // 將層tar流解包到層掛載點 if size, err = ApplyUncompressedLayer(layerFs, diff, options); err != nil { return } logrus.WithField("id", id).Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) return }
Create
創(chuàng)建就是在緩存中添加鏡像信息,保存配置
func (is *store) Create(config []byte) (ID, error) { var img Image err := json.Unmarshal(config, &img) if err != nil { return "", err } // Must reject any config that references diffIDs from the history // which aren't among the rootfs layers. rootFSLayers := make(map[layer.DiffID]struct{}) for _, diffID := range img.RootFS.DiffIDs { rootFSLayers[diffID] = struct{}{} } // 如果記錄的創(chuàng)建歷史非空層大于rootFS層數(shù),報錯 layerCounter := 0 for _, h := range img.History { if !h.EmptyLayer { layerCounter++ } } if layerCounter > len(img.RootFS.DiffIDs) { return "", errors.New("too many non-empty layers in History section") } // 將解析配置寫入content目錄 dgst, err := is.fs.Set(config) if err != nil { return "", err } imageID := IDFromDigest(dgst) is.Lock() defer is.Unlock() // 若鏡像已經(jīng)存在鏡像元數(shù)據(jù)緩存中,就直接返回 if _, exists := is.images[imageID]; exists { return imageID, nil } layerID := img.RootFS.ChainID() var l layer.Layer // 獲取鏡像只讀層,并緩存 if layerID != "" { if !system.IsOSSupported(img.OperatingSystem()) { return "", system.ErrNotSupportedOperatingSystem } l, err = is.lss[img.OperatingSystem()].Get(layerID) if err != nil { return "", errors.Wrapf(err, "failed to get layer %s", layerID) } } imageMeta := &imageMeta{ layer: l, children: make(map[ID]struct{}), } is.images[imageID] = imageMeta // 添加reference和id緩存 if err := is.digestSet.Add(imageID.Digest()); err != nil { delete(is.images, imageID) return "", err } return imageID, nil }
創(chuàng)建容器時是如何使用image的?
- 從緩存獲取鏡像配置進行校驗以及合并容器配置
- 以鏡像chainID作為容器掛載層(也是讀寫層)的parent
- 復(fù)制鏡像目錄內(nèi)容到容器目錄
Ref
到此這篇關(guān)于docker鏡像導(dǎo)入的實現(xiàn)方法的文章就介紹到這了,更多相關(guān)docker鏡像導(dǎo)入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- docker鏡像導(dǎo)入導(dǎo)出的兩種方法
- Docker鏡像與容器的導(dǎo)入導(dǎo)出以及常用命令總結(jié)
- Docker?鏡像導(dǎo)入導(dǎo)出過程介紹
- Docker鏡像與容器的導(dǎo)入導(dǎo)出操作實踐
- Docker鏡像的導(dǎo)入導(dǎo)出代碼實例
- docker鏡像的導(dǎo)入和導(dǎo)出的實現(xiàn)
- docker鏡像導(dǎo)入導(dǎo)出備份遷移的操作
- Docker鏡像導(dǎo)出與導(dǎo)入與拷貝實例分析
- Docker鏡像的導(dǎo)入導(dǎo)出的實現(xiàn)方法
- Docker 本地導(dǎo)入鏡像/保存鏡像/載入鏡像/刪除鏡像的方法
- 在 docker 之間導(dǎo)出導(dǎo)入鏡像的方法
- Docker鏡像保存為文件及從文件導(dǎo)入鏡像的方法
相關(guān)文章
docker中修改mysql最大連接數(shù)及配置文件的實現(xiàn)
這篇文章主要介紹了docker中修改mysql最大連接數(shù)及配置文件的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Linux下Docker及portainer相關(guān)配置方法
本文以CentOS 7為例,安裝docker CE版本,docker有兩種版本,社區(qū)版本CE和企業(yè)版本EE,通過實例代碼給大家介紹了Linux下Docker及portainer相關(guān)配置方法,感興趣的朋友跟隨小編一起看看吧2019-06-06Docker容器實現(xiàn)MySQL多源復(fù)制場景分析
這篇文章主要介紹了Docker容器實現(xiàn)MySQL多源復(fù)制,通過本文學(xué)習(xí)可以掌握多源復(fù)制的好處,通過使用場景分析給大家介紹的非常詳細,需要的朋友可以參考下2022-06-06使用Docker搭建Vsftpd 的 FTP 服務(wù)的詳細過程
FTP 基礎(chǔ)FTP 需要兩個端口,一個是數(shù)據(jù)端口,一個是控制端口,這篇文章主要介紹了使用Docker搭建Vsftpd的FTP服務(wù),需要的朋友可以參考下2022-08-08Docker-Compose創(chuàng)建mysql容器詳解
這篇文章主要介紹了Docker-Compose創(chuàng)建mysql容器詳解的相關(guān)資料,需要的朋友可以參考下2022-11-11Docker之蘋果Mac安裝Docker的兩種方式小結(jié)
這篇文章主要介紹了Docker之蘋果Mac安裝Docker的兩種方式小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04