Blogartikel

09.05.2019

EcoSystem

Continuous Delivery nach Sonatype Nexus mit Jenkins und dem Cloudogu EcoSystem

Um der steigenden Anforderung hinsichtlich der Entwicklungsgeschwindigkeit neuer Features gerecht zu werden, müssen diese immer schneller implementiert werden. Doch das ist nur eine Seite der Medaille, schließlich müssen diese Features auch in Produktion gebracht werden. Oft erfolgen Deployments manuell und sind damit fehleranfällig, binden Ressourcen und dauern möglicherweise lange. Als Lösung bietet sich die vollständige Automatisierung an – Continuous Delivery genannt.

Um der steigenden Anforderung hinsichtlich der Entwicklungsgeschwindigkeit neuer Features gerecht zu werden, müssen diese immer schneller implementiert werden. Doch das ist nur eine Seite der Medaille, schließlich müssen diese Features auch in Produktion gebracht werden. Oft erfolgen Deployments manuell und sind damit fehleranfällig, binden Ressourcen und dauern möglicherweise lange. Als Lösung bietet sich die vollständige Automatisierung an – Continuous Delivery genannt. Manchmal wird auch zwischen Continuous Delivery und Continuous Deployment unterschieden, allerdings ist das eine vielschichtige Debatte, die für den Kern des Artikels nicht entscheidend ist.

Continuous Delivery kann abhängig vom Artefakt-Typen verschiedene Bedeutungen haben und gleichermaßen kann man verschiedene Werkzeuge einsetzen, um es zu erreichen. Der Vorteil von Jenkins Pipelines ist die sehr einfache und modulare Realisierung. Dieser Artikel zeigt anhand zweier Praxis-Beispiele wie man Java Artefakte mit wenigen Zeilen Groovy Code kontinuierlich in Nexus Repositories ausliefert.

  • Das erste Beispiel zeigt, wie eine Open Source Java-Library zuerst beim Push auf ein GitHub Repository von Jenkins gebaut und getestet, anschließend mit SonarCloud analysiert und nach Maven Central deployed wird (siehe Abbildung 1).
  • Das zweite Beispiel entstammt dem Enterprise-Kontext und zeigt, wie mittels des Cloudogu EcoSystem eine Java-Webanwendung bei einem Push auf ein Git-Repository in SCM-Manager automatisch von Jenkins gebaut und getestet, von SonarQube analysiert und schließlich in ein privates Nexus Repository deployed wird (siehe Abbildung 2).

Obwohl die Infrastrukturen der Beispiele unterschiedlich sind, kann in beiden Fällen annähernd der gleiche Jenkins Pipeline-Code verwendet werden.

Cloud Services Workflow

Continuous Delivery nach Maven Central

Im Folgenden wird am Beispiel einer Open Source Java-Library gezeigt, wie man Continuous Delivery nach Maven Central realisieren kann. In nicht ganz 100 Zeilen der Jenkins Pipeline Groovy DSL (scripted Syntax) kann in einem Jenkinsfile eine ausgereifte Continuous Delivery Pipeline (mit Build, Unit- und Integration-Test, statischer Code Analyse und Deployment) realisiert werden. Listing 1 zeigt ein Beispiel, das auf dem Jenkinsfile der Library command-bus basiert. Aus Gründen der Übersichtlichkeit wurde es für diesen Artikel etwas gekürzt.

@Library('github.com/cloudogu/ces-build-lib@24c4f03')
import com.cloudogu.ces.cesbuildlib.*

node {

  Maven mvn = new MavenWrapper(this)

  catchError {

    stage('Checkout') {
      checkout scm
    }

    initMaven(mvn)

    stage('Build') {
      mvn 'clean install -DskipTests'
    }

    stage('Unit Test') {
      mvn 'test'
    }

    stage('Integration Test') {
      mvn 'verify -DskipUnitTests'
    }

    stage('Static Code Analysis') {
      def sonarQube = new SonarCloud(this, [sonarQubeEnv: 'sonarcloud.io-cloudogu'])

      sonarQube.analyzeWith(mvn)

      if (!sonarQube.waitForQualityGateWebhookToBeCalled()) {
        currentBuild.result ='UNSTABLE'
      }
    }

    stage('Deploy') {
      if (preconditionsForDeploymentFulfilled()) {

        mvn.useDeploymentRepository([id: 'ossrh', url: 'https://oss.sonatype.org/',
                                     credentialsId: 'mavenCentral-acccessToken', type: 'Nexus2'])

        mvn.setSignatureCredentials('mavenCentral-secretKey-asc-file', 'mavenCentral-secretKey-Passphrase')

        mvn.deployToNexusRepositoryWithStaging()
      }
    }
  }

  junit allowEmptyResults: true, testResults: '**/target/surefire-reports/TEST-*.xml, **/target/failsafe-reports/*.xml'

  mailIfStatusChanged(new Git(this).commitAuthorEmail)
}

boolean preconditionsForDeploymentFulfilled() {
  if (isBuildSuccessful() &&
      !isPullRequest() &&
      shouldBranchBeDeployed()) {
    return true
  } else {
    echo "Skipping deployment because of branch or build result: currentResult=${currentBuild.currentResult}, " +
      "result=${currentBuild.result}, branch=${env.BRANCH_NAME}."
    return false
  }
}

private boolean shouldBranchBeDeployed() {
  return env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'develop'
}

private boolean isBuildSuccessful() {
  currentBuild.currentResult == 'SUCCESS' &&
    // Build result == SUCCESS seems not to set be during pipeline execution.
    (currentBuild.result == null || currentBuild.result == 'SUCCESS')
}

void initMaven(Maven mvn) {

  if ("master".equals(env.BRANCH_NAME)) {

    echo "Building master branch"
    mvn.additionalArgs = "-DperformRelease"
    currentBuild.description = mvn.getVersion()
  }
}

Listing 1

Das Jenkinsfile enthält fünf so genannte Stages:

  • Checkout – holt den Code aus dem Git Repository,
  • Build, Unit Test und Integration Test – baut den Code mittels Maven und führt darauf Unit- und Integration-Tests aus,
  • Static Code Analysis – führt mit Hilfe von SonarQube (hier die öffentliche Instanz SonarCloud) eine statische Code Analyse aus und prüft die erfassten Metriken gegen ein definiertes Quality Goal
  • Deploy – deployt die Artefakte ins Nexus Repository (hier: die von Sonatype öffentlich bereitgestellte Instanz „Maven Central“) – sofern keine Fehler aufgetreten sind.

In der Pipeline wird die Jenkins Shared Library ces-build-lib eingesetzt. Sie enthält wiederverwendbare Bausteine für Maven (auch das Nexus Repository), Git und SonarQube. Mit den Jenkins Shared Libraries werden die gleichen Vorteile wie bei der Einbindung von Libraries bei der regulären Anwendungsentwicklung erzielt. Insbesondere übernimmt die ces-build-lib die Signierung und das Staging beim Deployment nach Maven Central sowie das Anstoßen der Kommentierung von Pull Requests durch SonarCloud und damit den größten Teil der Komplexität.

Alle Branches (einschließlich Feature Branches und Pull Requests) durchlaufen die automatisierte Qualitätskontrolle, die Deployments beschränken sich jedoch auf die stabileren Branches master und develop (an Git Flow angelehnt). Deployments werden durch einen Push auf diese angestoßen.

Damit die vorgestellte Pipeline in Jenkins ausgeführt werden kann, muss folgendes in Jenkins vorhanden sein:

  • Das "Pipeline: GitHub Groovy Libraries " Plugin. Es lädt Shared Library on the fly von GitHub.
  • Eine konfigurierte SonarQube Instanz mit der ID sonarcloud.io-cloudogu bei der URL, Authentication Token und Organization für SonarCloud konfiguriert sind. Damit Jenkins den von SonarQube asynchron berechneten Quality Gate Status erfährt, muss in SonarCloud ein Webhook auf die Jenkins-Instanz unter /sonarqube-webhook/ angelegt werden. Soll SonarCloud auch GitHub Issues kommentieren, muss in der GitHub Organization die SonarCloud Application für GitHub installiert sein.
  • Ein User Access Token für oss.sonatype.org (mittels dessen Releases aus dem Central Staging Repository nach Maven Central freigegeben werden) als Secret Text Credential mavenCentral-acccessToken.
  • Der private Key als ASC-Datei (als Secret File Credential mavenCentral-secretKey-asc-file). Mit diesem werden die Artefakte für Maven Central signiert.
  • Die Passphrase für den private Key (als Secret Text Credential mavenCentral-secretKey-Passphrase). Details finden sich in der Dokumentation zu Maven Central.

