找到计算docker镜像大小的源代码

Ste*_*eve 8 go docker docker-ce

我听说这个数字不等于图像内所有大小的图层相加。而且它也不是它占用的磁盘空间的大小。

现在我想通过源代码检查逻辑(在这个 repo: https://github.com/docker/docker-ce),因为眼见为实!但是在代码中导航了很多时间后,我发现我无法找到真正的imag-size-computing代码。

那么docker使用哪个函数/文件来执行大小逻辑呢?

BMi*_*tch 13

在深入挖掘之前,您可能会发现了解 Linux 如何实现覆盖文件系统很有用。我在介绍性演示的构建部分的第一个练习中包含了一些内容。该演示说明包括每个我跑的命令,它给你的图层的合并的想法,当你从一个层添加/修改/删除时会发生什么。


这是依赖于实现的,基于您的主机操作系统和正在使用的图形驱动程序。我以 Linux 操作系统和 Overlay2 为例,因为这是最常见的用例。

它首先查看图像层存储大小

// GetContainerLayerSize returns the real size & virtual size of the container.
func (i *ImageService) GetContainerLayerSize(containerID string) (int64, int64) {
    var (
        sizeRw, sizeRootfs int64
        err                error
    )

    // Safe to index by runtime.GOOS as Unix hosts don't support multiple
    // container operating systems.
    rwlayer, err := i.layerStores[runtime.GOOS].GetRWLayer(containerID)
    if err != nil {
        logrus.Errorf("Failed to compute size of container rootfs %v: %v", containerID, err)
        return sizeRw, sizeRootfs
    }
    defer i.layerStores[runtime.GOOS].ReleaseRWLayer(rwlayer)

    sizeRw, err = rwlayer.Size()
    if err != nil {
        logrus.Errorf("Driver %s couldn't return diff size of container %s: %s",
            i.layerStores[runtime.GOOS].DriverName(), containerID, err)
        // FIXME: GetSize should return an error. Not changing it now in case
        // there is a side-effect.
        sizeRw = -1
    }

    if parent := rwlayer.Parent(); parent != nil {
        sizeRootfs, err = parent.Size()
        if err != nil {
            sizeRootfs = -1
        } else if sizeRw != -1 {
            sizeRootfs += sizeRw
        }
    }
    return sizeRw, sizeRootfs
}
Run Code Online (Sandbox Code Playgroud)

在那里有一个调用,layerStores它本身就是一个到 layer.Store 的映射

// ImageServiceConfig is the configuration used to create a new ImageService
type ImageServiceConfig struct {
    ContainerStore            containerStore
    DistributionMetadataStore metadata.Store
    EventsService             *daemonevents.Events
    ImageStore                image.Store
    LayerStores               map[string]layer.Store
    MaxConcurrentDownloads    int
    MaxConcurrentUploads      int
    MaxDownloadAttempts       int
    ReferenceStore            dockerreference.Store
    RegistryService           registry.Service
    TrustKey                  libtrust.PrivateKey
}
Run Code Online (Sandbox Code Playgroud)

深入研究layer.Storefor的实现GetRWLayer,有以下定义

func (ls *layerStore) GetRWLayer(id string) (RWLayer, error) {
    ls.locker.Lock(id)
    defer ls.locker.Unlock(id)

    ls.mountL.Lock()
    mount := ls.mounts[id]
    ls.mountL.Unlock()
    if mount == nil {
        return nil, ErrMountDoesNotExist
    }

    return mount.getReference(), nil
}
Run Code Online (Sandbox Code Playgroud)

在此之后,为了找到Size挂载引用的实现,有一个进入特定图形驱动程序的函数

func (ml *mountedLayer) Size() (int64, error) {
    return ml.layerStore.driver.DiffSize(ml.mountID, ml.cacheParent())
}
Run Code Online (Sandbox Code Playgroud)

查看 overlay2 图形驱动程序以找到DiffSize 函数

func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
    if useNaiveDiff(d.home) || !d.isParent(id, parent) {
        return d.naiveDiff.DiffSize(id, parent)
    }
    return directory.Size(context.TODO(), d.getDiffPath(id))
}
Run Code Online (Sandbox Code Playgroud)

那就是调用naiveDiff在 graphDriver 包中实现 Size

func (gdw *NaiveDiffDriver) DiffSize(id, parent string) (size int64, err error) {
    driver := gdw.ProtoDriver

    changes, err := gdw.Changes(id, parent)
    if err != nil {
        return
    }

    layerFs, err := driver.Get(id, "")
    if err != nil {
        return
    }
    defer driver.Put(id)

    return archive.ChangesSize(layerFs.Path(), changes), nil
}
Run Code Online (Sandbox Code Playgroud)

下面archive.ChangeSize我们可以看到这个实现

// ChangesSize calculates the size in bytes of the provided changes, based on newDir.
func ChangesSize(newDir string, changes []Change) int64 {
    var (
        size int64
        sf   = make(map[uint64]struct{})
    )
    for _, change := range changes {
        if change.Kind == ChangeModify || change.Kind == ChangeAdd {
            file := filepath.Join(newDir, change.Path)
            fileInfo, err := os.Lstat(file)
            if err != nil {
                logrus.Errorf("Can not stat %q: %s", file, err)
                continue
            }

            if fileInfo != nil && !fileInfo.IsDir() {
                if hasHardlinks(fileInfo) {
                    inode := getIno(fileInfo)
                    if _, ok := sf[inode]; !ok {
                        size += fileInfo.Size()
                        sf[inode] = struct{}{}
                    }
                } else {
                    size += fileInfo.Size()
                }
            }
        }
    }
    return size
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,我们使用os.Lstat返回一个结构,其中包含Size对每个目录的添加或修改的每个条目。请注意,这是代码采用的几种可能路径之一,但我相信它是这种情况下更常见的路径之一。

  • 非常有启发性。已投赞成票。另外,我听过 https://player.fm/series/devops-and-docker-talk/docker-swarm-2020-with-docker-captain-brandon-mitchell,所以......我会尽力提供帮助能! (3认同)