Ce Projet dénommé Mini projet jenkins a été réalisé dans le cadre ma formation DEVOPS au 15ième Bootcamp de EAZYTraining de Septembre à Novembre 2023.
Il s’agit ici de mettre en œuvre une chaîne d’intégration continue pour un site web static (voir image en dessous) avec jenkins. le code source du site est ici .
Pour la réalisation de ce project j'ai effectuée les tâches ci-dessous:
- Provisionner 3 VPS
- Installation de docker sur chaque vps
- Installation jenkins sur un des vps
- Production du Dockerfile pour le Build
- Mise en place du Pipeline
- Configuration de github webhook
- Intégration de la notification slack
- Production du Jenkinsfile
J'ai suivi les étapes ci-dessous pour la réalisation du projet.
mon choix s'est porté sur des VPS de type Lightsail de AWS (01 pour Jenkins, 01 pour l'environnement de staging et le dernier pour l'environnement de la Prod)
- Debian 11.4
- docker et docker-compose en suivant la documentation qui se trouve ici
Sur le VPS de jenkins :
- Jenkins a été installé à l’aide d'un fichier docker-compose . Dans cette configuration le conteneur n’est accessible que sur la machine hôte à partir de 127.0.0.1:8080
version: '3.3'
services:
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
restart: always
privileged: true
user: root #update to set it for jenkins
ports:
- 127.0.0.1:8080:8080
volumes:
- /home/admin/.jenkins:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
342c609e0a56 jenkins/jenkins:lts "/usr/bin/tini -- /u…" 11 days ago Up 9 hours 127.0.0.1:8080->8080/tcp, 50000/tcp jenkins
Afin d’accéder à jenkins en dehors de l’hôte , j’ai installé Apache2 et fais un reverse proxy sur le 127.0.0.1:8080
<VirtualHost *:80>
ServerName jenkins.xxxxx
ServerAdmin webmaster@localhost
ProxyPass / http://127.0.0.1:8080/ nocanon
ProxyPassReverse / http://127.0.0.1:8080/
ProxyRequests Off
AllowEncodedSlashes NoDecode
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
<Proxy http://127.0.0.1:8080/*>
Order deny,allow
Allow from all
</Proxy>
ErrorLog ${APACHE_LOG_DIR}/jenkins.error.log
CustomLog ${APACHE_LOG_DIR}/jenkins.access.log combined
RewriteEngine on
RewriteCond %{SERVER_NAME} =jenkins.xxxxx
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
et enfin installé et configuré letsencrypt avec un sous-domaine sur chaque VPS.
Voici ci-dessous le dockerfile qui a permit de créer l'image Docker à partir de laquelle nos conteneurs seront créées.
FROM nginx:latest
LABEL maintainer="GBANE Assouman [email protected]"
WORKDIR /usr/share/nginx/html
COPY . .
EXPOSE 80
CMD [ "nginx","-g","daemon off;" ]
-
image de base Docker
FROM nginx:latest
LABEL maintainer="GBANE Assouman [email protected]" -
Répertoire du site
WORKDIR /usr/share/nginx/html -
Copie des fichiers dans le répertoire WORKDIR
COPY . . -
Port d'écoute de NGINX
EXPOSE 80 -
Démarrage de NGINX
CMD [ "nginx","-g","daemon off;" ]
Pour la mise en place du pipeline il faut:
- installer les plugins necéssaires
- créer des credentials
- créer un nouveau item de type pipeline
Installation des plugins
Pour l'installation des puglins necéssaires au pipeline on va dans
Tableau de bord -> Administrer jenkins -> Plugins -> Available plugins puis chercher et installer les plugins suivants:
- Docker
- Docker pipeline
- Docker plugin
- Github integration Plugin
- SSH Agent plugin
Création des crédentials
Afin de pusher l'image buildée sur Docherhub (hub.docker.com) et de se connecter sur les serveurs de staging et de prod via ssh on va créer les identifiants ci-dessous:
- dockerhub-credential de type Nom d'utilisateur et mot de passe pour Dockerhub
- SSH-KEY de type SSH Username with private key (copier la clé privée de connexion du serveur de staging)
- prod-area de type SSH Username with private key (copier la clé privée de connexion du serveur de la prod)
- slack-text de type Secret text pour l'intégration de slack
il faut au préalable aller dans Tableau de bord -> Administrer jenkins -> credentials pour la création des credentials
Création du pipeline
Pour créer un nouveau pipeline il faut cliquer sur nouveau item à gauche dans le Dashboard puis Pipeline
Pour déclencher automatiquement le lancement du pipeline à partir d'une action réalisée sur GitHub il faut:
- Renseigner l'url du repository dans le pipeline
- Autoriser le déclenchement du webhook
- Configurer le webhook depuis le repository github
confirmation que le webhook marche
Le pipeline sera basée sur un Jenkinsfile donc dans la section pipeline de la configuration il faut choisir Pipeline script from SCM
Nous allons intégrer slack à notre environnement jenkins pour recevoir directement des notifications après l'execution de notre pipeline.
- Installation du plugin slack-notification
Voici les étapes à suivre pour ajouter l’intégration Jenkins CI.
Dans le tableau de bord Jenkins, cliquer sur Administrer Jenkins dans la barre de navigation à gauche.
Cliquer sur Plugins puis Available plugins et rechercher Slack Notification dans l’onglet search available plugins. Cochez la case et installer le plugin.
Une fois l’installation effectuée, cliquer de nouveau sur Administrer Jenkins dans le volet de navigation gauche, puis accéder à Configure System (Configurer le système).
Rechercher la section Global Slack Notifier Settings (Paramètres de notification Slack globaux) et ajouter les valeurs suivantes :
Sous-domaine de l’équipe : workspace Identifiant d’authentification où jeton d’intégration :
Créez un identifiant de type secret texte ayant pour valeur Token
NB:
Pour obtenir le workspace et Token il faut au préalable:
- 1 créer un compte slack
- 2 configurer l'intégration de jenkins à partir d'ici
à partir de cet instant on peux ajouter les notifications slack dans le Jenkinsfile
à fin de rendre disponible la notification slack à tous les projets, je l'ai transformé en Shared Library qui va permettre de faire un simple appel dans les Jenkinsfiles.
Pour ce faire j'ai :
- crée un depôt Github voir ici et ajouté un fichier de type groovy appelé slackNotifier.groovy ensuite definir la fonction de notification ci-dessous
#!/usr/bin/env groovy
def call(String buildResult) {
if ( buildResult == "SUCCESS" ) {
slackSend color: "good", message: "CONGRATULATION: Job ${env.JOB_NAME} with buildnumber ${env.BUILD_NUMBER} was successful ! more info ${env.BUILD_URL}"
}
else if( buildResult == "FAILURE" ) {
slackSend color: "danger", message: "BAD NEWS:Job ${env.JOB_NAME} with buildnumber ${env.BUILD_NUMBER} was failed ! more info ${env.BUILD_URL}"
}
else if( buildResult == "UNSTABLE" ) {
slackSend color: "warning", message: "BAD NEWS:Job ${env.JOB_NAME} with buildnumber ${env.BUILD_NUMBER} was unstable ! more info ${env.BUILD_URL}"
}
else {
slackSend color: "danger", message: "BAD NEWS:Job ${env.JOB_NAME} with buildnumber ${env.BUILD_NUMBER} its result was unclear ! more info ${env.BUILD_URL}"
}
}
- configurer la Shared Library à partir de Administrer jenkins -> System -> Global Pipeline Libraries
- importer la Shared library précédemment configurée dans le Jenkinsfile
@Library('slack-shared-library') _
- et faire appel à la fonction de notification en dehors de la balise des stages
post {
always {
script {
/* Use Slack-notification.groovy from shared library */
slackNotifier currentBuild.result
}
}
}
Le Jenkinsfile contient 08 stages et un Post Actions pour slack
Les Stages sont:
- Cloning code (recupère le code sur github)
- Build Image (Build de l'image)
- Test Image (test de l'image en créant un conteneur)
- Release image (envoie l'image sur dockerhub)
- Clean image (arrête et supprime l'image)
- Deploy to Satging
- Test in staging
- Deploy to Prod
et les éléments ci-dessous ont été variabilisés pour faciliter leur réutilisation dans les différents stages:
IMAGE_NAME, IMAGE_TAG, DOCKER_HUB, HOST_PORT, INTERNAL_PORT, CONTAINER, STAGING_IP, PROD_IP, USER_NAME, STAGING_NAME, PROD_NAME
Jenkinsfile
/* groovylint-disable NglParseError */
/* groovylint-disable DuplicateListLiteral, DuplicateStringLiteral, GStringExpressionWithinString, LineLength, NestedBlockDepth, NglParseError */
/* groovylint-disable-next-line CompileStatic */
/* groovylint-disable-next-line CompileStatic, NglParseError */
/* import shared library */
// @library('shared-library')
/* groovylint-disable-next-line CompileStatic */
@Library('slack-shared-library') _
pipeline {
environment {
DOCKERHUB_PASSWORD = credentials('dockerhub-credentials')
}
agent any //declaration globale de l'agent
stages {
stage('Cloning code') {
steps {
script {
sh '''
rm -rf mini-projet-jenkins || echo "Directory doesn't exists "
sleep 2
git clone https://github.com/gbaneassouman/mini-projet-jenkins.git
'''
}
}
}
stage('Build image') {
steps {
script {
/* groovylint-disable-next-line GStringExpressionWithinString */
sh '''
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
'''
}
}
}
stage('Test image') {
steps {
script {
/* groovylint-disable-next-line GStringExpressionWithinString */
// sh 'docker stop ${CONTAINER}|| echo "already stopped"'
// sh 'docker rm -f ${CONTAINER} || echo "container does not exist"'
sh 'docker run --name ${CONTAINER} -d -p ${HOST_PORT}:${INTERNAL_PORT} ${IMAGE_NAME}:${IMAGE_TAG}'
sh 'sleep 10'
sh 'curl -k http://172.17.0.1:${HOST_PORT}|grep -i "DIMENSION"'
}
}
}
stage('Release image') {
steps {
script {
/* groovylint-disable-next-line GStringExpressionWithinString */
sh '''
docker save ${IMAGE_NAME}:${IMAGE_TAG} > /tmp/${IMAGE_NAME}:${IMAGE_TAG}.tar
docker image tag ${IMAGE_NAME}:${IMAGE_TAG} ${DOCKER_HUB}/${IMAGE_NAME}:${IMAGE_TAG}
echo $DOCKERHUB_PASSWORD_PSW | docker login -u ${DOCKER_HUB} --password-stdin
docker push ${DOCKER_HUB}/${IMAGE_NAME}:${IMAGE_TAG}
'''
}
}
}
stage('Clean image') {
steps {
script {
/* groovylint-disable-next-line GStringExpressionWithinString */
//ps aux | grep -i 5000 | awk '{print $2}' | xargs sudo kill -9
//kill $(ps aux | grep '5000' | awk '{print $2}')
sh '''
docker stop ${CONTAINER}
docker rm -f ${CONTAINER}
docker rmi -f ${IMAGE_NAME}
docker rmi -f ${DOCKER_HUB}/${IMAGE_NAME}
'''
}
}
}
stage('Deploy to Satging') {
steps {
script {
/* groovylint-disable-next-line GStringExpressionWithinString, NestedBlockDepth */
sshagent(['SSH-KEY']) {
sh '''
echo $DOCKERHUB_PASSWORD_PSW | docker login -u ${DOCKER_HUB} --password-stdin
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${STAGING_IP} docker stop ${STAGING_NAME}||echo "stopped"
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${STAGING_IP} docker rm ${STAGING_NAME}||echo "already deleted"
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${STAGING_IP} docker pull ${DOCKER_HUB}/${IMAGE_NAME}:${IMAGE_TAG}
sleep 120
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${STAGING_IP} docker run --name ${STAGING_NAME} -d -p ${HOST_PORT}:${INTERNAL_PORT} ${DOCKER_HUB}/${IMAGE_NAME}:${IMAGE_TAG}
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${STAGING_IP} curl -k http://172.17.0.1:${HOST_PORT}|grep -i "DIMENSION"
'''
}
}
}
}
stage('Test in staging') {
steps {
script {
sshagent(['SSH-KEY']) {
sh '''
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${STAGING_IP} curl -k http://172.17.0.1:${HOST_PORT}|grep -i "DIMENSION"
'''
}
}
}
}
stage('Deploy to Prod') {
steps {
script {
/* groovylint-disable-next-line GStringExpressionWithinString, NestedBlockDepth */
sshagent(['prod-area']) {
sh '''
echo $DOCKERHUB_PASSWORD_PSW | docker login -u ${DOCKER_HUB} --password-stdin
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${PROD_IP} docker stop ${PROD_NAME}||echo "stopped"
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${PROD_IP} docker rm ${PROD_NAME}||echo "already deleted"
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${PROD_IP} docker pull ${DOCKER_HUB}/${IMAGE_NAME}:${IMAGE_TAG}
sleep 120
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${PROD_IP} docker run --name ${PROD_NAME} -d -p ${HOST_PORT}:${INTERNAL_PORT} ${DOCKER_HUB}/${IMAGE_NAME}:${IMAGE_TAG}
ssh -o StrictHostKeyChecking=no -l ${USER_NAME} ${PROD_IP} curl -k http://172.17.0.1:${HOST_PORT}|grep -i "DIMENSION"
'''
}
}
}
}
}
post {
always {
script {
/* Use Slack-notification.groovy from shared library */
slackNotifier currentBuild.result
}
}
}
}
Résultat de l'execution du script
Notification Slack
Test in staging
Test in Prod
Ce fut une expérience enrichissante pour moi car ce module m'a permis de mettre en pratique les connaissances acquises sur Docker, et Jenkins
Assouman GBANE
Linkedin : https://www.linkedin.com/in/gbane-assouman
mini-projet-jenkins's People
Forkers
franck2605Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.