Schnellere Jenkins Pipelines durch den Maven Daemon

01.06.2021

Als Entwickler kennt man das Problem das Builds, vor allem in größeren Java Projekten, sehr viel Zeit in Anspruch nehmen. Das Maven Daemon Projekt ist eine einfache Möglichkeit, Builds z.B. in einer Jenkins Pipeline deutlich effektiver auszuführen.

Der Maven Daemon verwendet dazu Techniken, die bereits von Gradle bekannt sind. Dieser erreicht das unter anderem durch das Verwenden eines langlebigen Daemon Prozesses, welcher nur einmal gestartet und immer wiederverwendet wird. Somit muss die JVM, auf der die eigentlichen Builds laufen, nicht für jeden Build neu gestartet werden. Dadurch fallen Startzeiten und Laufzeitoptimierungszeiten (JIT) weg. Der Daemon kann auf https://github.com/mvndaemon/mvnd mit dazugehöriger Dokumentation und Schritten zur Installation auf einem lokalen System gefunden werden.

In diesem Artikel legen wir den Fokus auf die Integration in Jenkins.

Installation

Der Maven Daemon kann wie von Maven schon gewohnt in Jenkins über die Hilfsprogramme konfiguriert werden. Diese sind zu finden unter: Dashboard -> Jenkins verwalten -> Konfiguration der Hilfsprogramme. Dort angekommen kann unter dem Reiter „Maven“ eine neue Konfiguration angelegt oder eine bestehende Konfiguration angepasst werden.

Konfiguration Maven Daemon Jenkins Hilfsprogramme
Abb.1: Konfiguration Maven Daemon Jenkins Hilfsprogramme

 

  1. Durch einen Klick auf „Maven hinzufügen“ kann eine neue Maven Installation eingebunden werden. Hier müssen Sie das Standard Installationsverfahren Apache entfernen und ein neues Installationsverfahren hinzufügen.

  2. Für das Installationsverfahren wählen Sie „Entpacke *.zip / *.tar.gz-Archiv“ aus.

  3. Der Name ist beliebig wählbar und dient nur zur Identifikation des Tools.

  4. Die Download-URL finden Sie unter https://github.com/mvndaemon/mvnd/releases.

Abschließend speichern Sie die Konfiguration und der Maven Daemon kann über den Befehl mvnd ausgeführt werden.

Beispiel Scripted Pipeline:

node(){
    stage("Install"){
        sh('mvnd install')
    }
}

Wechsel zwischen Maven und Maven Daemon

Eine Voraussetzung, um den Maven Daemon in internen Projekten zu verwenden, war, dass eine Möglichkeit existieren soll, die eine Auswahl des Ausführungsmodus erlaubt. Da der Maven Befehl mit verschiedenen Parametern ausgeführt wird, wird in einer Shared Library ein groovy Script definiert, um die Ausführung von Maven an einem zentralen Platz zu halten und dieselbe Definition wiederzuverwenden. Im Folgenden wird ein minimales Beispiel aufgezeigt, zur Unterscheidung, welcher der zwei Befehle ausgeführt werden soll.

In der Pipeline wird eine Umgebungsvariable gesetzt zur Unterscheidung zwischen den zwei Befehlen.

Beispiel Scripted Pipeline:

// Include shared library with the key 'test-daemon'. Ensure the share library is configured in jenkins.
library "project-commons"
// --

// Pipeline configurations
properties([
        parameters([booleanParam(defaultValue: false, description: "Set this flag to true if you want to use the Maven Daemon instead of Maven.", name: "MVND")])
])
// --

    node(){
        gitCheckoutStage()
        
        env.setProperty('USE_MVND', params.MVND)
        
        stage('Execute Maven Stage'){
            executeMaven('-version')          
        }
    }

Das Script prüft die gesetzte Umgebungsvariable und gibt den zu verwendeten Befehl als Script Anweisung auf der Shell zurück.
Beispiel executeMaven.groovy:

/**
 * Executes Maven or Maven Daemon depending on the environment variable USE_MVND.
 * 
 * @param args arguments to use
 */
def call(String args) {
    withEnv(install()){
        def mode = this.env.USE_MVND == "true" ? "mvnd" : "mvn"
        return sh(script: "${mode} ${args}")
    }
}

/**
 * Install maven.
 *
 * @param jdkTool jdk jenkins tool name
 * @param mvnTool maven jenkins tool name
 * @param mvndTool maven daemon jenkins tool name
 *
 * @return returns a list of the tool environment paths
 */
List install(String jdkTool = "java11", String mvnTool = "maven", String mvndTool = "maven-daemon") {
    List envs = [
        "PATH+JAVA=${getToolPath(jdkTool)}",
        "JAVA_HOME=${tool(jdkTool)}",
        "PATH+MAVEN=${getToolPath(mvnTool)}",
        "PATH+MAVEN-DAEMON=${getToolPath(mvndTool)}",
        "JAVA_TOOL_OPTIONS=-XX:+UseContainerSupport"
    ]
    return envs
}

private String getToolPath(String name) {
    return tool(name) + "/bin"
}

Laufzeiten Vergleich

Für die Ermittlung der Laufzeit wurde ein Maven Multi Module Projekt angelegt sowie Jenkins und die Pipeline entsprechend konfiguriert. Die Pipeline besteht aus drei Stages.

Stage-1 mvn/mvnd clean package: Bei erstmaliger Ausführung wird der Daemon gestartet der für folgende Aufrufe wiederverwendet wird.

Stage-2 mvn/mvnd clean package: Da der Daemon bereits gestartet ist weist die darauffolgende Ausführung, desselben Befehls, eine deutlich kürzere Laufzeit auf.

Stage-3 mvn/mvnd clean package -DskipTests: Die Ausführung ohne Softwaretests benötigt im Vergleich auffallend weniger Zeit. Hierbei erkennt man das die Ausführung der eigentlichen Tests einen Großteil der Laufzeit benötigt, somit ist zu sehen, dass der Daemon eine geringe Auswirkung auf die Laufzeit der Softwaretests hat.

Benchmark mvn-mvnd
Abb. 2: Diagramm Benchmark mvn-mvnd
Fazit

Durch die recht einfache Einbindung in eine Jenkins Pipeline, kann eine einfache Art zur Laufzeitersparnis erreicht werden. Im Vergleich wurde die Laufzeit auf über die Hälfte verkürzt. Der große Vorteil des Daemon ist zudem, dass die Anwendung nicht von der bekannten Maven Ausführung abweicht und somit auch alle Maven Parameter akzeptiert.

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*