CI/CD Pipeline & Integrationstests auf OpenShift – Teil 3/3

Angela Stempfel

Im zweiten Teil der Blogpost-Serie haben wir euch gezeigt, wie eine Applikation auf OpenShift deployed wird. Der dritte Teil geht nun auf die CI/CD Integration bzw. unsere Pipeline ein, welche das Deployment und die Tests automatisiert. Angela Stempfel und Christoph Raaflaub berichten.

Workflow und Systemübersicht

Verschiedene Systeme werden benötigt, um den Vorgang zu automatisieren. In der folgenden Grafik wird veranschaulicht, wie diese zusammenhängen.

Unser Workflow funktioniert wie folgt:

  1. Der Source Code wird mit Gitlab verwaltet und bei jedem Commit wird ein Build auf dem Jenkins Buildserver getriggert
  2. Der Source Code wird kompiliert, die benötigten Dependencies werden vom Artifactory geholt
  3. Die erstellten jar Files werden ins Artifactory gepushed
  4. Die Docker Images werden erstellt und ebenfalls ins Artifactory gepushed
  5. Mit dem OC-Tool werden auf OpenShift Projekte für das Testing erstellt
  6. Deployment von FMSx auf OpenShift mit den Docker Images aus dem Artifactory

Die FMSx Delivery Pipeline

Den Workflow haben wir mit einer Jenkins Pipeline implementiert. Durch die Parallelisierung der Stages konnten wir einiges an Zeit gewinnen. Hier seht ihr die Übersicht über all unserer Stages und Steps.

In den folgenden Abschnitten werden wir das Jenkinsfile unseres Projektes näher anschauen und Schritt für Schritt erklären.

Frontend Build erstellen und Resultate stashen

Als Erstes wird der Frontend Build erstellt. Die benötigten Tools werden im Environment Abschnitt installiert. Der Build ist als Shell-Script implementiert. Weil die Pipeline auf verschiedenen Agents läuft, müssen wir die Artefakte für späteres Wiederverwenden stashen.

stage('cockpit frontend build for production')
     agent any
     environment {
           NVM_HOME = tool('NVM')
           YARN_HOME = tool('YARN')
           PATH = "$YARN_HOME/bin:node_modules/.bin:$PATH"
     }
     steps 
           dir('fmsx-cockpit/fmsx-web') {
                sh """#!/bin/bash +x
                rm -rf reports/* coverage/ logs/*
                source $NVM_HOME/nvm.sh
                nvm install ${env.NODE_VERSION}
                nvm use --delete-prefix ${env.NODE_VERSION}
                yarn -v
                yarn install:ci
                echo "master or development branch"
                yarn build:prod                  
                """
                stash includes: 'dist/**', name: 'frontend-prod-build'
           }
    }
}

Schritt Docker Images erstellen & Push ins Artifactory

Im zweiten Stage unseres Jenkinsfiles werden die Docker Images, welche wir deployen möchten, erstellt und ins Artifactory gepushed. Jetzt stellen sich wahrscheinlich einige die Frage, wieso dies bereits an dieser Stelle geschieht. In der groben Übersicht würde dies erst in einem späteren Schritt passieren. Wir builden zuerst alle benötigten Docker Images und pushen diese ins Artifactory, damit das Deployment auf OpenShift parallel zu unseren Unit Tests erfolgen kann. Das Deployment dauert immer eine Weile und so können wir etwas Zeit einsparen.

Im folgenden Ausschnitt aus dem Jenkinsfile wird gezeigt, wie die Docker Images vom FMSx erstellt werden. Dazu wird das Maven fabric8 Plugin benutzt. Der Push ins Artifactory wird mit Docker Shell Commands gemacht.

withEnv(["PATH+MAVEN=${MVN_HOME}/bin:${env.JAVA_HOME}/bin"]) {
   //build everything for images
   sh "mvn -B -V -e clean package -DskipTests=true"

   withDockerRegistry(credentialsId: "${DOCKER_REGISTRY_CREDENTIALS}", url: "https://${DOCKER_PULL_REGISTRY}") {
       dir('fmsx-integration-tests') {
           sh "mvn docker:build"
       }
   }
}

withDockerRegistry(credentialsId: "${DOCKER_REGISTRY_CREDENTIALS}", url: "https://${DOCKER_PUSH_REGISTRY}") {

   sh "docker tag fmsx-integration-tests/hub:v${OPENSHIFT_PROJECT}  ${DOCKER_PUSH_REGISTRY}/hub:$OPENSHIFT_PROJECT"
   sh "docker push  ${DOCKER_PUSH_REGISTRY}/hub:$OPENSHIFT_PROJECT"
}

Deployment auf OpenShift

Nun erstellen wir auf OpenShift ein neues temporäres Projekt, gegen welches wir später die e2e tests sowie Integration-Tests laufen lassen können.

