Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

Continuous Delivery für Microservices mit Jenkins und dem Job DSL Plugin

8.1.2015 | 5 Minuten Lesezeit

In klassischen monolithischen Architekturen ist die Zahl der Release-Artefakte gering und somit ist es auch relativ einfach die zugehörigen Jenkins-Jobs manuell zu erstellen und zu pflegen. Aber gerade in einer Microservice-Architektur steigt die Zahl der Deployment-Einheiten drastisch an und man benötigt hier nun definitiv eine automatisierte Lösung. Auch in Enterprise-Umgebungen mit sehr vielen gleichartigen Build-Jobs macht es nicht wirklich Sinn alle Jobs manuell anzulegen. Darüber hinaus sieht man bei vielen Jenkins-Installation separate Jobs für das Bauen, Releasen, Deployen und zum Testen der Applikationen:

Eine Continuous Delivery pipeline für eine monolithische Anwendung sieht dann z.B. so aus:

  1. build-application
  2. release-application
  3. deploy-application-to-dev
  4. run-tests
  5. deploy-application-to-qa

Ziele

Um den speziellen Anforderungen von Microservices in Bezug auf das Build- und Release-Management gerecht zu werden, zeige ich in diesem Blogartikel einen Weg, wie man die folgenden Ziele erreichen kann:

  1. Automatisierte Generierung eines Jenkins-Jobs, wenn der Benutzer ein neues Repository bzw. Modul ins SCM einfügt
  2. Nur noch 1 Jenkins-Job pro Release-Artefakt, der in der Lage ist den Build, das Release und das Deployment der Anwendung bzw. des Microservice durchzuführen

Build und Release

Bevor wir uns das Job DSL Plugin näher anschauen, möchte ich zunächst meinen favorisierten Weg für das Releasing von Anwendungen mit Maven zeigen. Vielleicht sind einige von euch auch nicht ganz so zufrieden mit dem Maven Release Plugin . Ich sehe hier zwei größere Probleme:

  1. Zu viele redundante Schritte (z.B. 3 volle clean/compile/test Zyklen!!)
  2. Instabilität – das Plugin modifiziert die pom.xml im SCM, so dass oft manuelle Schritte notwendig sind, um die Änderungen rückgängig zu machen, wenn vorher das Release schiefgelaufen ist

Für weitere Informationen zu diesem Thema kann ich die Blogartikel von Axel Fontaine empfehlen. Er zeigt ebenfalls einen alternativen Weg auf ein Release zu erstellen, den wir hier in diesem Artikel auch beschreiten werden.

Der Build ist sehr einfach:

  1. Jenkins startet den Build bei jedem SCM-Commit
  2. Jenkins baut die Software
  3. 1mvn clean package

