CICD Spring Boot App avec Jenkins, Docker, GitHub Hooks, SonarCloud

Contexte et Objectifs

Les enjeux des logiciels de nos jours sont nombreux et complexes. Les entreprises doivent s’assurer que leurs logiciels sont sécurisés, fiables, performants, évolutifs afin de répondre à la satisfaction du client. Cette évolutivité constante exige la mise en place des processus de livraisons logiciels rapides. Et ce processus commence avec la mise en place d’une chaine d’intégration et de livraison continue efficiente ; D’où l’importance d’utiliser des outils tels que Jenkins ; Aussi les entreprises sont de plus en plus intéressées par des Framework qui accélèrent et simplifient le développement Java d’applications Web et de micro services comme Java Spring Boot.

Durée de mise en place : 30 à 45 min

Etapes

Alors pour mettre en place une CICD avec Jenkins pour notre application Java nous allons :

  • Présentez l’architecture d’exécution de la pipeline CICD
  • Installez Docker et Docker Compose ;
  • Installez et configurez Jenkins ;
  • Configurez GitHub Hooks ;
  • Configurez les outils pour la qualimétrie du code avec SonarCloud ;
  • Configurez Docker Hub ;
  • Configurez un projet de type Pipeline avec les plugins et identifiants
  • Implémentez le script Groovy Jenkinsfile ;

Environnement

Avant de débuter l’implémentation, suivant, présentons les différents outils et plateformes :

  • CentOS : c’est l’acronyme de Community ENTerprise Operating System, c’est un projet Open Source qui produit deux distributions Linux distinctes : CentOS Stream et CentOS Linux. Dans cet article nous utilisons CentOS Linux qui est une distribution en aval de Red Hat Enterprise Linux. Elle est le plus souvent utilisée pour le développement et le déploiement.

Pour plus d’information consulter : CentOS, qu’est-ce que c’est ? (redhat.com)

  • Jenkins est un outil open source de serveur d’automatisation. Il aide à automatiser les parties du développement logiciel liées au build, aux tests et au déploiement, et facilite l’intégration continue et la livraison continue.

Pour plus d’information consulter : Jenkins: CI/CD pour DevOps – EAZYTraining

  • Java Spring Boot (Spring Boot) est un outil qui accélère et simplifie le développement d’applications Web et de microservices avec Spring Framework grâce à trois fonctionnalités principales : configuration automatique, approche directive de la configuration et possibilité de créer des applications autonomes.

Pour plus d’information consulter : Spring Boot

  • Docker & Docker Compose & Docker Hub : Il vous permet de séparer vos applications de votre infrastructure afin de fournir rapidement des logiciels. Docker Compose est un outil permettant de définir et d’exécuter des applications Docker multi-conteneurs. Docker Hub est le registre officiel de Docker. Il s’agit d’un répertoire SaaS permettant de gérer et de partager les conteneurs.  En tirant parti des méthodologies de Docker pour la livraison, le test et le déploiement du code, vous pouvez réduire considérablement le délai entre l’écriture du code et son exécution en production.

Pour plus d’information consulter : Introduction à Docker – EAZYTraining

  • GitHub Hooks : Ils vous permettent de vous abonner à des événements qui se produisent dans un système logiciel et de recevoir automatiquement des données sur votre serveur lorsque ces événements se produisent. Lorsque vous créez un webhook, vous spécifiez une URL et vous vous abonnez aux événements qui se produisent sur GitHub. Lorsqu’un événement auquel votre webhook est abonné se produit, GitHub envoie une requête HTTP contenant des données sur l’événement à l’URL que vous avez spécifiée.

Pour plus d’information consulter : About webhooks – GitHub Docs

  • SonarCloud : C’est un service d’analyse de code basé sur le cloud, conçu pour détecter les problèmes de codage dans plus de 30 langages, frameworks et plateformes IaC. En s’intégrant directement à votre pipeline CI ou à l’une de nos plateformes DevOps prises en charge, votre code est vérifié par rapport à un ensemble étendu de règles qui couvrent de nombreux attributs du code, tels que la maintenabilité, la fiabilité et les problèmes de sécurité, à chaque demande de fusion/extraction.

