Ein Docker-Image ist eine verknüpfte Liste von Dateisystemebenen. Jede Anweisung in einer Docker-Datei erstellt eine Dateisystemebene, die die Unterschiede im Dateisystem vor und nach der Ausführung der entsprechenden Anweisung beschreibt. Der docker inspect
Unterbefehl kann in einem Docker-Image verwendet werden, um die Art der Verknüpfung mit einer Liste von Dateisystemebenen aufzuzeigen.
Die Anzahl der in einem Bild verwendeten Ebenen ist wichtig
- beim Verschieben oder Ziehen von Bildern, da dies die Anzahl der gleichzeitigen Uploads oder Downloads beeinflusst.
- Beim Starten eines Containers werden die Ebenen kombiniert, um das im Container verwendete Dateisystem zu erstellen. Je mehr Ebenen beteiligt sind, desto schlechter ist die Leistung, aber die verschiedenen Dateisystem-Backends sind unterschiedlich davon betroffen.
Dies hat verschiedene Konsequenzen für die Art und Weise, wie Bilder erstellt werden sollen. Der erste und wichtigste Rat, den ich geben kann, ist:
Tipp Nr. 1 Stellen Sie sicher, dass die Erstellungsschritte, bei denen Ihr Quellcode beteiligt ist, so spät wie möglich in der Docker-Datei eingehen und nicht mit den vorherigen Befehlen mit a &&
oder a verknüpft sind ;
.
Der Grund dafür ist, dass alle vorherigen Schritte zwischengespeichert werden und die entsprechenden Ebenen nicht immer wieder heruntergeladen werden müssen. Dies bedeutet schnellere Builds und schnellere Releases, was Sie wahrscheinlich wollen. Interessanterweise ist es überraschend schwierig, den Docker-Cache optimal zu nutzen.
Mein zweiter Rat ist weniger wichtig, aber ich finde ihn unter dem Gesichtspunkt der Wartung sehr nützlich:
Tipp Nr. 2 Schreiben Sie keine komplexen Befehle in die Docker-Datei , sondern verwenden Sie Skripte, die kopiert und ausgeführt werden sollen.
Ein Dockerfile, das diesen Rat befolgt, würde so aussehen
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh
und so weiter. Der Ratschlag, mehrere Befehle mit zu binden, &&
hat nur einen begrenzten Umfang. Es ist viel einfacher, mit Skripten zu schreiben, in denen Sie Funktionen usw. verwenden können, um Redundanz zu vermeiden, oder zu Dokumentationszwecken.
Leute, die an Vorprozessoren interessiert sind und bereit sind, den geringen Overhead zu vermeiden, der durch die COPY
Schritte verursacht wird , und die tatsächlich im laufenden Betrieb eine Docker - Datei erzeugen, in der die
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
Sequenzen werden durch ersetzt
RUN base64 --decode … | sh -x
wo das …
ist die Base64-codierte Version apt_setup.sh
.
Mein dritter Rat ist für Leute, die die Größe und die Anzahl der Lagen zu den möglichen Kosten längerer Bauten begrenzen möchten.
with
Tipp # 3 Verwenden Sie das -idiom, um Dateien zu vermeiden, die in Zwischenebenen, nicht jedoch im resultierenden Dateisystem vorhanden sind.
Eine Datei, die durch eine Docker-Anweisung hinzugefügt und durch eine spätere Anweisung entfernt wurde, ist im resultierenden Dateisystem nicht vorhanden, wird jedoch in den Docker-Ebenen, die das Docker-Bild in der Konstruktion bilden, zweimal erwähnt. Einmal mit dem Namen und dem vollständigen Inhalt in der Ebene, der sich aus dem Hinzufügen der Anweisung ergibt, und einmal als Löschhinweis in der Ebene, der sich aus dem Entfernen der Anweisung ergibt.
Nehmen wir zum Beispiel an, wir brauchen vorübergehend einen C-Compiler und ein Image und betrachten das
# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc
(Ein realistischeres Beispiel wäre, eine Software mit dem Compiler zu erstellen, anstatt nur das Vorhandensein des Compilers mit dem --version
Flag zu bestätigen.)
Das Dockerfile-Snippet erstellt drei Ebenen, von denen die erste die vollständige gcc-Suite enthält, sodass die entsprechenden Daten, auch wenn sie nicht im endgültigen Dateisystem vorhanden sind, immer noch Teil des Images sind und bei jedem Herunterladen, Hochladen und Entpacken der heruntergeladen werden müssen endgültiges Bild ist.
Das with
-idiom ist eine gebräuchliche Form in der funktionalen Programmierung, um den Ressourcenbesitz und die Ressourcenfreigabe von der Logik, die es verwendet, zu isolieren. Es ist einfach, diese Redewendung auf Shell-Scripting zu übertragen, und wir können die vorherigen Befehle als das folgende Script umformulieren, das COPY & RUN
wie in Hinweis 2 verwendet wird.
# with_c_compiler SIMPLE-COMMAND
# Execute SIMPLE-COMMAND in a sub-shell with gcc being available.
with_c_compiler()
(
set -e
apt-get install -y gcc
"$@"
trap 'apt-get --purge autoremove -y gcc' EXIT
)
with_c_compiler\
gcc --version
Komplexe Befehle können in Funktionen umgewandelt werden, so dass sie an die weitergeleitet werden können with_c_compiler
. Es ist auch möglich, Aufrufe mehrerer with_whatever
Funktionen zu verketten, dies ist jedoch möglicherweise nicht sehr wünschenswert. (Unter Verwendung von esoterischeren Funktionen der Shell ist es sicherlich möglich with_c_compiler
, komplexe Befehle zu akzeptieren, aber es ist in allen Aspekten vorzuziehen, diese komplexen Befehle in Funktionen zu verpacken.)
Wenn wir den Ratschlag Nr. 2 ignorieren möchten, würde das resultierende Dockerfile-Snippet lauten
RUN apt-get install -y gcc\
&& gcc --version\
&& apt-get --purge autoremove -y gcc
was wegen der Verschleierung nicht so einfach zu lesen und zu warten ist. Sehen Sie, wie die Shell-Skript-Variante den wichtigen Teil gcc --version
heraushebt, während die verkettete &&
Variante diesen Teil mitten im Rauschen vergräbt.