Der Releaseprozess ist ebenfalls sehr einfach:

  1. Benutzer löst Releasebuild aus
  2. SNAPSHOT-Version in pom.xml wird durch Releaseversion ersetzt
  3. 1mvn build-helper:parse-version versions:set -DnewVersion=${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-${BUILD_NUMBER}
  4. Jenkins baut die Software
  5. 1mvn clean package
  6. Artefakte in Repository deployen
  7. 1mvn deploy
  8. Version im SCM taggen
  9. 1mvn scm:tag
    1job(type: Maven) {
    2    name("batch-boot-demo")
    3    triggers { scm("*/5 * * * *") }
    4    scm {
    5        git {
    6            remote {
    7                url("https://github.com/codecentric/spring-samples")
    8            }
    9            createTag(false)
    10        }
    11    }
    12    rootPOM("batch-boot-demo/pom.xml")
    13    goals("clean package")
    14    wrappers {
    15        preBuildCleanup()
    16        release {
    17            preBuildSteps {
    18                maven {
    19                    mavenInstallation("Maven 3.0.4")
    20                    rootPOM("${projectName}/pom.xml")
    21                    goals("build-helper:parse-version")
    22                    goals("versions:set")
    23                    property("newVersion", "\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-\${BUILD_NUMBER}")
    24                }
    25            }
    26            postSuccessfulBuildSteps {
    27                maven {
    28                    rootPOM("${projectName}/pom.xml")
    29                    goals("deploy")
    30                }
    31                maven {
    32                    rootPOM("${projectName}/pom.xml")
    33                    goals("scm:tag")
    34                }
    35                downstreamParameterized {
    36                    trigger("deploy-application") {
    37                        predefinedProp("STAGE", "development")
    38                    }
    39                }
    40            }
    41        }
    42    }		
    43    publishers {
    44        groovyPostBuild("manager.addShortText(manager.build.getEnvironment(manager.listener)[\'POM_VERSION\'])")
    45    }		
    46}

    Das obige DSL-Skript enthält die schon gezeigten Build- und Release-Schritte aus dem ersten Teil des Artikels. Um den Releaseprozess in einen Buildjob zu integrieren, nutzen wir das Jenkins Release Plugin , welches einen neuen Menupunkt in die Job UI einfügt (siehe Screenshot). Das „groovyPostBuild“-Element fügt die Versionsnummer der Buildhistorie hinzu (siehe Screenshot), so dass man sie direkt sehen kann, wenn man die Jobansicht öffnet. Um das Ganze direkt mal auszuprobieren, habe ich hier ein Docker-Image erstellt, was alle wichtigen Dinge enthält. Die notwendigen Installationsanweisungen befinden sich ebenfalls bei Github. Alternativ kann man natürlich auch schon einen evtl. bestehenden Jenkins nutzen und die fehlenden Plugins noch nachinstallieren.

    Deployment

    Aktuell ist es nun möglich die Artefakte zu bauen und zu releasen, aber das eigentliche Deployment auf das Zielsystem fehlt noch. Da wir keine separaten Deployment-Jobs für jedes Artefakt haben wollen, nutzen wir dafür das Promoted Builds Plugin . Dieses Plugin führt den Begriff der „Promotion“ ein. Ein „promoted Build“ ist ein erfolgreicher Build, der zusätzliche Kriterien erfüllt. In unserem Fall handelt es sich dabei um das Erreichen einer bestimmten Umgebung. Diese „promoted Builds“ bekommen dann innerhalb der Buildhistorie ein kleines farbiges Sternchen für jede Umgebung (siehe Screenshot unten), wo die Anwendung mit der jeweiligen Version installiert wurde.

    Um die Promotion-Stufen auch mitzugenerieren, wird folgendes DSL-Skript dem Hauptskript von oben hinzugefügt (Siehe auch job-dsl-example.groovy ):

    1promotions {
    2    promotion("Development") {
    3        icon("star-red")
    4        conditions {
    5            manual('')
    6        }
    7        actions {
    8            downstreamParameterized {
    9                trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
    10                    predefinedProp("ENVIRONMENT","dev.microservice.com")
    11                    predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
    12                    predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
    13                }
    14            }
    15        }
    16    }
    17    promotion("QA") {
    18        icon("star-yellow")
    19        conditions {
    20            manual('')
    21            upstream("Development")
    22        }
    23        actions {
    24            downstreamParameterized {
    25                trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
    26                    predefinedProp("ENVIRONMENT","qa.microservice.com")
    27                    predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
    28                    predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
    29                }
    30            }
    31        }
    32    }	
    33    promotion("Production") {
    34        icon("star-green")
    35        conditions {
    36            manual('prod_admin')
    37            upstream("QA")
    38        }
    39        actions {
    40            downstreamParameterized {
    41                trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
    42                    predefinedProp("ENVIRONMENT","prod.microservice.com")
    43                    predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
    44                    predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
    45                }
    46            }
    47        }
    48    }							
    49}

    Mit Hilfe des Elements „manual(‚user‘)“ ist es möglich die Ausführung einer Promotion auf bestimmte User oder auch Gruppen zu beschränken. Insbesondere beim Produktionsdeployment macht das Sinn ;-). Wenn die Promotion manuell ausgelöst wird, dann werden die konfigurierten Aktionen durchgeführt. In unserem Szenario wird letztendlich nur der Deploy-Job angestoßen, der die Anwendung auf die Maschine(n) installiert. Nach der erfolgreichen Ausführung und auch nur dann erhält der korrespondierende Build ein farbiges Sternchen. Mit dem „upstream(‚promotionName‘)“-Element kann man zusätzlich noch die Promotionstufen voneinaner abhängig machen, z.B. ist ein Produktionsdeployment nur dann erlaubt, wenn das Artefakt vorher auch in QA installiert wurde. Das alles funktioniert schon sehr gut, aber leider gibt es hier auch eine schlechte Nachricht. Das Promoted Builds Plugin wird aktuell noch nicht offiziell vom Job DSL Plugin unterstützt. Mein Kollege Thomas hat hier schon sehr gute Arbeit geleistet, aber leider wurde der Pull Request bis heute noch nicht gemerged. Es ist jedoch schon ein Alpha-Release vorhanden, welches in der Zwischenzeit genutzt werden kann, um schonmal zu starten. Man kann nur hoffen, dass es hier bald bessere Neuigkeiten gibt. Eine Alternative dazu ist auch, dass man die Promotions in einem Templatejob definiert und diesen dann mit dem using-Element innerhalb der Job DSL referenziert.

    Mehrere Jobs generieren

    Im ersten Schritt oben wird bisher nur ein einziger Job generiert („batch-boot-demo“). Als nächstes wollen wir nun direkt mehrere Jobs automatisiert generieren. In diesem Beispiel nutzen wir ein Repository bei Github welches einige Spring Projekte enthält. Mit der Github API ist es sehr einfach die Inhalte des Repos auszulesen.

    1def repository = 'codecentric/spring-samples'
    2def contentApi = new URL("https://api.github.com/repos/${repository}/contents")
    3def projects = new groovy.json.JsonSlurper().parse(contentApi.newReader())
    4projects.each { 
    5    def projectName = it.name
    6    job(type: Maven) {
    7        name("${projectName}")
    8        triggers { scm("*/5 * * * *") }
    9 
    10        [...]
    11    }	
    12}

    Alternativ kann man sich natürlich auch ein kleines Shell-Skript schreiben, welches sich direkt mit der SCM-API unterhält und dann die Inhalte in eine Datei umleitet:

    1codecentric:spring-samples

    Innerhalb des DSL-Skriptes kann dann auf die Datei zugegriffen werden:

    1readFileFromWorkspace("projects.txt").eachLine {
    2 
    3    def repository = it.split(":")[0]
    4    def projectName = it.split(":")[1]
    5 
    6    [...]
    7 
    8}

Beitrag teilen

Gefällt mir

0

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.