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

1 Kommentar

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. mvn 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. mvn build-helper:parse-version versions:set -DnewVersion=${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-${BUILD_NUMBER}
  4. Jenkins baut die Software
  5. mvn clean package
  6. Artefakte in Repository deployen
  7. mvn deploy
  8. Version im SCM taggen
  9. mvn 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:

job(type: Maven) {
    name("batch-boot-demo")
    triggers { scm("*/5 * * * *") }
    scm {
		git {
		    remote {
		        url("https://github.com/codecentric/spring-samples")
		    }
		    createTag(false)
		}
	}
	rootPOM("batch-boot-demo/pom.xml")
	goals("clean package")
	wrappers {
		preBuildCleanup()
		release {
			preBuildSteps {
				maven {
					mavenInstallation("Maven 3.0.4")
					rootPOM("${projectName}/pom.xml")
					goals("build-helper:parse-version")
					goals("versions:set")
					property("newVersion", "\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-\${BUILD_NUMBER}")
				}
			}
			postSuccessfulBuildSteps {
				maven {
				    rootPOM("${projectName}/pom.xml")
					goals("deploy")
				}
				maven {
					rootPOM("${projectName}/pom.xml")
					goals("scm:tag")
				}
				downstreamParameterized {
					trigger("deploy-application") {
						predefinedProp("STAGE", "development")
					}
				}
			}
		}
	}		
	publishers {
		groovyPostBuild("manager.addShortText(manager.build.getEnvironment(manager.listener)[\'POM_VERSION\'])")
	}		
}

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.

job_view

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.

promotions

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

promotions {
	promotion("Development") {
		icon("star-red")
		conditions {
			manual('')
		}
		actions {
			downstreamParameterized {
				trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
					predefinedProp("ENVIRONMENT","dev.microservice.com")
					predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
					predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
				}
			}
		}
	}
	promotion("QA") {
		icon("star-yellow")
		conditions {
			manual('')
			upstream("Development")
		}
		actions {
			downstreamParameterized {
				trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
					predefinedProp("ENVIRONMENT","qa.microservice.com")
					predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
					predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
				}
			}
		}
	}	
	promotion("Production") {
		icon("star-green")
		conditions {
			manual('prod_admin')
			upstream("QA")
		}
		actions {
			downstreamParameterized {
				trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
					predefinedProp("ENVIRONMENT","prod.microservice.com")
					predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
					predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
				}
			}
		}
	}							
}

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.

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

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:

codecentric:spring-samples

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

readFileFromWorkspace("projects.txt").eachLine {
 
	def repository = it.split(":")[0]
	def projectName = it.split(":")[1]
 
	[...]
 
}

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?

Dennis Schulte

Dennis Schulte ist seit 2009 als Senior IT Consultant bei der codecentric AG tätig. Er unterstützt seine Kunden insbesondere im Bereich Enterprise-Architekturen, Microservices, Continuous Delivery, DevOps und der Optimierung von IT-Prozessen. Aufgrund seiner langjährigen Erfahrung als Architekt und Entwickler verfügt er über ein umfassendes Wissen im Bereich Java und Open-Source-Technologien. Seine Projektschwerpunkte liegen in der Architekturberatung und der Durchführung von Projekten in Enterprise-Unternehmen.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • Jonas Hecht

    10. Mai 2016 von Jonas Hecht

    Glück gehabt – wir wollten gerade auf die Job-DSL umstellen und gleichzeitig das Promoted Builds Plugin weiternutzen: Ab Version 2.26 (gestern released) ist der Support drin 🙂

Kommentieren

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