docker镜像是如何导入的?

镜像导入是由image/tarexport/load.go#tarexporter.Load()完成的

以下代码参考github.com/docker/docker版本v0.0.0-20181129155816-baab736a3649

主要是注册镜像信息以及解包镜像tar流到新root

导出和保存的区别在于

  • 导出(export): 仅导出文件结构
  • 保存(save): 保存镜像历史和元数据

这意味着导出将不会包含USER、EXPOSE等Dockerfile里面的命令,也就无法转移镜像到另一台机器上了

func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {var progressOutput progress.Outputif !quiet {progressOutput = streamformatter.NewJSONProgressOutput(outStream, false)}outStream = streamformatter.NewStdoutWriter(outStream)// 1. 创建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 []manifestItemif err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil {return err}var parentLinks []parentLinkvar imageIDsStr stringvar imageRefCount int// 4. 从manifest中读取并解析到imagefor _, 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.RootFSrootFS.DiffIDs = nil// 若image rootFS diffID数量与manifest中记录的层数不一致,则报错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.OSif 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 := rootFSr.Append(diffID)newLayer, err := l.lss[os].Get(r.ChainID())if err != nil {// 如果没有注册,那就注册layernewLayer, 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与缓存中layer 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 = 0for _, 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)}// 设置已加载的id、referencel.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()执行

// 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需要一个新根和一个旧根。旧根必须是新根的子目录,它是调用pivot_root后当前rootfs驻留的位置。New root是新rootfs设置的位置。在调用pivot_root之后,旧根会被移除,因此在新根下不再可用。这类似于libcontainer设置容器rootfs的方式// 在这里是以前面创建的临时目录作为新root,并在其下创建privot_root作为老root,最后切换到新rootif err := chroot(flag.Arg(0)); err != nil {fatal(err)}// 将tar流解包到新rootif err := archive.Unpack(os.Stdin, "/", options); err != nil {fatal(err)}// fully consume stdin in case it is zero paddedif _, err := flush(os.Stdin); err != nil {fatal(err)}os.Exit(0)
}

loadLayer

注册镜像层以及加载层tar流到对应目录下

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.Readerif 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流和本层镜像的chainIDreturn 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 errorvar pid stringvar p *roLayer// 1. 从缓存中获取到给定chainID的层信息if string(parent) != "" {p = ls.get(parent)if p == nil {return nil, ErrLayerDoesNotExist}pid = p.cacheID// Release parent chain if errordefer func() {if err != nil {ls.layerL.Lock()ls.releaseLayer(p)ls.layerL.Unlock()}}()if p.depth() >= maxLayerDepth {err = ErrMaxDepthExceededreturn nil, err}}// 2. 创建新的只读层layer := &roLayer{parent:         p,cacheID:        stringid.GenerateRandomID(),referenceCount: 1,layerStore:     ls,references:     map[Layer]struct{}{},descriptor:     descriptor,}// 3. 准备文件系统(overlay2)文件目录结构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. 从给定读写层流中提取变化的内容到镜像层挂载点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 errorerr = errors.New("layer already exists")return existingLayer.getReference(), nil}if err = tx.Commit(layer.chainID); err != nil {return nil, err}ls.layerMap[layer.chainID] = layerreturn layer.getReference(), nil
}

driver.Create

为镜像层创建diff、work、lower目录,并写入镜像层tar流lower内容到对应lower目录

func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {dir := d.dir(id)// 1. 获取当前用户在宿主机对应的userID、groupIDrootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)if err != nil {return err}root := idtools.Identity{UID: rootUID, GID: rootGID}// 2. 为当前用户创建镜像目录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 failureif 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. 设置储存配额if driver.options.quota.Size > 0 {// Set container disk quota limitif err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil {return err}}}// 5. 创建镜像diff目录if err := idtools.MkdirAndChown(path.Join(dir, "diff"), 0755, root); err != nil {return err}// 6. 创建指向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. 创建镜像work目录作为overlay2内部使用if err := idtools.MkdirAndChown(path.Join(dir, "work"), 0700, root); err != nil {return err}// 10. 找到父层(也就是tar中的镜像层)lower文件,并写入到当前层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引用的分层文件系统的挂载点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

创建就是在缓存中添加镜像信息,保存配置

func (is *store) Create(config []byte) (ID, error) {var img Imageerr := 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{}{}}// 如果记录的创建历史非空层大于rootFS层数,报错layerCounter := 0for _, 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()// 若镜像已经存在镜像元数据缓存中,就直接返回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
}

创建容器时是如何使用image的?

  1. 从缓存获取镜像配置进行校验以及合并容器配置
  2. 以镜像chainID作为容器挂载层(也是读写层)的parent
  3. 复制镜像目录内容到容器目录

Ref

  1. https://stackoverflow.com/questions/22655867/what-is-the-difference-between-save-and-export-in-docker

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/64355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python3+requests:接口自动化测试(二)

前言:上篇文章python3requestsunittest:接口自动化测试(一):已经介绍了基于unittest框架的实现接口自动化,但是也存在一些问题,比如最明显的测试数据和业务没有区分开,接口用例不便于…

【机器学习】线性回归

Model Representation 1、问题描述2、表示说明3、数据绘图4、模型函数5、预测总结附录 1、问题描述 一套 1000 平方英尺 (sqft) 的房屋售价为300,000美元,一套 2000 平方英尺的房屋售价为500,000美元。这两点将构成我们的数据或训练集。面积单位为 1000 平方英尺&a…

2010-2021年上市公司和讯网社会责任评级CSR数据/和讯网上市公司社会责任数据

2010-2021年上市公司和讯网社会责任评级CSR数据 1、时间:2010-2021年 2、指标:股票名称、股票代码、年份、总得分、等级、股东责任、员工责任、供应商、客户和消费者权益责任、环境责任、社会责任、所属年份 3、样本量:4万 4、来源&#…

数据结构与算法基础-学习-31-交换排序之冒泡排序、快速排序

排序的其他相关知识点和源码分享可以参考之前的博客: 《数据结构与算法基础-学习-30-插入排序之直接插入排序、二分插入排序、希尔排序》 一、交换排序基本思想 两两比较,如果发生逆序则交换位置,直到所有数据记录都排好序为止。 二、冒…

linux下C程序查看内存剩余大小

嵌入式linux中很多内存资源都比较小&#xff0c;所以很多程序中在malloc时就需要提前知道内存剩余的大小 下面是一种调用 sysinfo接口来实现。 示例代码如下&#xff1a; #include <stdio.h> #include <linux/kernel.h> #include <linux/unistd.h> #includ…

大模型理解之CLIP

前言 2021年2月份&#xff0c;CLIP模型被提出&#xff0c;想法很简单&#xff0c;性能高效&#xff0c;而且具备很好的泛化性。我在这里简单谈论下我对CLIP模型的理解&#xff0c;以及发现的一些问题。 我是在沐神的视频中了解的CLIP, 里面提到CLIP最大的贡献在于打破了固定类…

