Den neuen Java-Release-Zyklus beherrschen – Teil 2

Java ganz konkret: Wie man sich auf den neuen JDK-Release-Zyklus richtig vorbereitet

Sven Ruppert

© Shutterstock / Maksim Kabakou

Seit einiger Zeit ist bekannt, dass Oracle die Zyklen, in denen das JDK veröffentlicht wird, geändert hat. Nun ist es soweit, dass die Auswirkungen auch für die meisten Projekte spürbar werden. Wie kann man sich auf die nun doch recht schnelle Abfolge neuer JDKs in der Entwicklung einstellen? Mit welchen Ansätzen kann man sich die Arbeit erleichtern, welche Werkzeuge werden Erleichterung verschaffen?

Im ersten Teil haben wir uns angesehen, wie wir ein lokales Projekt innerhalb eines Docker-Containers übersetzen können und somit das Ziel „JDK“ nicht zwingend nativ auf der Entwicklungsplattform vorhalten müssen. Allerdings hatte dies bei der Verwendung von Maven den Nachteil, dass der Prozess recht lange dauern kann. Der Grund hierfür war, dass alle notwendignen Abhängigkeiten in den jeweils frisch erzeugten Container geladen werden mussten. Diesmal werden wir uns verschiedene Wege ansehen, wie man das Ganze beschleunigen kann.

So wurde es das letzte Mal gemacht

Um das Projekt in einem Docker-Container zu übersetzen, wurde der nachfolgende Befehl auf der Kommandozeile im Wurzelverzeichnis des Projekts ausgeführt.

docker run -it --rm --name compileJDK10 \
        -v "$(pwd)":/usr/src/mymaven \
        -w /usr/src/mymaven \
        svenruppert/maven-3.5-jdk-zulu-10 \
        mvn clean install

Hiermit wurde das Projektverzeichnis in den Docker-Container eingebunden und der gewünschte Maven-Befehl ausgeführt.

Der einfache Weg

Viele Jahre lang wurden die JDK-Releases, wenn man von den
Kommen wir nun zum einfachsten Weg: Wenn man die Abhängigkeiten, die in einem Projekt verwendet werden, nicht jedes Mal neu aus dem Internet ziehen möchte, so kann man das lokale .m2-Verzeichnis als eine Art Cache verwenden. Hierzu muss man lediglich das .m2-Verzeichnis ebenfalls in den neu erzeugten Container mit einbinden. Der in dem Container laufende Maven-Prozess sieht dieses Verzeichnis dann und verwendet es. Um dies zu erreichen, müssen wir lediglich den Befehl um die folgende Zeile erweitern.

-v "path/to/m2":/root/.m2/

Alles zusammen ergibt dann:

docker run --rm --name compile \
    -v "path/to/m2":/root/.m2/ \ 
    -v "$(pwd)":/usr/src/mymaven \
    -w /usr/src/mymaven \
    svenruppert/maven-3.5-jdk-openjdk-11 \
    mvn clean install

Nun gibt es aber einige Nachteile bei dieser Lösung. Gehen wir davon aus, dass wir zur selben Zeit auch lokal mit Maven arbeiten werden, so kann es sein, dass sich die beiden Prozesse ins Gehege kommen. Das kann immer dann passieren, wenn mit lokalen SNAPSHOTS gearbeitet wird. Diese Lösung kann also lediglich dann ohne Komplikationen verwendet werden, wenn man immer nur einen Prozess darauf laufen lässt.

Cachen von Release Versionen

Um nun die Möglichkeit zu bekommen, gleichzeitig mit verschiedenen Maven-Prozessen auf dem Rechner arbeiten zu können, muss man also das .m2-Verzeichnis jeweils für den Prozess exklusiv zur Verfügung stellen. Wir wollen nicht damit beginnen, das Verzeichnis abwechselnd zu sperren. Man kann daher den folgenden Weg gehen: Anstelle der einfachen Bereitstellung desselben Verzeichnisses kann man mit einer Kopie arbeiten. Das Verzeichnis wird also nicht in den Container eingebunden sondern kopiert. Dadurch kann der in dem Container laufende Prozess sich frei in dem Repository bewegen und Manipulationen vornehmen.

-v "path/to/m2":/root/.m2/

Alles zusammen ergibt dann:

docker run --rm --name compile \
    -v "path/to/m2":/root/.m2/ \ 
    -v "$(pwd)":/usr/src/mymaven \
    -w /usr/src/mymaven \
    svenruppert/maven-3.5-jdk-openjdk-11 \
    mvn clean install

Auch diese Methode hat einen Nachteil. Es wird immer die aktuelle Version dieses Verzeichnisses verwendet. Ist es dieselbe Version, die auch bei der lokalen Entwicklung verwendet wird, sind mit hoher Wahrscheinlichkeit auch SNAPSHOTS im Repository vorhanden. Das ist natürlich nicht gewünscht.

Zentraler Nexus

Um die SNAPSHOTS aus dem Repository zu entfernen, muss eine reine Version aufgebaut werden. Wenn man sich genau ansieht, was am längsten dauert, so wird es bei den meisten der Download aus dem Internet sein. Hier bietet es sich an, genau diese Artefakte in einem eigenen Repository vorzuhalten. In diesem Beispiel habe ich mich für den Nexus entschieden, da er frei verfügbar ist und keine großen Konfigurationen benötigt, um in Betrieb genommen zu werden.