Pour plus d’information consulter : SonarCloud Documentation (sonarsource.com)

Pour la mise en place de notre processus CICD sur une application Spring il est important de préciser les versions de certains outils. Alors nous allons travailler avec :

  • CentOS 7.9
  • Docker 24.0.6
  • Docker Compose v2.21.0 
  • Jenkins v2.454-jdk17 de Docker

Présentation de l’architecture d’exécution la pipeline CICD

Figure 1: Présentation de l’architecture d’exécution la pipeline CICD

Figure 1: Présentation de l’architecture d’exécution la pipeline CICD

Notre architecture est constituée comme suit :

  • Développeur Spring ou DevOps pousse une fonctionnalité sur GitHub ;
  • GitHub sauvegarde la nouvelle version de notre code et déclenche la pipeline CICD au niveau du serveur Jenkins via un webhook ;
  • Serveur Jenkins déployé à l’aide de docker exécute la pipeline CICD à travers les étapes : Test, Quality, Build, Package, Deploy Staging, Test Staging, Deploy PROD et Test PROD ;
  • SonarCloud après authentification du Serveur Jenkins pendant l’exécution de l’étape Quality du pipeline, mesure la qualité du code applicatif et renvoi le statut grâce au webhook
  • Docker Hub après authentifications provenant du Serveur Jenkins ou STAGING ou PROD respectivement lors des exécutions des étapes Package, Deploy Staging et Deploy Prod du pipeline, accepte le push et le pull d’une version de l’image docker de l’application ;
  • Serveur STAGING via une connexion SSH du Serveur Jenkins accepte le déploiement de l’application part l’étape Deploy  Staging de la pipeline ;
  • Serveur PROD via une connexion SSH du Serveur Jenkins accepte le déploiement de l’application part l’étape Deploy  Prod de la pipeline  ;

Installation de Docker et Docker Compose

Pour installer Docker et Docker Compose sur CentOS 7 exécutez les étapes suivantes :Installez le paquet yum-utils qui vous fournira l’utilitaire yum-config-manager et configurez le dépôt.

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
  • Installez la version spécifique de Docker et Docker Compose comme suit :
sudo yum install docker-ce-24.0.6 docker-ce-cli-24.0.6 containerd.io docker-buildx-plugin docker-compose-plugin
  • Activez Docker au démarrage du système, ensuite démarrer le service comme suit :
sudo systemctl enable docker
sudo systemctl start 
  • Attribuez les droits d’exécution du daemon Docker à votre utilisateur dans mon cas centos comme suit :
sudo usermod –aG docker centos
  • Vérifiez que l’installation a réussi comme suit :
docker --version
docker compose version
  • Vérifiez que la version retournée par Docker est 24.0.6 et vérifier que Docker Compose est aussi installé

Pour plus d’information consulter : Install Docker Engine on CentOS | Docker Docs

Installation et configuration de Jenkins

Pour l’installation de Jenkins nous allons utiliser Docker. Afin de le faire, exécutez les étapes suivantes :

  • Créez le répertoire setup_jenkins et créez à l’intérieur de ce dernier le fichier docker-compose.yml
mkdir setup_jenkins
cd setup_jenkins && touch docker-compose.yml
  • Récupérez le chemin de la commande docker 
Figure 2: Récupérez le chemin de la commande docker

Figure 2: Récupérez le chemin de la commande docker

Ajoutez le contenu suivant dans le fichier docker-compose.yml (remplacez path_daemon_docker par le chemin de la commande docker)

version: '4'
services:
  jenkins:
    privileged: true
    user: root
    container_name: jenkins-launch1
    image: jenkins/jenkins:2.454-jdk17
    restart: always
    ports:
      - 80:8080
      - 50000:50000
    volumes:
      - <path_daemon_docker>:/usr/bin/docker
      - jenkins_launch:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sockvolumes:
 jenkins_launch:
  • Lancez votre conteneur Jenkins
docker compose up –d
  • Vérifiez que votre conteneur est en cours d’exécution
Figure 3 : Vérifiez que votre conteneur est en cours d’exécution

Figure 3 : Vérifiez que votre conteneur est en cours d’exécution

  • Récupérez le mot de passe admin
docker exec -i jenkins-launch1 cat /var/jenkins_home/secrets/initialAdminPassword

Ensuite configurons Jenkins en via le navigateur

Web. Afin de le faire, exécutez les étapes suivantes :

  • Dans la barre d’URL, tapez : http://<ip_publicpour accéder à l’UI de Jenkins,;
  • Insérez le mot de passe admin

Figure 4: Insérez le mot de passe admin

  • Installez les plugins suggérés ;

Figure 5: Installez les plugins suggérés

  • Créez votre utilisateur avec ses identifiants ;
  • Laissez la configuration par défaut de l’instance ;

Figure 6: Laissez la configuration par défaut de l’instance

Démarrez Jenkins et visualisez le tableau de bord ;

Figure 7: Démarrez Jenkins et visualisez le tableau de bord

Figure 8: Démarrez Jenkins et visualisez le tableau de bord

Configuration de github Hooks

Afin de pouvoir déclencher notre processus de CICD par un webhook GitHub, à la suite de la publication d’une nouvelle fonctionnalité dans notre repository sur GitHub, effectuez les actions suivantes :

  • Créez un compte sur GitHub si vous n’en avez pas ;
  • Forkez ce repos https://github.com/AnselmeG300/PayMyBuddy.git en décochant l’option main only, pour pouvoir récupérer toute les branches
  • Créez un webhook dans l’onglet settings de votre repo créé à partir du fork

Figure 9: Créez un webhook dans l’onglet settings de votre repo créé à partir du fork

  • Cliquez sur le bouton Add webhook et vérifiez que la synchronisation se passe bien

Figure 10: Cliquez sur le bouton Add webhook et vérifiez que la synchronisation se passe bien

  • Pour que notre processus de CICD qui sera géré par Jenkins, puisse être déclenché par ce webhook, installez le plugin GitHub Integration dans Jenkins comme suit :

Figure 11: Installez le plugin GitHub Integration dans Jenkins

Configuration des outils pour la Qualimétrie de code avec SonarCloud

L’objectif de cette configuration est de permettre à notre serveur Jenkins de communiquer avec SonarCloud et de configurer notre application afin de mesurer la qualité du code ; Pour ce faire, effectuez les actions qui suivent au niveau de SonarCloud : 

Figure 12: Créez un compte sur SonarCloud à partir de votre compte GitHub

  • Une fois connectez, suivez le lien Projects – SonarCloud, pour débuter la création d’un projet ;
  • Débutez la création d’un projet à partir de votre repos GitHub comme suit :

Figure 13: Débutez la création d’un projet à partir de votre repos GitHub

Figure 14: Débutez la création d’un projet à partir de votre repos GitHub

Sélectionnez le compte qui contient votre projet et continuez comme suit ;

Figure 15: Sélectionnez votre repository

Figure 16: Sélectionnez votre repository

Figure 17: Sélectionnez votre repository

Si vous n’aviez pas d’organisation vous allez être rediriger vers la page de création d’une organisation

Figure 18: création d’une organisation

Figure 19: création d’une organisation

  • Cliquez à nouveau sur Analyze projects

Figure 20: Cliquez à nouveau sur Analyze projects

Figure 21: Cliquez à nouveau sur Analyze projects

Figure 22: Cliquez à nouveau sur Analyze projects

  • Désactivez l’analyse automatique du projet car cela doit être déclenchée à partir de notre pipeline

Figure 23: Désactivez l’analyse automatique du projet

  • Afin de permettre au scanner Sonar de se connecter à SonarCloud et d’identifier le projet où il doit renvoyer les résultats, générez un token (gardez le, vous en aurez besoin dans la suite de la configuration) et récupérez l’ID du projet (Project Key), l’ID de l’organisation (Organization Key) ;

Figure 24: Générez un token

Figure 25: Récupérez l’ID du projet (Project Key), l’ID de l’organisation (Organization Key);

  • Définissez un webhook (URL : {url de votre machine jenkins}/sonarqube-webhook) sur SonarCloud vers notre Jenkins afin que les résultats de l’analyse de notre code puissent être retourné à notre Pipeline sur Jenkins ; Sinon elle restera en attente ;

Figure 26: Définissez un SonarCloud webhook

Figure 27: Définissez un SonarCloud webhook

Les configurations dans SonarCloud terminées, continuons avec celles au niveau du code applicatif : Dirigez-vous dans sur la branche deploy, sur cette branche cous aurez le répertoire vous devez visualiser une répertoire nommé deploy

Figure 28: Dirigez-vous dans sur la branche deploy

  • Créez le répertoire .m2 à la racine de votre application
  • Dans le répertoire .m2 créez le fichier settings.xml avec la configuration suivante (Remplacez Project Key et Organization key par l’ID de votre projet et de votre organisation sur SonarCloud respectivement) :
<settings>
    <pluginGroups>
        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
    </pluginGroups>
    <profiles>
        <profile>
            <id>sonar</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <sonar.host.url>
                    https://sonarcloud.io
                </sonar.host.url>
                <sonar.coverage.exclusions>
                    **/exceptions/*
                </sonar.coverage.exclusions>
                <sonar.projectKey>Project Key</sonar.projectKey>
                <sonar.organization>Organization Key</sonar.organization>
            </properties>
        </profile>
    </profiles>
</settings> 

·        Intégrez le plugin Jacoco, afin d’obtenir la mesure sur la couverture des tests en rajoutant le contenu suivant dans le fichier pom.xml

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.8</version>
    <configuration>
        <excludes>
            <exclude>**/com/paymybuddy/paymybuddy/PayMyBuddyApplication.*</exclude>
            <exclude>**/constants/*</exclude>
            <exclude>**/exceptions/*</exclude>
            <exclude>**/model/*</exclude>
            <exclude>**/repository/*</exclude>
        </excludes>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <!-- attached to Maven test phase -->
        <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Pour terminer cette partie, effectuez les configurations suivantes sur Jenkins :

Créez un identifiant d’ID sonartoken de type Secret text

Figure 29: Créez un identifiant d’ID sonartoken de type Secret text

·        Ajoutez le plugin SonarQube Scanner for Jenkins afin d’exécuter sonar à partir de Jenkins

Figure 30: Ajoutez le plugin SonarQube Scanner for Jenkins

·        Configurez un serveur SonarQube avec comme nom SonarCloudServer dans paramètres système pour permettre à Jenkins de se connecter à SonarCloud

Figure 31: Configurez un serveur SonarQube avec comme nom SonarCloudServer

  • Configuration de DockerHub

Figure 32: Connectez-vous et récupérez votre username sur Docker hub

Créez un identifiant d’ID DockerHubCredentials de type Username with password dans Jenkins pour se connecter au DockerHub

Figure 33: Créez un identifiant d’ID DockerHubCredentials de type Username with password

  • Configuration d’un projet de type Pipeline avec les plugins et identifiants


Pour exécuter notre pipeline, en plus des configurations déjà effectuées sur Jenkins ajoutez les configurations suivantes :

  • Installez les plugins Docker et Docker pipeline pour pouvoir utiliser un agent de type Docker dans notre pipeline

Figure 34: Installez les plugins Docker et Docker pipeline

Installez le plugin ssh agent pour vous connectez en SSH sur vos serveurs Staging et Prod ; NB : Sur ces serveurs doivent être installé docker et ssh.


Créez un identifiant d’ID MYSQL_AUTH et de type Username with password dans Jenkins pour la connexion du front de l’application à sa base de données

Figure 35: Créez un identifiant d’ID MYSQL_AUTH et de type Username with password

Créez un identifiant d’ID SSH_AUTH_SERVER et de type SSH Username with private key dans Jenkins pour nous connecter à nos serveurs Staging et Prod

Figure 36: Créez un identifiant d’ID SSH_AUTH_SERVER et de type SSH Username with private key

  • Vérifiez que vous avez quatre identifiants

Figure 37: Vérifiez que vous avez quatre identifiants

  • Créez un projet de type Pipeline nommé CICD en suivant les menus Dashboard, puis new item 

Figure 38: Créez un projet de type Pipeline nommé CICD
Configurez le projet comme suit :

Figure 39: Configurez le projet

Figure 40: Configurez le projet

Figure 41: Configurez le projet

Implémentation du script Groovy Jenkinsfile

Pour finaliser la mise en place de notre processus CICD effectuez les actions suivantes :

  • Dirigez-vous à la racine de votre projet applicatif et rajoutez le répertoire agent
  • A l’intérieur du répertoire agent créez le fichier Dockerfile et ajoutez le contenu suivant :
FROM docker:20
RUN  apk add --no-cache git
RUN  apk add --no-cache maven
  • Retournez à la racine de votre projet applicatif, créez le fichier Jenkinsfile et rajoutez le contenu suivant :
pipeline {
    agent {
        dockerfile {
            filename 'agent/Dockerfile'
            args '-v /root/.m2:/root/.m2 -v /var/run/docker.sock:/var/run/docker.sock'
        }
    }

    environment {
        DOCKERHUB_AUTH = credentials('DockerHubCredentials')
        MYSQL_AUTH= credentials('MYSQL_AUTH')
        IMAGE_NAME= 'paymybuddy'
        IMAGE_TAG= 'latest'
        HOSTNAME_DEPLOY_STAGING = "34.230.2.252"
        HOSTNAME_DEPLOY_PROD = "54.145.64.29"
    }

    stages {

        stage('Test') {
            steps {
                sh 'mvn clean test'
            }

            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }

        stage('SonarQube analysis') {
            steps {
                withSonarQubeEnv('SonarCloudServer') {
                    sh 'mvn sonar:sonar -s .m2/settings.xml'
                }
            }
        }

        stage('Quality Gate') {
            steps {
                timeout(time: 60, unit: 'SECONDS') {
                    waitForQualityGate abortPipeline: false
                }
            }
        }

        stage('Package') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }

        stage('Build and push IMAGE to docker registry') {
            steps {
                sh """
                    docker build --secret id=db_user,env=SPRING_DATASOURCE_USER --secret id=db_password,env=SPRING_DATASOURCE_PASSWORD -t ${DOCKERHUB_AUTH_USR}/${IMAGE_NAME}:${IMAGE_TAG} .
                    echo ${DOCKERHUB_AUTH_PSW} | docker login -u ${DOCKERHUB_AUTH_USR} --password-stdin
                    docker push ${DOCKERHUB_AUTH_USR}/${IMAGE_NAME}:${IMAGE_TAG}
                """
            }
        }

        stage ('Deploy in staging') {
            when {
                expression { GIT_BRANCH == 'main' }
            }
            steps {
                sshagent(credentials: ['SSH_AUTH_SERVER']) { 
                    sh '''
                        [ -d ~/.ssh ] || mkdir ~/.ssh && chmod 0700 ~/.ssh
                        ssh-keyscan -t rsa,dsa ${HOSTNAME_DEPLOY_STAGING} >> ~/.ssh/known_hosts
                        scp -r deploy centos@${HOSTNAME_DEPLOY_STAGING}:/home/centos/
                        command1="cd deploy && echo ${DOCKERHUB_AUTH_PSW} | docker login -u ${DOCKERHUB_AUTH_USR} --password-stdin"
                        command2="echo 'IMAGE_VERSION=${DOCKERHUB_AUTH_USR}/${IMAGE_NAME}:${IMAGE_TAG}' > .env && echo ${MYSQL_AUTH_PSW} > secrets/db_password.txt && echo ${MYSQL_AUTH_USR} > secrets/db_user.txt"
                        command3="docker compose down && docker pull ${DOCKERHUB_AUTH_USR}/${IMAGE_NAME}:${IMAGE_TAG}"
                        command4="docker compose up -d"
                        ssh -t centos@${HOSTNAME_DEPLOY_STAGING} \
                            -o SendEnv=IMAGE_NAME \
                            -o SendEnv=IMAGE_TAG \
                            -o SendEnv=DOCKERHUB_AUTH_USR \
                            -o SendEnv=DOCKERHUB_AUTH_PSW \
                            -o SendEnv=MYSQL_AUTH_USR \
                            -o SendEnv=MYSQL_AUTH_PSW \
                            -C "$command1 && $command2 && $command3 && $command4"
                    '''
                }
            }
        }

        stage('Test Staging') {
            when {
                expression { GIT_BRANCH == 'main' }
            }
            steps {
                sh '''
                    sleep 30
                    apk add --no-cache curl
                    curl ${HOSTNAME_DEPLOY_STAGING}:8080
                '''
            }
        }

        stage ('Deploy in prod') {
            when {
                expression { GIT_BRANCH == 'main' }
            }
            steps {
                sshagent(credentials: ['SSH_AUTH_SERVER']) { 
                    sh '''
                        [ -d ~/.ssh ] || mkdir ~/.ssh && chmod 0700 ~/.ssh
                        ssh-keyscan -t rsa,dsa ${HOSTNAME_DEPLOY_PROD} >> ~/.ssh/known_hosts
                        scp -r deploy centos@${HOSTNAME_DEPLOY_PROD}:/home/centos/
                        command1="cd deploy && echo ${DOCKERHUB_AUTH_PSW} | docker login -u ${DOCKERHUB_AUTH_USR} --password-stdin"
                        command2="echo 'IMAGE_VERSION=${DOCKERHUB_AUTH_USR}/${IMAGE_NAME}:${IMAGE_TAG}' > .env && echo ${MYSQL_AUTH_PSW} > secrets/db_password.txt && echo ${MYSQL_AUTH_USR} > secrets/db_user.txt"
                        command3="docker compose down && docker pull ${DOCKERHUB_AUTH_USR}/${IMAGE_NAME}:${IMAGE_TAG}"
                        command4="docker compose up -d"
                        ssh -t centos@${HOSTNAME_DEPLOY_PROD} \
                            -o SendEnv=IMAGE_NAME \
                            -o SendEnv=IMAGE_TAG \
                            -o SendEnv=DOCKERHUB_AUTH_USR \
                            -o SendEnv=DOCKERHUB_AUTH_PSW \
                            -o SendEnv=MYSQL_AUTH_USR \
                            -o SendEnv=MYSQL_AUTH_PSW \
                            -C "$command1 && $command2 && $command3 && $command4"
                    '''
                }
            }
        }

        stage('Test Prod') {
            when {
                expression { GIT_BRANCH == 'main' }
            }
            steps {
                sh '''
                    sleep 30
                    apk add --no-cache curl
                    curl ${HOSTNAME_DEPLOY_PROD}:8080
                '''
            }
        }
    }

}

  • Modifiez les variables d’environnements HOSTNAME_DEPLOY_STAGING par l’IP de votre serveur STAGING et HOSTNAME_DEPLOY_PROD par l’IP de votre serveur PROD 
  • Vérifiez que votre code contient les fichiers et dossiers dans la capture

Figure 42: Vérifiez que votre code contient les fichiers et dossiers dans la capture

  • Fusionnez ces modifications sur votre branche main (branche principale) de votre repo GitHub ; Si tout est bien configuré, à la suite de cette modification votre pipeline va être déclenchée ; Et les étapes de votre CICD vont être exécutées avec succès
  • Inspectez les résultats sur Jenkins, SonarCloud, Jenkins et sur votre navigateur

Figure 43: Inspectez les résultats sur Jenkins

Figure 44: Inspectez les résultats sur Jenkins

Figure 45: Inspectez les résultats sur SonarCloud

Figure 46: Inspectez les résultats sur Docker hub

Figure 47: Inspectez les résultats sur Staging

Figure 48: Inspectez les résultats sur Prod

Références

Techniques

Personnelles