stage('deploy fmsx to openshift') {
   steps {
       withCredentials([[$class       : 'StringBinding',
                           credentialsId: "${SERVICE_ACCOUNT}",
                           variable     : 'openshift_token']]) {
           withEnv(["KUBECONFIG=${pwd()}/.kube", "PATH+OC_HOME=${OC_HOME}/bin", "ose3url=${OPENSHIFT_URL}"]) {
               sh 'oc login $ose3url --token=$openshift_token'
               sh "echo create build project: ${OPENSHIFT_PROJECT}"
               sh "openshift/scripts/create-empty-project-bls.sh ${OPENSHIFT_PROJECT}"

Auf dem Buildserver ist es wichtig die Variable KUBECONFIG auf den Workspace zeigen zu lassen. Ansonsten könnten parallele Builds aus anderen Projekten die Konfiguration verändern und die Befehle werden in einem falschen Projekt abgesetzt.

Damit Jenkins im OpenShift berechtigt ist, Projekte anzulegen, muss ein spezieller ServiceAccount erstellt werden. Mehr dazu am Ende von diesem Blogpost.
Mit dem Jenkins ServiceAccount wird nun das OpenShift Project erstellt. Die Variable OpenShift Project setzt sich folgendermassen zusammen:

OPENSHIFT_PROJECT = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}" 

Für den Zugriff auf Artifactory erstellt die Pipeline ein Pull Secret mit den Credentials. Dies verwendet der default ServiceAccount für die Authentifizierung beim Holen der Docker Images.

withCredentials([usernamePassword(credentialsId: 'jenkins_artifactory', 
usernameVariable: 'ARTIFACTORY_USER', passwordVariable: 'ARTIFACTORY_PWD')]) {
   sh " oc create secret docker-registry bls-artifactory \\\n" +
           "    --docker-server=${DOCKER_REGISTRY_HOST} \\\n" +
           "    --docker-username=${ARTIFACTORY_USER} \\\n" +
           "    --docker-password='${ARTIFACTORY_PWD}' \\\n" +
           "    --docker-email=jenkins_artifactory_ext@bls.ch"
}
sh "oc secrets link default bls-artifactory --for=pull"

Jetzt können die Docker Images der Applikation deployed werden:

  • Docker Image von Artifactory in die interne Docker Registry importieren
  • Komponenten Ressourcen erstellen
    • dabei wird das Deployment getriggert

Hier als Beispiel die Cockpit Komponente:

sh "oc import-image fmsx-cockpit --confirm --from='${DOCKER_PUSH_REGISTRY}/cockpit:${OPENSHIFT_PROJECT}'"
sh "oc process -f openshift/templates/cockpit-deploymentconfig-template.yaml -p PROJECT_NAMESPACE=${OPENSHIFT_PROJECT} -p 
SPRING_PROFILES_ACTIVE=h2-integrationtests | oc apply -f -"

Wie ihr seht, wird das Cockpit mit dem Spring Profil “h2-integrationtests” deployed.

Testing

Während nun alle Komponenten auf OpenShift deployed werden, läuft im nächsten Stage das Testing. Folgende Tests werden nun parallel ausgeführt:

  •  Unit Tests
    • Frontend Unit Tests
    • Unit Tests aller FMSX Komponenten
  • Integration und System Tests
    • Backend Integration Tests
    • Frontend e2e Tests

Die letzten zwei Punkte möchten wir euch im Detail erklären. Die Integrationstests sowie die e2e Tests warten bis fmsx-cockpit und fmsx-hub deployed und ready sind.
Dazu werden die “health” Endpoints abgefragt:

sh '''#!/bin/bash +x
set -e
healthUrlCockpit=https://fmsx-cockpit-${OPENSHIFT_PROJECT}.osp.ad.bls.ch/actuator/health
healthUrlHub=https://fmsx-hub-${OPENSHIFT_PROJECT}.osp.ad.bls.ch/actuator/health
echo healthUrlCockpit=https://fmsx-cockpit-${OPENSHIFT_PROJECT}.osp.ad.bls.ch/actuator/health              
echo healthUrlHub=https://fmsx-hub-${OPENSHIFT_PROJECT}.osp.ad.bls.ch/actuator/health
healthy() { curl --silent --fail -L $1 | grep -q -e '^{\"status\":\"UP\"'; }
echo "Waiting for cockpit and hub to start ..."; until healthy $healthUrlCockpit && 
healthy $healthUrlHub ; do sleep 5 ; done ; echo "Server up"
'''

Sobald die Applikation ready ist, starten die Tests. Dem Maven Command wird nun die URL des erstellten Projektes angegeben.

mvn -B -V -e clean test -Dmaven.test.failure.ignore=true -Dcockpit-backend-
url=https://fmsx-cockpit-${OPENSHIFT_PROJECT}.osp.ad.bls.ch/api -Dmocklok-
url=https://fmsx-mocklok-${OPENSHIFT_PROJECT}.osp.ad.bls.ch/api

Für die Frontend e2e Tests müssen noch diverse Konfigurationen an die URL’s des temporären OpenShift FMSx Projektes angepasst werden.

  • Protractor Konfiguration
  • Dev-Server Proxy
//replace baseUlr in protractor config
sh "sed -i 's+http://localhost:4200+https://fmsx-cockpit-$
{OPENSHIFT_PROJECT}.osp.ad.bls.ch+g' fmsx-cockpit/fmsx-web/e2e/protractor-ci.conf.js"
// prepare devserver config
sh "sed -i 's+http://localhost:8080+https://fmsx-cockpit-$
{OPENSHIFT_PROJECT}.osp.ad.bls.ch+g' fmsx-cockpit/fmsx-web/proxy-devserver.conf.js"
sh "sed -i 's/local development server/fmsx build project/g' fmsx-cockpit/fmsx-web/proxy-devserver.conf.js"
sh "sed -i 's/changeOrigin: false/changeOrigin: true/g' fmsx-cockpit/fmsx-web/proxy-devserver.conf.js"

Die Frontend e2e Tests führen denselben Check auf den Health Endpoint aus und starten sobald dieser ready ist. Die Tests werden mit dem Command “yarn e2e” gestartet:

sh """#!/bin/bash +x
       source \${HOME}/.nvm/nvm.sh
       nvm use --delete-prefix ${env.NODE_VERSION}
       yarn install:ci
       yarn e2e:ci --dev-server-target= -c openshift --protractor-config=e2e/protractor-ci.conf.js
“””
           }
       }
...

Die e2e Tests setzen voraus, dass auf dem Jenkinsslave der Chromium Browser installiert ist.

Nun sind wir auch schon am Schluss unserer Pipeline angelangt. Wichtig ist, dass in einem post step immer noch das OpenShift Projekt gelöscht wird:

post {
   always {
       script {
           if (params.DELETE_OPENSHIFT_BUILD == true) {
               withCredentials([[$class       : 'StringBinding',
                                   credentialsId: "${SERVICE_ACCOUNT}",
                                   variable     : 'openshift_token']]) {
                   withEnv(["KUBECONFIG=${pwd()}/.kube", "PATH+OC_HOME=${OC_HOME}/bin", "ose3url=${OPENSHIFT_URL}"]) {
                       sh 'oc login $ose3url --token=$openshift_token'
                       sh "oc project ${OPENSHIFT_PROJECT} || true"
                       sh "oc delete project ${OPENSHIFT_PROJECT} || true"
                   }
               }
           }
       }
   }
}

Develop/Master Deployment

Wenn der Build auf dem Development oder Master Branch ausgeführt wird, dann wird gleichzeitig auch noch ein Deployment auf die Dev- bzw. Test-Umgebung stattfinden.

Statische Code Analyse

Statische Code Analysen, wie z.B. SonarQube, werden einmal in der Nacht ausgeführt.

Zusammenfassung

Mit dem Einsatz von OpenShift kann die Test-Infrastruktur automatisiert und On-Demand erstellt werden.
Es werden dieselben OpenShift Ressourcen Definitionen verwendet, welche auch für produktive Projekte benutzt werden. Somit ist die Testumgebung sehr ähnlich zur Produktion und die Ressourcen Definitionen werden auch mitgetestet.

Weiterführende Informationen

Informationen zu Jenkins Pipelines

Puzzle ITC hat ein Jenkins Pipelin Techlab erstellt.

Dort werden die Pipeline Files erklärt und auch Best Practices gezeigt.

Jenkins Berechtigung auf Openshift

Berechtigungen im OpenShift werden mit ServiceAccounts vergeben.

Für globale Rechte verwenden wir ein zentrales OpenShift Projekt zum Erstellen des ServiceAccounts für Jenkins. In diesem Fall ist es das bls-infra Projekt.

Erstellung des ServiceAccount mit Namen jenkins-sa:

oc create serviceaccount jenkins-sa -n bls-infra 

Dieser ServiceAccount hat noch keine Rechte. Für die Projekt Erstellung benötigt man die self-provisioner Rolle.

Die ist eine Cluster Rolle und wird so vergeben:

oc adm policy add-cluster-role-to-user self-provisioner -z jenkins-sa -n bls-infra 

Zur Verwendung des ServiceAccount wird sein Passwort, sprich Token, benötigt:

oc serviceaccounts get-token jenkins-sa -n bls-infra 

Dieses Token muss als Secret-Text Credential im Jenkins hinterlegt werden.

Jetzt kann dieses Credential in den Jenkins Pipelines für die Authentifizierung verwendet werden.

Schreibe einen Kommentar

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