Docker Container Loading - Teil 5

10 kreative Wege, Docker Images zu bauen: Jib

Roland Huß

© Shutterstock / Good_24

Ist der klassische Weg, Docker Images über docker build zu bauen, wirklich in jedem Fall die beste Art und Weise? In seiner Artikelserie geht Roland Huß, Software Engineer bei Red Hat, dieser Frage auf den Grund und stellt zehn alternative und kreative Wege vor, Docker Images zu erstellen.

Wie im vorherigen Teil dieser Artikelserie angekündigt (es ging um Packer), werden wir uns heute anschauen, wie das Bauen von Docker Images in einen Java-Build-Prozess integriert werden kann. Eigentlich war an dieser Stelle geplant, das allgemein einsetzbare fabric8/docker-maven-plugin vorzustellen. Jedoch kam letzte Woche die Ankündigung von Google zu Jib zuvor, die einiges an Wirbel erzeugt hat. Daher schauen wir uns heute zunächst Jib an, um dann in der nächsten Folge das docker-maven-plugin inklusive eines Vergleiches der beiden Java-Build-Integrationen zu beleuchten.

Jib Grundlagen

Jib ist tatsächlich schon ein etwas älteres Projekt. Google hat es im Rahmen seiner Google-Plattform vor über einem Jahr gestartet. Ziel von Jib ist es, das Bauen von Docker Images in den regulären Build-Prozess zu integrieren. Dabei stehen neben dem eigentlichen Bauen zwei Eigenschaften im Vordergrund: Geschwindigkeit und Reproduzierbarkeit.

Geschwindigkeit wird durch das Aufteilen der Applikation in verschiedenen Schichten realisiert, bei der normalerweise nur eine davon bei Code-Änderungen gebaut werden muss. Das klappt allerdings nur für bestimmte Klassen für Anwendungen, wie wir gleich sehen werden.

Falls sich der Inhalt einer dieser Schichten nicht ändert, wird diese nicht neu gebaut. Somit könnne die Images reproduzierbar wieder erzeugt werden, da Build Timestamps und andere Build-spezifischen Daten herausgefiltert werden.

Das Ganze wird durch einen sogenannten daemonless Build realisiert, bei dem man keinen Docker Daemon braucht. Wie das ganze genau funktioniert, sehen wir im nächsten Abschnitt.

Unter der Haube

Jib bietet für Gradle und Maven jeweils ein Plug-in an, mit dem Docker Images gebaut werden können. Für das Maven Plug-in unterstützt Jib drei Goals:

  • jib:dockerBuild zum Bauen eines Images, um es anschließend zu einem Docker Daemon hochzuladen.
  • jib:build zum Bauen eines Images und anschließendem Upload zu einer Docker Registry.
  • jib:exportDockerContext, um ein Dockerfile zusammen mit dem Build-Kontext (d.h. alle inkludierten Dateien) zu erzeugen, sodass damit ein regulärer Dockerfile Build durchgeführt werden könnte.

Das Besondere an Jib ist, dass es das eigentliche Image lokal baut, ohne eine Docker Daemon zu kontaktieren. Dabei werden alle Image-Schichten und Metadaten lokal im Docker oder OCI-Format erzeugt. Das Ganze passiert direkt aus dem Java Code des Plug-ins heraus, ohne eine externes Tool zu konsultieren.

Eine Bedingung für die effektive Nutzung von Jib ist, dass das Projekt eine spezielle Struktur für die Paketierung als sog. Flat Classpath App mit einer Main-Klasse, Abhängigkeiten in Form von JAR-Dateien und Resourcedateien wie Properties, die aus dem Classpath gelesen werden. Das Gegenstück dazu sind Fat Jars, die all diese Artefakte in einem einzigen JAR vereinigen. Spring Boot hat das Fat-Jar-Format als Paketierung populär gemacht. Jib dagegen stellt sich auf den Standpunkt, dass das Docker Image selbst die eigentliche Paketierung ist, sodass am Ende wiederum nur ein einzelnes Artefakt (in diesem Fall das Image) gemanagt werden muss.

DevOpsCon Whitepaper 2018

Free: BRAND NEW DevOps Whitepaper 2018

Learn about Containers,Continuous Delivery, DevOps Culture, Cloud Platforms & Security with articles by experts like Michiel Rook, Christoph Engelbert, Scott Sanders and many more.

Flat Classpath Apps haben einige Nachteile. Ein grosser Vorteil jedoch ist, dass man die verschiedenen Artefakte in verschiedene Schichten des Docker Images organisieren kann. Dabei werden die am wenigsten Veränderlichen (wie z.B. die Abhängigkeiten) in einer tieferen Schicht des Layer Stacks gelegt, sodass dieser nicht neu gebaut werden muss, wenn sich an den Abhängigkeiten nichts ändert. Und genau das macht Jib: Es steckt alle Jars ,von denen die Anwendung abhängt, in eine Schicht, alle Resource-Dateien in eine andere und die eigentlichen Applikationsklassen in eine dritte Schicht. Diese drei Schichten werden lokal gecached. Somit ist ein erneutes Bauen der Images, bei denen sich nur der Applikationscode ändert, viel schneller möglich, als wenn jedesmal ein einzelnes Fat Jar gebaut werden müsste, das natürlich nach jedem Bauen anders aussieht.