Aber egal welcher Repository-Manager zum Einsatz kommt, der Mirror muss in der Datei settings.xml definiert werden. Dafür kann man genau denselben nehmen, der auch für die lokale Entwicklung verwendet wird. Hierzu erzeut man eine settings.xml-Datei, in der die sicherheitsrelevanten Elemente entfernt worden sind, und fügt diese dem Projekt hinzu. Bei dem Bau des Containers kann man dann diese Datei anstelle des .m2-Verzeichnisses hinzufügen.

docker run --rm --name compile \
    -v "$(pwd)/_data/nexus/settings.xml":/root/.m2/settings.xml \ 
    -v "$(pwd)":/usr/src/mymaven \
    -w /usr/src/mymaven \
    svenruppert/maven-3.5-jdk-openjdk-11 \
    mvn clean install

Alle Abhängigkeiten werden jetzt aus dem im lokalen Netzwerk vorhandenen Repository geholt. Im Vergleich zu der vorherigen Version allerdings nur die Abhängigkeiten, die für diesen Build benötigt werden. Auch SNAPSHOTS sind hiervon unbetroffen. Letzteres natürlich nur dann, wenn im Repository keine eingespielt worden sind. An der Stelle kann man davon ausgehen, dass der Build ausschließlich auf stabilen Versionen basiert. Die Geschwindigkeit ist abhängig von der lokalen Netzwerkgeschwindigkeit, die hoffentlich signifikant höher ist als die Internetgeschwindigkeit.

Docker-based Nexus

Auch das kopieren im eigenen Netzwerk kann unter Umständen noch zu längeren Wartezeiten führen. Um diese zu umgehen, muss man alles auf den eigenen Rechner verlagern. Das ist dank Docker ebenfalls ein sehr einfaches Unterfangen geworden. Hier wird lediglich der Nexus in einem lokalen Docker-Container vorgehalten und die für den Build-Prozess eingesetzten Docker-Container holen sich die Maven-Artefakte von dieser Instanz. Der Vorgang ist dann nochmals schneller, hat jedoch zusätzlich die positive Eigenschaft, dass keine SNAPSHOTS enthalten sind. Um nun einen Nexus im eigenen Docker Host bereitzustellen, ist lediglich ein kurzer Befehl notwendig.

docker run -d -p 8081:8081 --name nexus sonatype/nexus3

Der dann laufende Nexus hat standardmäßig einen Proxy für Maven Central definiert und kann sofort zum Einsatz kommen. Weitere Repositorys, die von dem Nexus vorgehalten werden sollen, müssen allerdings von Hand defniert werden. Man kann schließlich einen eigenen, auf dem Rechner unabhängig laufenden Nexus zur Beschleunigung verwenden.

Dependend Builds

Kommen wir zu der Situation, in der mehr als ein Build vorhanden ist, diese Builds allerdings in einer direkten SNAPSHOT-Abhängigkeit stehen. Es soll also der erste Build einen SNAPSHOT erzeugen, der für den darauf folgenden Build zur Verfügung gestellt werden muss. Auf jeden Fall kann/muss man hierbei sicherstellen, dass die jeweiligen Abhängigkeiten sauber aufeinander aufbauen.

Manueller Ansatz

Beim manuellen Ansatz wird ein Verzeichnis (.m2/repository/) erzeugt, das dann bei den einzelnen Containern eingebunden wird. Der Befehl mvn clean install kann genutzt werden, um die erzeugten Artefakte in diesem Verzeichnis abzulegen. Wird genau dieses Verzeichnis wieder verwendet, stehen die SNAPSHOTS zur Verfügung. Letztendlich sind wir wieder bei einer der ersten Versionen, die wir angedacht hatten, nur dass diesmal ein frisches Verzeichnis pro Pipeline angelegt wird.

Nexus for the build

Wenn man an dieser Stelle die aufeinander aufbauenden Builds auf verschiedene Rechner verteilen möchte, muss man wieder mit einem Repository arbeiten, das von allen beteiligten Knoten erreichbar ist. Es muss eine Nexus-Instanz pro Pipeline erzeugt werden.

Vorhandene Werkzeuge

Relativ schnell erreicht man eine Stelle, an der klar wird, dass der manuelle Aufwand zu hoch sein wird. Daher stellt sich die Frage, mit welchen Werkzeugen nun gearbeitet werden kann. Es gibt hier verschiedene Ansätze, die wir in den nächsten Teilen betrachten werden.

Fazit

Wir haben uns angesehen, wie der Build-Prozess beschleunigt werden kann. Wir sind verschiedene Wege gegangen und manchmal reicht der einfache Weg aus, um zum Ziel zu kommen:
Nicht immer muss es eine große komplexe Infrastruktur sein. Aber selbst wenn verschiedene Teile benötigt werden, kann mittels Docker recht einfach und zügig so manche Konstruktion abgebildet werden. Auch hier gibt es wieder Potential zur Optimierung, was wir im nächsten Teil weiter angehen werden.

Für die ungeduldigen Leser sei hier schon mal auf die Sourcen hingewiesen, die auf GitHub verfügbar sind.

Wer Fragen und Anmerkungen hat, kann sich gerne per Twitter (@SvenRuppert) oder direkt per Mail an mich wenden.

Happy Coding!

Lesen Sie auch:

Java 11, LTS und der ganze Rest: Bleibt Java kostenlos?

Verwandte Themen:

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: