Suchen Sie den Quellcode für die Berechnung der Größe eines Docker-Images

8

Ich habe gehört, dass die Anzahl nicht allen Größen von Ebenen entspricht, die sich in einem Bild addieren. Und es ist auch nicht die Größe des Speicherplatzes, den es belegt.

Jetzt möchte ich die Logik anhand des Quellcodes überprüfen (in diesem Repo: https://github.com/docker/docker-ce ), denn Sehen ist Glauben! Nachdem ich lange im Code navigiert hatte, stellte ich fest, dass ich den tatsächlichen Code für die Berechnung der Bildgröße nicht finden konnte.

Welche Funktion / Datei wird vom Docker verwendet, um die Größenlogik auszuführen?

Steve
quelle

Antworten:

13

Bevor Sie zu tief graben, ist es möglicherweise hilfreich zu verstehen, wie Linux das Overlay-Dateisystem implementiert. Ich füge ein wenig dazu die erste Übung des Build-Abschnitts meiner Intro-Präsentation hinzu . Die Demo-Notizen enthalten alle Befehle, die ich ausführe, und geben Ihnen eine Vorstellung davon, wie Ebenen zusammengeführt werden und was passiert, wenn Sie eine Ebene hinzufügen / ändern / löschen.


Dies hängt von der Implementierung ab, basierend auf Ihrem Host-Betriebssystem und dem verwendeten Grafiktreiber. Ich nehme das Beispiel eines Linux-Betriebssystems und von Overlay2, da dies der häufigste Anwendungsfall ist.

Zunächst wird die Speichergröße der Bildebene betrachtet :

// 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
}

In gibt es einen Aufruf, layerStoresdem selbst eine Zuordnung zur Ebene zugeordnet ist .

// 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
}

Bei der layer.StoreImplementierung für GetRWLayergibt es die folgende Definition :

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
}

Um die SizeImplementierung für die Mount-Referenz zu finden, gibt es diese Funktion , die in den spezifischen Grafiktreiber gelangt:

func (ml *mountedLayer) Size() (int64, error) {
    return ml.layerStore.driver.DiffSize(ml.mountID, ml.cacheParent())
}

Schauen Sie sich den Grafiktreiber overlay2 an, um die DiffSize-Funktion zu finden :

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))
}

Das ist ein Aufruf, naiveDiffder Size im graphDriver-Paket implementiert :

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
}

Im Folgenden sehen archive.ChangeSizewir diese Implementierung :

// 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
}

An diesem Punkt geben wir os.Lstateine Struktur zurück, die Sizein jedem Eintrag ein Hinzufügen oder Ändern zu jedem Verzeichnis enthält. Beachten Sie, dass dies einer von mehreren möglichen Pfaden ist, die der Code einschlägt, aber ich glaube, dass dies einer der häufigsten für dieses Szenario ist.

BMitch
quelle
2
Sehr lehrreich. Upvoted. Außerdem habe ich player.fm/series/devops-and-docker-talk/… angehört , also ... versuche ich zu helfen, wann immer ich kann!
VonC
0

Sie können Größe in bekommen

$ docker image ls
REPOSITORY  TAG                 IMAGE ID            CREATED             SIZE
nginx       1.12-alpine         24ed1c575f81        2 years ago        15.5MB

Dieser Funktionscode ist hier https://github.com/docker/docker-ce/blob/5a0987be93654b685927c2e5c2d18ac01022d20c/components/cli/cli/command/image/list.go

und erhält die Größe von diesem Code https://github.com/docker/docker-ce/blob/524986b1d978e1613bdc7b0448ba2cd16b3988b6/components/cli/cli/command/formatter/image.go

und schließlich benötigen Sie https://github.com/docker/docker-ce/blob/531930f3294c31db414f17f80fa8650d4ae66644/components/engine/daemon/images/images.go

Ryabchenko Alexander
quelle