Aber schauen wir uns im Detail an was Jib bei den Maven Goals jib:dockerBuild oder jib:build macht:

  • Zunächst werden die Schichten des Basis-Images geladen und lokal abgelegt. Standardmäßig wird das Java-Basis-Image gcr.io/distroless/java genutzt. Das kann jedoch konfiguriert werden.
  • Es werden die folgenden drei Schichten für die Applikation aufgebaut:
    • Abhängigkeiten
    • Resourcen
    • Applikationsklassen
  • Diese drei Schichten werden gecached, d.h. wenn sich die Daten in einer Schicht nicht ändern, dann wird diese Schicht auch nicht neu geschrieben.
  • All diese Schichten (die des Basis-Images und die der Applikation) werden parallel bearbeitet. Sobald alle Schichten lokal vorhanden sind, wird das eigentlich Image aufgebaut und um die fehlenden Metadaten ergänzt. Als ENTRYPOINT wird fest folgender Wert gesetzt:
    java <jvm-flags> -cp dep_dir/*:resource_dir/:class_dir/ <main-class>
    

    <jvm-flags> kann optional konfiguriert werden und <main-class> ist die zwingend erforderliche Main-Klasse. Die Dateien, die zu dem angegebenen Classpath führen, kommen von dem zugrundeliegendem Maven- oder Gradle-Projekt und werden in den vom Plug-in vorbereiteten Verzeichnissen abgelegt. Zusätzlich werden alle konfigurierten Argumente als CMD des Images übernommen sowie konfigurierte Ports (EXPOSE) eingefügt.

  • Zum Schluss werden die so präparierten Image-Schichten mit den Metadaten in ein TAR-Archive gepackt und dieses entweder zu einem Docker Daemon (jib:dockerBuild) oder einer Docker Registry (jib:build) hochgeladen.

Jib: Pro und Contra

Für den Use Case, den Googles Tool unterstützt, hat Jib einige Vorteile:

  • Das Tool ist sehr schnell für inkrementelle Builds von Flat Classpath Apps, da Artefakte mit unterschiedlicher Volatilität in unterschiedlichen Schichten gepuffert werden.
  • Es ist kein Docker Daemon erforderlich, was einerseits die Anforderungen an das Buildsystem minimiert, andererseits aber auch die Sicherheit erhöht, da das Bauen des Images keine root-Berechtigung benötigt.
  • Es können reproduzierbare Images erzeugt werden. Das passiert bei Jib so, dass bei den zugefügten Dateien die Datei-Owner und die Zeitstempel der Dateien auf einen fixen Wert gesetzt werden. Solange keine Zeitstempel z.B. in Property-Dateien erzeugt werden, können so bei zwei verschiedenen Builds jeweils identische Images erzeugt werden.
  • Jib unterstützt sowohl Maven als auch Gradle.

Es gibt aber auch Nachteile, die zum Teil in der Zukunft behoben werden könnten, andere sind jedoch auch prinzipbedingt und daher eher als gegeben anzusehen:

  • Der Einsatz von Jib macht tatsächlich nur für die angesprochenen Flat Classpath Apps Sinn. Spring-Boot-, Thorntail- und Java-EE-Anwender schauen also erst einmal in die Röhre, da diese Tools auf Fat Jars oder WAR als Paketierungsformat setzen. Für Spring Boot gibt es mit dem Spring Boot Thin Launcher eine alternative Paketierung, ebenso wie die Hollow Jars für Thorntail. Diese Technologien wären prinzipiell auch für einen Einsatz mit Jib geeignet, jedoch fehlt bislang eine entsprechende Unterstützung.
  • Der Startup der Anwendung ist hart codiert mit einem einfach Aufruf von java. Es gibt keine Möglichkeit, ein optimiertes Startup-Skript wie run-java-sh zu verwenden.
  • Es können keine zusätzlichen Dateien wie Konfigurationsdateien oder Agenten wie Prometheus‘ jmx_exporter hinzugefügt werden. Jedoch existiert dafür inzwischen ein Pull Request.
  • Die Classpath-Reihenfolge ist fest vorgegeben, sodass es z.B. nicht möglich ist, Ressourcen in den Abhängigkeiten zu überschreiben.
  • Jib benutzt statt eines Dockerfiles eine eigene XML-Syntax für die Konfiguration.

Wenn also das Projekt „passt“ (Flat Classpath App), dann sollte man sich Jib unbedingt anschauen. In der nächsten Folge werden wir dann das docker-maven-plugin kennenlernen, mit dem man beliebige Java-Projekte bauen kann und das auch ansonsten noch einiges an zusätzlichen Features bietet.

Verwandte Themen:

Geschrieben von
Roland Huß
Roland Huß
Roland Huß ist ein Software Engineer, der für Red Hat an fabric8, einer Microservices-Plattform für Kubernetes und OpenShift arbeitet. Er entwickelt seit fast 20 Jahren, hat aber niemals seine Wurzeln als Systemadministrator vergessen. Roland arbeitete aktiv in Open-Source-Projekten mit und betreut sowohl Jolokia, die JMX-HTTP Bridge als auch das populäre Docker-Maven-Plug-in fabric8io. Und er liebt Chilis.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: