webentwicklung-frage-antwort-db.com.de

Multiple RUN vs. Single Chained RUN in Dockerfile, was ist besser?

Dockerfile.1 führt mehrere RUN aus:

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 schließt sich ihnen an:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

Jedes RUN erstellt eine Ebene, daher habe ich immer angenommen, dass weniger Ebenen besser sind und daher Dockerfile.2 ist besser.

Dies ist offensichtlich wahr, wenn ein RUN etwas entfernt, das durch ein vorheriges RUN hinzugefügt wurde (d. H. yum install nano && yum clean all), aber in Fällen, in denen jedes RUN etwas hinzufügt, müssen wir einige Punkte berücksichtigen:

  1. Ebenen sollen nur einen Unterschied über dem vorherigen hinzufügen. Wenn die spätere Ebene also nichts entfernt, was in einer vorherigen Ebene hinzugefügt wurde, sollte zwischen beiden Methoden nicht viel Speicherplatz gespart werden ...

  2. Ebenen werden parallel vom Docker Hub gezogen, also Dockerfile.1, obwohl wahrscheinlich etwas größer, würde theoretisch schneller heruntergeladen werden.

  3. Wenn ein vierter Satz hinzugefügt wird (d. H. echo This is the D > d) und lokaler Wiederaufbau, Dockerfile.1 würde dank Cache schneller bauen, aber Dockerfile.2 müsste alle 4 Befehle erneut ausführen.

Also die Frage: Was ist ein besserer Weg, um ein Dockerfile zu erstellen?

88
Yajo

Nach Möglichkeit füge ich Befehle, die Dateien erstellen, immer mit Befehlen zusammen, mit denen dieselben Dateien in einer einzigen RUN -Zeile gelöscht werden. Dies liegt daran, dass jede RUN -Zeile dem Bild eine Ebene hinzufügt. Die Ausgabe entspricht im wahrsten Sinne des Wortes den Änderungen des Dateisystems, die Sie mit docker diff Für den von ihm erstellten temporären Container anzeigen können. Wenn Sie eine Datei löschen, die auf einer anderen Ebene erstellt wurde, registriert das Union-Dateisystem lediglich die Dateisystemänderung auf einer neuen Ebene. Die Datei befindet sich weiterhin auf der vorherigen Ebene und wird über das Netzwerk übertragen und auf der Festplatte gespeichert. Wenn Sie also Quellcode herunterladen, extrahieren, in eine Binärdatei kompilieren und dann die TGZ- und Quelldateien am Ende löschen, möchten Sie, dass dies alles in einer einzigen Ebene geschieht, um die Bildgröße zu verringern.

Als Nächstes teile ich die Ebenen persönlich auf, basierend auf dem Potenzial für die Wiederverwendung in anderen Bildern und der erwarteten Caching-Nutzung. Wenn ich 4 Bilder habe, die alle dasselbe Basis-Image haben (z. B. debian), kann ich eine Sammlung allgemeiner Hilfsprogramme für die meisten dieser Bilder in den ersten Ausführungsbefehl ziehen, damit die anderen Bilder vom Caching profitieren.

Die Reihenfolge in der Docker-Datei ist wichtig, wenn Sie sich die Wiederverwendung des Bild-Cache ansehen. Ich schaue mir alle Komponenten an, die sehr selten aktualisiert werden, möglicherweise nur, wenn das Basisimage aktualisiert wird, und füge diese im Dockerfile ein. Gegen Ende der Docker-Datei füge ich alle Befehle ein, die schnell ausgeführt werden und sich häufig ändern können, z. Hinzufügen eines Benutzers mit einer Host-spezifischen UID oder Erstellen von Ordnern und Ändern von Berechtigungen. Wenn der Container interpretierten Code (z. B. JavaScript) enthält, der aktiv entwickelt wird, wird dieser so spät wie möglich hinzugefügt, sodass bei einer Neuerstellung nur diese einzelne Änderung ausgeführt wird.

In jeder dieser Änderungsgruppen konsolidiere ich so gut ich kann, um Ebenen zu minimieren. Wenn es also 4 verschiedene Quellcode-Ordner gibt, werden diese in einem einzigen Ordner abgelegt, sodass sie mit einem einzigen Befehl hinzugefügt werden können. Alle Paketinstallationen von so etwas wie apt-get werden nach Möglichkeit in einem einzigen RUN zusammengeführt, um den Aufwand für den Paketmanager (Aktualisieren und Bereinigen) zu minimieren.


pdate für mehrstufige Builds:

Ich mache mir weniger Sorgen um die Reduzierung der Bildgröße in den nicht abschließenden Phasen eines mehrstufigen Builds. Wenn diese Phasen nicht markiert und an andere Knoten gesendet werden, können Sie die Wahrscheinlichkeit einer Cache-Wiederverwendung maximieren, indem Sie jeden Befehl in eine separate RUN -Zeile aufteilen.

Dies ist jedoch keine perfekte Lösung, um Ebenen zu zerquetschen, da Sie zwischen den Phasen nur die Dateien kopieren und nicht den Rest der Bild-Metadaten wie Einstellungen für Umgebungsvariablen, Eintrittspunkt und Befehl. Und wenn Sie Pakete in einer Linux-Distribution installieren, sind die Bibliotheken und andere Abhängigkeiten möglicherweise über das gesamte Dateisystem verteilt, wodurch eine Kopie aller Abhängigkeiten erschwert wird.

Aus diesem Grund verwende ich mehrstufige Builds als Ersatz für das Erstellen von Binärdateien auf einem CI/CD-Server, sodass auf meinem CI/CD-Server nur das Tool zum Ausführen von docker build Und kein JDK ausgeführt werden muss , nodejs, go und alle anderen installierten Kompilierungswerkzeuge.

65
BMitch

Offizielle Antwort in ihren Best Practices aufgeführt (offizielle Bilder MÜSSEN diese einhalten)

Minimieren Sie die Anzahl der Ebenen

Sie müssen das Gleichgewicht zwischen der Lesbarkeit (und damit der langfristigen Wartbarkeit) der Docker-Datei und der Minimierung der Anzahl der verwendeten Ebenen finden. Seien Sie strategisch und vorsichtig in Bezug auf die Anzahl der Ebenen, die Sie verwenden.

Seit Docker 1.10 fügen die Anweisungen COPY, ADD und RUN Ihrem Bild eine neue Ebene hinzu. Seien Sie vorsichtig, wenn Sie diese Aussagen verwenden. Versuchen Sie, Befehle in einer einzigen Anweisung RUN zu kombinieren. Trennen Sie dies nur, wenn dies für die Lesbarkeit erforderlich ist.

Weitere Informationen: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

Update: Mehrstufig im Docker> 17.05

Bei mehrstufigen Builds können Sie mehrere FROM -Anweisungen in Ihrer Docker-Datei verwenden. Jede FROM -Anweisung ist eine Stufe und kann ein eigenes Basisimage haben. In der letzten Phase verwenden Sie ein minimales Basis-Image wie Alpine, kopieren die Build-Artefakte aus früheren Phasen und installieren die Laufzeitanforderungen. Das Endergebnis dieser Etappe ist Ihr Image. Hier kümmern Sie sich also um die Ebenen, wie oben beschrieben.

Wie üblich verfügt Docker über hervorragende Dokumente für mehrstufige Builds. Hier ist ein kurzer Auszug:

Bei mehrstufigen Builds verwenden Sie mehrere FROM-Anweisungen in Ihrer Docker-Datei. Jeder FROM-Befehl kann eine andere Basis verwenden, und jeder von ihnen beginnt eine neue Phase des Builds. Sie können Artefakte selektiv von einer Stufe zur nächsten kopieren und dabei alles zurücklassen, was Sie im endgültigen Bild nicht möchten.

Ein großartiger Blog-Beitrag dazu ist hier zu finden: https://blog.alexellis.io/mutli-stage-docker-builds/

So beantworten Sie Ihre Punkte:

  1. Ja, Schichten sind wie Unterschiede. Ich glaube nicht, dass Ebenen hinzugefügt werden, wenn es absolut keine Änderungen gibt. Das Problem ist, dass Sie etwas in Ebene 2 nicht entfernen können, nachdem Sie es installiert/heruntergeladen haben. Sobald also etwas in eine Ebene geschrieben ist, kann die Bildgröße nicht mehr verringert werden, indem diese entfernt wird.

  2. Obwohl Ebenen parallel gezogen werden können, wodurch sie möglicherweise schneller werden, erhöht jede Ebene zweifellos die Bildgröße, selbst wenn sie Dateien entfernen.

  3. Ja, das Zwischenspeichern ist nützlich, wenn Sie Ihre Docker-Datei aktualisieren. Aber es funktioniert in eine Richtung. Wenn Sie 10 Ebenen haben und die Ebene 6 ändern, müssen Sie immer noch alles von Ebene 6 bis 10 neu erstellen. Es kommt also nicht allzu oft vor, dass der Erstellungsprozess dadurch beschleunigt wird, aber das Image wird garantiert unnötig vergrößert.


Vielen Dank an @ Mohan , der mich daran erinnert hat, diese Antwort zu aktualisieren.

22
Menzo Wijmenga

Die obigen Antworten scheinen veraltet zu sein. Die docs beachten:

Vor Docker 17.05 und vor Docker 1.10 war es wichtig, die Anzahl der Ebenen in Ihrem Bild zu minimieren. Die folgenden Verbesserungen haben diesen Bedarf gemindert:

[...]

Docker 17.05 und höher bietet Unterstützung für mehrstufige Builds, mit denen Sie nur die benötigten Artefakte in das endgültige Image kopieren können. Auf diese Weise können Sie Tools und Debuginformationen in Ihre Zwischenerstellungsphasen einbeziehen, ohne das endgültige Image vergrößern zu müssen.

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

und

Beachten Sie, dass in diesem Beispiel auch zwei RUN-Befehle mithilfe des Bash && -Operators künstlich komprimiert werden, um zu vermeiden, dass eine zusätzliche Ebene im Bild erstellt wird. Dies ist störungsanfällig und schwer zu warten.

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

Best Practice scheint sich dahingehend geändert zu haben, mehrstufige Builds zu verwenden und die Dockerfiles lesbar zu halten.

13
Mohan

Dies hängt davon ab, was Sie in Ihre Bildebenen einfügen.

Der entscheidende Punkt ist, so viele Ebenen wie möglich zu teilen:

Schlechtes Beispiel:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

Gutes Beispiel:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Ein weiterer Vorschlag ist, dass das Löschen nicht nur dann sinnvoll ist, wenn es auf derselben Ebene stattfindet wie das Hinzufügen/Installieren.

2
xdays