四轴飞行器的电池研究(MatlabSimulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

全球免费编程教育网站:Code.org

全球免费编程教育网站&#xff1a;Code.org 官网地址注册使用 你还在为小朋友的编程教育而发愁吗&#xff1f; 你还在为小朋友放假无聊而头疼吗&#xff1f; 他来了他来了&#xff0c;全球免费编程教育网站来了。 2013年成立的Code.org是一个非营利组织。 它致力于为年轻女子、…

Xilinx UltraScale架构之可配置逻辑块CLB

目录 一、概览 二、UltraScale架构 2.1 UltraScale/UltraScale特点 2.2 与7系列CLB差异 三、 CLB结构 3.1 LUT 3.2 FF 3.3 多路选择器Multiplexers 3.4 进位链Carry Chain 四、应用 4.1 分布式RAM 4.2 移位寄存器 4.3 进位链Carry Chain 五、参考资料 一、概览 二…

专门针对开发人员,攻击者利用Rust获取操作系统信息

近日&#xff0c;研究人员在 Rust 编程语言的 crate 注册表中发现了一些恶意软件包&#xff0c;专门针对开发人员。 Phylum 在上周发布的一份报告中称&#xff0c;这些库是由一个名为 "amaperf "的用户在 2023 年 8 月 14 日至 16 日之间上传的。现已删除的软件包名…

【LeetCode-中等题】114. 二叉树展开为链表

文章目录 题目方法一&#xff1a;前序遍历&#xff08;构造集合&#xff09; 集合&#xff08;构造新树&#xff09;方法二&#xff1a;原地构建方法三&#xff1a;前序遍历--迭代&#xff08;构造集合&#xff09; 集合&#xff08;构造新树&#xff09; 题目 方法一&#x…

el-select 选择一条数据后,把其余数据带过来

1. 案例&#xff1a; ps: 票号是下拉框选择&#xff0c;风险分类、场站名称以及开始时间是选择【票号】后带过来的。 2. 思路: 使用官网上给的方法&#xff0c;选择之后&#xff0c;触发change方法从而给其余字段赋值 3. 代码 <el-form-itemlabel"票号&#xff1a;&…

buildAdmin的使用笔记

安装buildAdmin 下载完整包&#xff0c;解压进入 buildadmin 的文件夹&#xff0c; 输入命令 composer install 启动的时候使用&#xff0c; php think run 就可以了 为什么启动只需要&#xff0c; php think run 这种启动方式&#xff0c; 我是头一回看见 &#xff0c;后来才…

如何在IT技术面试中脱颖而出?

前序 在竞争激烈的IT领域&#xff0c;技术面试是进入理想工作的关键一步。然而&#xff0c;要在面试中脱颖而出&#xff0c;您需要更多的准备和策略。以下是一些实际且深入的方法&#xff0c;可以帮助您在IT技术面试中脱颖而出。 准备和策略 确保深入了解公司和职位 在面试前…

Android 手游聚合SDK小知识(一)

Android 手游聚合SDK小知识(一) Android 手游聚合SDK小知识(二) 聚合分包 前言 回头想想&#xff0c;在安卓游戏SDK这个领域&#xff0c;我也呆了4年了&#xff0c;从啥都不懂的小菜鸟&#xff0c;逐渐靠自己不断学习&#xff0c;对这个行业也算有了一些理解&#xff0c;趁着…

如何击败那个令程序员绝望的Bug

灵异事件&#xff01;程序里发现了新Bug但是它正常运行啦&#xff01;人生处处有Bug&#xff0c;但有些Bug实在令人目瞪口呆&#xff0c;久久不能忘怀。 有一天&#xff0c;我正忙着维护一个庞大的软件项目&#xff0c;数以百万计的行代码交织成复杂的网络。项目中的一个模块引…

[贪心] 拼接最小数

这道题思路并不难&#xff0c;我主要想学习其一些对于字符串的处理。 代码如下&#xff1a; #include <iostream> #include <string> #include <algorithm> using namespace std;const int MAXN 10000; string nums[MAXN];bool cmp(string a, string b) {…

柯桥外贸英语培训哪家表专业,零基础可以学吗

“我也是”一出口&#xff0c;紧跟着就是 “Me too! / I think so. / I agree with you. 等等”。 今天&#xff0c;咱们换个说法&#xff0c;来表达一下两个人不谋而合、意见一致吧~~ 01 影视对接 02 Subtitle WA: And these are her puppies. Come here, come here, littl…

useEffect 不可忽视的 cleanup 函数

在 react 开发中&#xff0c; useEffect 是我们经常会使用到的钩子&#xff0c;一个基础的例子如下&#xff1a; useEffect(() > {// some code here// cleanup 函数return () > {doSomething()} }, [dependencies])上述代码中&#xff0c; cleanup 函数的执行时机有如下…

js 获得元素的offsetLeft

要获得元素相对于其offsetParent元素左侧边缘的距离&#xff08;即offsetLeft&#xff09;&#xff0c;可以使用如下代码&#xff1a; var el document.getElementById(your-element-id); var offsetLeft el.offsetLeft;其中&#xff0c;el为要获取offsetLeft的元素对象&…