Continuous Delivery in ein On-Premises Nexus Repository

Cloudogu EcoSystem Workflow

Der gleiche Workflow wie für die Open Source Bibliothek kann auch im Enterprise-Umfeld auf eigener Infrastruktur verwendet werden. Abbildung 2 zeigt eine mögliche Toolauswahl: SCM-Manager statt GitHub, ein eigenes Nexus Repository statt Maven Central und SonarQube statt SonarCloud. Diese Tools und viele weitere bekommt man z.B. durch das Cloudogu EcoSystem per Knopfdruck vollautomatisiert vorkonfiguriert und mit Single Sign-on bereitgestellt.

In diesem Umfeld kann annähernd die gleiche Jenkins Pipeline wie im ersten Beispiel verwendet werden. Das zeigt das Jenkinsfile des zweiten Beispiels cloudogu/spring-petclinic (siehe Listing 2). Dabei handelt es sich um das prominente Beispiel einer Java-Webanwendung, das durch ein Jenkinsfile um Continuous Delivery erweitert wurde.

@Library('github.com/cloudogu/ces-build-lib@24c4f03')
import com.cloudogu.ces.cesbuildlib.*

properties([
        disableConcurrentBuilds()
])

node {

    String cesFqdn = findHostName()
    String cesUrl = "https://${cesFqdn}"

    Maven mvn = new MavenWrapper(this)

    catchError {

        stage('Checkout') {
            checkout scm
        }

        stage('Build') {
            mvn 'clean package -DskipTests'
        }

        String jacoco = "org.jacoco:jacoco-maven-plugin:0.8.1"
        parallel(
                test: {
                    stage('Unit Test') {
                        mvn "${jacoco}:prepare-agent test ${jacoco}:report"
                    }
                },
                integrationTest: {
                    stage('Integration Test') {
                        mvn "${jacoco}:prepare-agent-integration failsafe:integration-test failsafe:verify ${jacoco}:report-integration"
                    }
                }
        )

        stage('Static Code Analysis') {
            def sonarQube = new SonarQube(this, [usernamePassword: 'jenkins-sonar', sonarHostUrl: "${cesUrl}/sonar"])

            sonarQube.analyzeWith(mvn)
        }

        stage('Deploy') {
            mvn.useDeploymentRepository([id: cesFqdn, url:  "${cesUrl}/nexus", credentialsId: 'jenkins-nexus', type: 'Nexus3'])

            mvn.deployToNexusRepository()
        }
    }

    junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml'
}

Listing 2

Jedes Projekt ist verschieden, daher haben die Pipelines auch kleinere Unterschiede. Im Vergleich zum ersten Beispiel finden sich die folgenden relevanten Unterschiede:

  • Die URLs zu SonarQube und Nexus sind hier relativ zu Jenkins (einfacher wartbar dank des Cloudogu EcoSystem).
  • Unit- und Integration-Tests werden gleichzeitig ausgeführt, um schnelleres Feedback zu erhalten.
  • Bei der Deploy-Stage wird im Gegensatz zu Maven Central kein Staging-Repository verwendet.

Generell ist dieses Beispiel etwas einfacher, daher muss auf Jenkins auch weniger vorhanden sein, um die Pipeline auszuführen:

  • Das "Pipeline: GitHub Groovy Libraries" Plugin wie beim ersten Beispiel,
  • das Username With Password Credential jenkins-sonar, zu einem technischen User in SonarQube und
  • das Username With Password Credential jenkins-nexus, zu einem technischen User in Nexus.

Fazit

Dieser Artikel zeigt, wie einfach Continuous Delivery von Java-Artefakten in Nexus Repositories sein kann. Dabei können in unterschiedlichen Anwendungsfällen wie Open Source und Enterprise jeweils die gewünschten Tools eingesetzt werden. Selbstverständlich kann die Qualitätssicherung auch erweitert werden, beispielsweise durch die Schwachstellen-Analyse mit Sonatype Lifecycle oder Lasttests – eben DevSecOps. Unabhängig der konkreten Toolauswahl ermöglichen die Jenkins Pipelines auf elegante und übersichtliche Weise Continuous Delivery.

Tags