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:
- build-application
- release-application
- deploy-application-to-dev
- run-tests
- 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:
- Automatisierte Generierung eines Jenkins-Jobs, wenn der Benutzer ein neues Repository bzw. Modul ins SCM einfügt
- 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:
- Zu viele redundante Schritte (z.B. 3 volle clean/compile/test Zyklen!!)
- 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:
- Jenkins startet den Build bei jedem SCM-Commit
- Jenkins baut die Software
1mvn clean package
Der Releaseprozess ist ebenfalls sehr einfach:
- Benutzer löst Releasebuild aus
- SNAPSHOT-Version in pom.xml wird durch Releaseversion ersetzt
- Jenkins baut die Software
- Artefakte in Repository deployen
- Version im SCM taggen
1mvn build-helper:parse-version versions:set -DnewVersion=${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-${BUILD_NUMBER}
1mvn clean package
1mvn deploy
1mvn scm:tag
Einzelnen Job generieren
Um die Jobs nun automatisiert zu generieren, benötigen wir das Job DSL Plugin im Jenkins. Dies kann durch den normalen Jenkins-Plugin-Mechanismus installiert werden. Dieses Plugin ist wirklich zu empfehlen. Ich nutze es in einigen Projekten seit einem Jahr um Hunderte von Jobs zu generieren und wir hatten bisher keine nennenswerten Probleme damit. Auch wenn die Benutzung wirklich sehr einfach ist, würde ich für den Start auf jeden Fall dieses detaillierte Tutorial empfehlen, um die generelle Funktionsweise zu verstehen. Der in dem Tutorial erwähnte Seed-Job heißt hier „job-generator“ und enthält im ersten Schritt folgendes DSL-Skript:
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}
Nun ist es sehr wichtig dem Seed-Job einen SCM-Trigger zu verpassen, damit der Job auch jedesmal losläuft, wenn im SCM ein Projekt eingecheckt oder auch gelöscht wird. Das Job DSL Plugin ist sogar so intelligent, dass es bestehende Jenkins Jobs entfernt, wenn das zugehörige SCM-Projekt gelöscht wurde.
Was denkt ihr? Nutzt ihr bereits das Job DSL Plugin ?
Weitere Beiträge
von Dennis Schulte
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
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.
Blog-Autor*in
Dennis Schulte
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.