Java-Container-Images mit Jib erstellen
17. August 2021
0 Min. LesezeitWenn Sie schon länger als eine Minute mit Container-Images arbeiten, kennen Sie wahrscheinlich die allgegenwärtigen Dokumente, die Schritt für Schritt beschreiben, wie ein Image erstellt wird: Dockerfiles. Wussten Sie, dass es immer mehr Tools gibt, mit denen sich OCI-konforme Images ohne Dockerfiles erstellen lassen? In diesem Artikel sehen wir uns Jib an, ein zu 100 % Java-basiertes Tool, mit dem sich hochoptimierte Images erstellen lassen, ohne dass Sie sich um ein korrekt aufgebautes Dockerfile kümmern müssen.
Ich gehe davon aus, dass Sie bereits verstehen, wie Images erstellt werden, und zumindest Grundkenntnisse zu Dockerfiles haben. Falls Sie neu damit sind, lesen Sie vielleicht meinen Beitrag zu developer-driven workflows, in dem ich ausführlich auf das Erstellen von Container-Images eingehe.
Die Herausforderung
Als Java-Entwickler kümmern Sie sich neben Ihrer Anwendungs-Codebasis und den Bibliotheksabhängigkeiten wahrscheinlich auch darum, welche JDK- und JVM-Versionen Sie verwenden. Dazu gehören möglicherweise die Version des Web-App-Servers, auf dem Sie aufbauen, sowie Laufzeitkonfigurationen wie die Abstimmung des Garbage Collectors und Datenbankverbindungen. Womit Sie sich nicht unbedingt befassen mussten, sind Aspekte auf Betriebssystemebene wie die Installation von Paketen, Dateisystemberechtigungen, die zur Laufzeit verwendete UID/GID und andere Konfigurationsdetails. Historisch wurden diese von den Betriebs- und Sicherheitsteams verwaltet, die Ihnen Plattformen bereitstellen.
Mit der Einführung von Container-Runtimes und den darauf aufbauenden Orchestrierungssystemen fallen viele dieser tiefer liegenden Aspekte zunehmend in den Zuständigkeitsbereich der Entwicklungsteams. Sie werden in Form von Infrastructure-as-Code-Konfigurationsdateien (IaC) verwaltet, die Teil der Code-Repositories für Anwendungen und Services sind. Diese Dateien können verschiedene Formen haben, etwa Kubernetes-YAML, Terraform-HCL, CloudFormation-JSON und natürlich Dockerfiles, die die Struktur des Container-Images festlegen, in dem Ihre Anwendung ausgeführt wird.
Ihre Anwendung in ein Image zu packen und in einem Container auszuführen, ist normalerweise keine besonders schwierige Aufgabe. Sicherzustellen, dass das Image korrekt aufgebaut, optimiert und sicher ist und den Standards Ihres Unternehmens entspricht, kann dagegen eine Herausforderung sein. Von Entwicklern wird heute erwartet, dass sie sich mit Tools für Betriebssystem-Scans auskennen, Image-Kennzeichnungsstandards einhalten, die aktuellen Dockerfile-Best Practices kennen und noch vieles mehr übernehmen. Linting- und Scan-Tools wie Hadolint, Dockle und unser Angebot Snyk Container können Sie dabei unterstützen. Aber was wäre, wenn sich die meisten – oder sogar alle – dieser neuen Anforderungen und Boilerplate-Inhalte automatisieren ließen?
Jib: ein zu 100 % Java-basiertes Tool zum Erstellen von Container-Images
Jib ist ein Open-Source-Tool, das vollständig in Java geschrieben ist und OCI-konforme (Docker v2) Container-Images erstellt – ganz ohne Dockerfile oder installierte Container-Runtime. Jib lässt sich als eigenständiges CLI-Tool verwenden, wird aber meist als Plugin in einen Maven- oder Gradle-Build-Schritt eingebunden. Mit dem Plugin führen Sie einfach denselben Build-Befehl mvn oder gradle aus, den Sie bereits zum Erstellen Ihrer .jar-, .war- und anderer Artefakte verwenden. Damit erstellen Sie zusätzlich ein OCI-Image, das Ihre Anwendung in einem Container ausführen kann, und können es bei Bedarf auch bereitstellen.
Ausgehend von einer Spring-Boot-Anwendung lässt sich Jib ganz einfach hinzufügen: Fügen Sie diesen XML-Code in Ihre Maven-Datei pom.xml ein (die vollständige Dokumentation finden Sie hier) und führen Sie den Befehl mvn package erneut aus. Hier können Sie festlegen, wo der Container abgelegt werden soll. Der Einfachheit halber stellt dieses Beispiel das Image in einer Image-Registry bereit, die wir später zum Ausführen verwenden. Sie können das Image aber auch in eine lokale **.**tar-Datei schreiben lassen oder – sofern ein Docker-Daemon ausgeführt wird und zugänglich ist – im lokalen Docker-Image-Cache ablegen lassen (genau wie bei einem Docker-Build).
Hinweis: In der obigen Ausgabe sehen Sie einige interessante „Warnungen“, auf die wir später noch eingehen.
Führen Sie jetzt auf einem Rechner mit installierter Container-Runtime einfach den Container aus: docker run --rm -it -p 8080:8080 myimage:tag
Wenn Sie Gradle verwenden, finden Sie in der offiziellen Dokumentation alle Informationen dazu, wie Sie dasselbe in Ihrer Datei build.gradle umsetzen.
Das ist schon alles. Zum Erstellen von Images sind weder Dockerfile noch zusätzliche Tools erforderlich – Sie verwenden einfach dieselben Build-Tools, mit denen Sie bereits Ihre Java-Artefakte erstellen. Das vereinfacht nicht nur den Alltag von Entwicklern, sondern erleichtert auch die Unterstützung von CI-Build-Agents erheblich, da weder der Docker-Socket freigegeben noch andere Build-Tools verwaltet werden müssen.
Ein genauerer Blick auf das Image
Der Image-Build-Prozess ist also vielleicht einfacher. Wenn es Ihnen aber wie mir beim ersten Anblick geht, haben Sie wahrscheinlich Fragen wie diese …
Wie erstellt Jib Images?
Wie jedes Tool zum Erstellen von Images erzeugt Jib mehrere Dateisystem-Layer. Sie enthalten die Java-Runtime, Ihre Anwendung und alle zugehörigen Abhängigkeiten sowie Metadaten, anhand derer die Container-Engine weiß, wie die JVM gestartet werden soll.
Hier sehen Sie die Layer-Informationen für dieses Beispiel, ausgegeben mit dem Befehl docker image history:
Beachten Sie, dass bei den ersten vier Layern unter CREATED jeweils „vor 51 Jahren“ steht. Das ist ein Nebeneffekt der wiederholbaren Build-Strategie von Jib, mit der bei Builds derselben Codebasis exakt dieselben Layer-Hashes entstehen sollen. Weitere Informationen dazu finden Sie in den häufig gestellten Fragen.
Wie die Layer-Kommentare zeigen, stammen einige Layer aus dem standardmäßigen AdoptOpenJDK-Basis-Image – mehr dazu weiter unten. Danach folgen der Reihe nach:
Bibliotheksabhängigkeiten, die in Ihren Maven-/Gradle-Dateien definiert sind
Ressourcendateien
Klassendateien aus Ihrem kompilierten Anwendungscode
Argumentdateien für die Java-JVM.
Sehen wir uns mit dem Open-Source-Tool dive noch genauer an, welche Dateien in diesen vier Layern enthalten sind:
Abhängigkeiten
Dieser Layer fügt alle .jar-Dateien zum Ordner /app/libs hinzu.
Ressourcen
Hier sehen wir /app/resources und alle zugehörigen Dateien aus unseren Ressourcenordnern.
Klassen
Dieser Layer enthält unter /app/classes die .class-Dateien aus der Kompilierungsphase des Builds.
JVM-Argumentdateien
Schließlich enthält der Ordner /app im obersten Verzeichnis einige Dateien mit Argumenten, die beim Starten der JVM im Container verwendet werden. In diesem Beispiel würden Sie beim Prüfen dieser beiden Dateien den Runtime-Classpath und Informationen zur Main-Class finden.
Hinweis: Je nach Struktur Ihres Maven-/Gradle-Builds können Ihre mit Jib erstellten Images weitere Layer enthalten. Weitere Informationen zu den anderen möglichen Layern finden Sie in den häufig gestellten Fragen zu Jib.
Welches Basis-Image verwendet Jib, und was ist, wenn ich ein eigenes Image verwenden möchte?
Standardmäßig verwendet Jib für JAR-Builds das offizielle Docker-Hub-Basis-Image adoptopenjdk:jre-8 und für WAR-Builds das offizielle Docker-Hub-Basis-Image jetty. Das lässt sich über die Plugin-Konfiguration anpassen. Einzelheiten dazu finden Sie in der offiziellen Dokumentation, einschließlich der Konfiguration eigener Deklarationen wie ENTRYPOINT, USER oder weiterer Werte. Die Dokumentation erklärt auch, warum es sinnvoll ist, ein eigenes Basis-Image festzulegen und einen bestimmten Hash zu verwenden, um reproduzierbare Builds zu gewährleisten. Viele Unternehmen haben intern freigegebene Basis-Images, die Entwicklungsteams verwenden müssen und die von Sicherheits- und Betriebsteams geprüft und gehärtet wurden. Hier sehen Sie ein Beispiel dafür, wie Sie die Maven-Datei pom.xml anpassen, um ein solches Image zu verwenden.
Auch zusätzliche Einstellungen wie eine standardisierte Image-Kennzeichnung werden unterstützt. Angenommen, Ihr Unternehmen verlangt beispielsweise, dass alle Images die folgenden Labels enthalten:
Git-URL des Quellcodes
Git-Commit-ID/-Hash
Build-Version des Maven-Projekts
Wenn die pom.xml bereits auf diese Daten zugreifen kann, ist die Konfiguration des Jib-Plugins zum Hinzufügen der Labels ganz einfach:
Wenn wir das erstellte Image prüfen, sehen wir, dass die Labels angewendet wurden – genau so, als wären sie über LABELS-Zeilen im Dockerfile hinzugefügt worden. (Hier verwende ich das großartige Kommandozeilen-Tool jq, um nur dieses Array aus der Antwort herauszufiltern.)
Stellen Sie sich nun vor, all diese komplexen standardisierten Konfigurationen wären über <pluginManagement> und andere übliche Maven-Konfigurationen in einem übergeordneten POM definiert. Entwickler müssten sich dann weder um dieses Boilerplate-XML kümmern noch es überhaupt ansehen. Und Architekten könnten die Standards unternehmensweit durchsetzen und aktualisieren, ohne die Teams damit zu belasten!
Wie kann ich sicher sein, dass alles sicher ist?
Eine der Herausforderungen bei allgemeineren Tools zum Erstellen von Images wie Dockerfiles ist, dass Sie beim Build praktisch alles tun können: Pakete installieren, mit ADD Dateien von beliebigen Webservern herunterladen und viele weitere Schritte ausführen, die überprüft und genau unter die Lupe genommen werden müssen. Bei Jib werden viele dieser Entscheidungen automatisch getroffen. Um sie zu überschreiben, müssen Sie die Maven-/Gradle-Konfiguration explizit ändern. Diese Änderungen sind bei einer Code-Review klar erkennbar – genau wie Änderungen an Abhängigkeiten oder anderen Build-Konfigurationen.
Für Sicherheits-Scans bietet Snyk Container eine großartige Funktion: Empfehlungen für verschiedene Basis-Images mit weniger Schwachstellen. Ab sofort funktioniert diese Funktion auch ohne Dockerfile, aus dem hervorgeht, welches Basis-Image Ihr Image verwendet. So können Sie die Empfehlungen auch für mit Jib oder einem anderen Tool ohne Dockerfile erstellte Images nutzen.
Wenn Sie Ihre eigenen Java-Container prüfen möchten, erstellen Sie ein kostenloses Konto und erhalten Sie Anweisungen zur Installation des Snyk-Scan-Tools.
Für dieses Beispiel habe ich als Basis-Image openjdk:8u121-jre festgelegt, mvn package ausgeführt und das Image auf meinen Laptop heruntergeladen. Jetzt führe ich einfach den Befehl snyk container test dafür aus.
Wie Sie sehen, hat der Scan automatisch openjdk:8u181-jre-stretch als Basis-Image erkannt (dies ist ein Alias-Tag für openjdk:8u181-jre) und 410 Schwachstellen gemeldet. Außerdem wurden alternative Basis-Images empfohlen: von einem Upgrade auf eine kleinere Versionsstufe bis zum neuesten Tag openjdk:8-jre, der etwa halb so viele Probleme aufweist, bis hin zu neueren JVMs ohne bekannte Schwachstellen.
Sie sehen also: Wir können nicht nur feststellen, welche Schwachstellen in unserem Image vorhanden sind, sondern erhalten auch Empfehlungen, wie sie sich mit einem neueren Basis-Image beheben lassen – ganz ohne eine einzige Zeile Dockerfile-Code zu schreiben.
Zusammenfassung
Wie Sie sehen, kann Jib Entwicklern die Containerisierung ihrer Java-Anwendungen erheblich erleichtern, da sie weder die Dockerfile-Syntax erlernen noch unbekannte Tools installieren müssen. Außerdem unterstützt es Architekten dabei, Standards mithilfe der vertrauten Projekt-Hierarchien von Maven oder Gradle zu verwalten. Da Jib OCI-konforme Images erstellt, können Sie außerdem Industriestandard-Tools wie den Snyk-Container-Scanner verwenden, um Ihre Anwendung zu prüfen, bereitzustellen und auszuführen.
Wenn Sie auch ein Nicht-Java-Projekt unterstützen, kommen andere Tools in diesem Bereich infrage, etwa Buildah, Bazel, Earthly und verschiedene BuildKit-bezogene Projekte. Außerdem hat mein Kollege Pas Apicella kürzlich einen Artikel über Cloud Native Build Packs veröffentlicht, den Sie unbedingt lesen sollten.
Vergessen Sie nicht, ein kostenloses Konto zu erstellen und noch heute mit dem Testen Ihrer Container, Open-Source-Abhängigkeiten und Ihres IaC-Codes zu beginnen!
Sichern Sie Ihre Infrastruktur an der Quelle
Snyk automatisiert die IaC-Sicherheit und Compliance in Workflows und erkennt abweichende sowie fehlende Ressourcen.
