Docker Best Practices: Sortierte Layer

Das korrekte Sortieren der Layer eines Docker-Images optimiert den Build und auch die Ausführung der verschiedenen Images. Schlimmstenfalls wird immer alles neu gebaut und dann durch die komplette Infrastruktur geschoben.

Dieser Artikel ist Teil der Serie “Docker Best Practices”. Hier die Links auf die bisherigen Beiträge:

Beim Erzeugen von Docker-Images wird häufig ein logischer Aufbau verfolgt. Dies entspricht leider nicht immer der für Docker sinnvollen Reihenfolge der Kommandos. D. h. der Aufbau eines Dockerfiles könnte initial z. B. wie folgt aussehen:

FROM ....
COPY  ...                   # Dateien hinzufügen (1)
COPY  ...                   # Dateien hinzufügen (2)
RUN yum install ....        # Software installieren
ENV .....                   # Konfiguration
RUN ....                    # weitere Änderungen
CMD                         # Festlegung des Kommandos

Der Aufbau sieht auf den ersten Blick logisch sortiert aus. Führt man den Docker-Build mehrfach aus, erhält man üblicherweise ab dem 2. Build für jeden Schritt die Ausgabe “Using cache”.

Betrachten wir, was dahinter passiert. Das FROM-Statement wählt das Basis-Image aus. Solange sich dessen Inhalt (also der Hash) nicht verändert, kann der nächste Schritt aus dem Cache kommen.

Dieser nächste Schritt ist “COPY”. Für COPY wird das Kommando selbst und auch der Inhalt der angegebenen Datei(en) geprüft. Wenn sich diese verändert haben, wird der entsprechende Layer neu gebaut. Mit diesem Layer aber auch alle darüber liegenden, egal ob sie sich verändert haben – ihre Basis ist nun eine andere! Kopiert der zweite COPY-Befehl die soeben erstellte Software, so ändert sich dieser Layer vermutlich mit jedem Build.

Darauf folgt z. B. eine Software-Installation in RUN, die vermutlich von diesen Dateien völlig unberührt ist. Trotzdem muss sie erneut ausgeführt werden, weil sich die zuvor erstellten Layer verändert haben. Ein besserer Aufbau des Dockerfile sieht also aus wie folgt:

FROM ....
RUN yum install ....        # Software installieren 
ENV .....                   # Konfiguration 
COPY  ...                   # Dateien hinzufügen (1)
RUN ....                    # weitere Änderungen 
COPY  ...                   # Dateien hinzufügen (2)
CMD                         # Festlegung des Kommandos

Die Installation über den Package-Manager selbst ändert sich sehr selten, auf jeden Fall seltener als die Dateien, die jetzt sogar nach den ENV-Einträgen hinzugefügt werden. Die in den ENV-Einträgen definierten Pfade werden in diesem Beispiel stabiler eingeschätzt als die Dateien der COPY-Kommandos. Diese sind nur teilweise für den abschließenden RUN-Befehl notwendig, daher kann dieser in diesem Beispiel zwischen die beiden COPY-Befehle umsortiert werden.

Best Practice

Die Kommandos im Dockerfile sollen möglichst so aufgebaut werden, dass stabile und große Layer möglichst früh gebaut werden. Während der Entwicklung des Dockerfiles werden vermutlich auch diese Kommandos häufig angepasst, daher sollte das Augenmerk auf die langfristig erwarteten Änderungen gelegt werden.

Volatile Kommandos oder Inhalte erzeugen neue Layer und sollten daher in ihrer Größe optimiert sein. Kleine inhaltliche Änderungen sollten nur kleine Änderungen an den Images bewirken. Man kann dies mit “docker history [image]” nach einigen Builds der Images ermitteln. Die großen und tiefen Layer sollten deutlich älter sein, als die kleinen und neuen Layer.

In Continuous Integration und -Deployment Umgebungen kann man häufig den Build Cache nicht nutzen, da man den konkreten Docker-Host, auf dem der Build läuft, nicht unter Kontrolle hat. In diesem Fall kann man das Dockerfile in zwei Dateien aufteilen:

  • der stabile Teil bis zum letzten RUN in Dockerfile-basis, welches manuell bei Änderungen gebaut und als eigenes Image in die Registry gepusht wird
  • der volatile Teil, der die eigentliche Software beinhaltet (COPY und CMD am Ende) in Dockerfile, was weiterhin bei jedem “docker build” herangezogen wird.

Pitfalls

Der Build-Cache ist in der Regel sehr angenehm, kann sich aber auch negativ auswirken. Die Installation durch den Package-Manager yum wird aber u. U. nie erneuert wenn der Build Cache lange bestehen bleibt.

Außerdem sollte darauf geachtet werden, dass sich die Kommandos im Dockerfile verändern müssen, wenn sich der Inhalt dahinter verändern soll. Daher sind Software-Installationen mit konkreten Versionen besser.

Dann definiert das Dockerfile exakt den Inhalt und wenn man etwas Aktuelleres möchte, verändert man das Dockerfile. Damit wird automatisch der Build Cache nicht mehr herangezogen. “docker build –no-cache” ist hierfür nur die “quick & dirty”-Lösung.

Fazit

Da jeder Layer einen Bezug zu seiner Basis hat, löst der erste Layer, der Veränderungen enthält, einen kompletten Build der restlichen Layer aus. Dies kann durch geschickte Sortierung nach Änderungshäufigkeit und Größe einfach optimiert werden, auch wenn Dockerfiles dann auf den ersten Blick eine seltsame Reihenfolge zu haben scheinen. Unnötige Rebuilds werden damit nicht mehr durch die gesamte CI/CD-Infrastruktur und die Server geschoben.

Short URL for this post: https://wp.me/p4nxik-3qH
This entry was posted in Java Runtimes - VM, Appserver & Cloud and tagged . Bookmark the permalink.

3 Responses to Docker Best Practices: Sortierte Layer

  1. Pingback: Docker Best Practices: Keine “FAT JARs” mehr! | techscouting through the java news

  2. Pingback: Docker Best Practices: Alle Wege führen nach Docker | techscouting through the java news

  3. Avatar Agira says:

    Helpful article on Best practices for writing Dockerfiles.
    I am sharing an article on Benefits Of Using Docker And Kubernetes Together here: https://www.agiratech.com/benefits-of-using-docker-and-kubernetes-together/ which may be useful for your readers.

Leave a Reply