Exercices et Projets Jenkins
Exercice 1 : Premier Pipeline
Objectif : Créer un pipeline simple qui affiche des messages.
Instructions
- Créer un nouveau job Pipeline
- Écrire un Jenkinsfile avec 3 stages
- Exécuter et vérifier les logs
Solution
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello, Jenkins!'
echo "Build number: ${BUILD_NUMBER}"
echo "Job name: ${JOB_NAME}"
}
}
stage('Environment') {
steps {
sh 'printenv | sort'
}
}
stage('Date') {
steps {
script {
def date = sh(script: 'date', returnStdout: true).trim()
echo "Current date: ${date}"
}
}
}
}
post {
success {
echo '✅ Pipeline completed successfully!'
}
failure {
echo '❌ Pipeline failed!'
}
}
}
Exercice 2 : Pipeline Node.js
Objectif : CI/CD pour une application Node.js.
Instructions
- Créer une app Node.js simple
- Pipeline avec : install, lint, test, build
- Archiver les artifacts
Structure du projet
my-node-app/
├── src/
│ └── index.js
├── tests/
│ └── index.test.js
├── package.json
└── Jenkinsfile
Jenkinsfile
pipeline {
agent {
docker {
image 'node:18-alpine'
args '-v /tmp:/tmp'
}
}
environment {
CI = 'true'
npm_config_cache = '/tmp/.npm'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 15, unit: 'MINUTES')
timestamps()
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'node --version'
sh 'npm --version'
}
}
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Lint') {
steps {
sh 'npm run lint || true'
}
}
stage('Test') {
steps {
sh 'npm test -- --coverage'
}
post {
always {
junit 'coverage/junit.xml'
publishHTML([
reportDir: 'coverage/lcov-report',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Archive') {
steps {
archiveArtifacts(
artifacts: 'dist/**/*',
fingerprint: true
)
}
}
}
post {
always {
cleanWs()
}
success {
echo '✅ Build succeeded!'
}
failure {
echo '❌ Build failed!'
}
}
}
Exercice 3 : Multibranch Pipeline
Objectif : Pipeline différent selon la branche.
Jenkinsfile
pipeline {
agent any
environment {
APP_NAME = 'my-app'
}
stages {
stage('Build') {
steps {
sh 'echo "Building ${APP_NAME}"'
sh 'npm ci && npm run build'
}
}
stage('Test') {
steps {
sh 'npm test'
}
}
stage('Deploy to Dev') {
when {
branch 'develop'
}
steps {
echo 'Deploying to Development...'
sh 'kubectl apply -f k8s/dev/'
}
}
stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
echo 'Deploying to Staging...'
sh 'kubectl apply -f k8s/staging/'
}
}
stage('Deploy to Production') {
when {
buildingTag()
}
input {
message 'Deploy to Production?'
ok 'Deploy'
submitter 'admin,deployers'
}
steps {
echo "Deploying tag ${TAG_NAME} to Production..."
sh 'kubectl apply -f k8s/prod/'
}
}
}
}
Exercice 4 : Pipeline Docker
Objectif : Build et push d'une image Docker.
Jenkinsfile
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'registry.example.com'
APP_NAME = 'my-app'
DOCKER_CREDENTIALS = credentials('docker-registry')
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
env.IMAGE_TAG = "${BUILD_NUMBER}-${GIT_COMMIT_SHORT}"
}
}
}
stage('Build Application') {
agent {
docker { image 'node:18' }
}
steps {
sh 'npm ci'
sh 'npm run build'
stash includes: 'dist/**', name: 'build-output'
}
}
stage('Build Docker Image') {
steps {
unstash 'build-output'
script {
docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}")
}
}
}
stage('Test Image') {
steps {
script {
docker.image("${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}").inside {
sh 'node --version'
sh 'npm --version'
}
}
}
}
stage('Push Image') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') {
def image = docker.image("${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}")
image.push()
image.push('latest')
}
}
}
}
stage('Cleanup') {
steps {
sh "docker rmi ${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG} || true"
}
}
}
post {
always {
cleanWs()
}
}
}
Projet Final : Pipeline CI/CD Complet
Objectif : Pipeline de production avec toutes les bonnes pratiques.
Architecture
Jenkinsfile
@Library('jenkins-shared-lib') _
pipeline {
agent none
environment {
APP_NAME = 'production-app'
DOCKER_REGISTRY = 'registry.example.com'
SONAR_PROJECT = 'my-org:production-app'
}
options {
buildDiscarder(logRotator(numToKeepStr: '20', daysToKeepStr: '30'))
timeout(time: 1, unit: 'HOURS')
timestamps()
disableConcurrentBuilds()
}
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['none', 'staging', 'production'],
description: 'Environment to deploy'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: 'Skip tests (use with caution)'
)
}
stages {
stage('Checkout') {
agent any
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
env.GIT_AUTHOR = sh(script: 'git log -1 --format="%an"', returnStdout: true).trim()
env.VERSION = "${BUILD_NUMBER}-${GIT_COMMIT_SHORT}"
}
stash includes: '**', excludes: '.git/**', name: 'source'
}
}
stage('Build & Test') {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: node
image: node:18
command: ['sleep', 'infinity']
volumeMounts:
- name: npm-cache
mountPath: /root/.npm
volumes:
- name: npm-cache
persistentVolumeClaim:
claimName: npm-cache
'''
}
}
stages {
stage('Install') {
steps {
container('node') {
unstash 'source'
sh 'npm ci'
}
}
}
stage('Lint') {
steps {
container('node') {
sh 'npm run lint'
}
}
}
stage('Unit Tests') {
when {
expression { return !params.SKIP_TESTS }
}
steps {
container('node') {
sh 'npm run test:unit -- --coverage'
}
}
post {
always {
junit 'coverage/junit.xml'
}
}
}
stage('Build') {
steps {
container('node') {
sh 'npm run build'
stash includes: 'dist/**', name: 'build-artifacts'
}
}
}
}
}
stage('Security Scan') {
agent any
steps {
unstash 'source'
sh 'npm audit --production || true'
// Trivy scan sera fait sur l'image Docker
}
}
stage('SonarQube') {
agent any
steps {
unstash 'source'
withSonarQubeEnv('SonarQube') {
sh '''
sonar-scanner \
-Dsonar.projectKey=${SONAR_PROJECT} \
-Dsonar.sources=src \
-Dsonar.tests=tests
'''
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 10, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
stage('Docker Build & Push') {
agent any
steps {
unstash 'source'
unstash 'build-artifacts'
script {
def image = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}")
// Security scan
sh "trivy image --exit-code 1 --severity HIGH,CRITICAL ${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} || true"
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-creds') {
image.push()
if (env.BRANCH_NAME == 'main') {
image.push('latest')
}
}
}
}
}
stage('Deploy Staging') {
when {
anyOf {
branch 'main'
expression { return params.DEPLOY_ENV == 'staging' }
}
}
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: kubectl
image: bitnami/kubectl
command: ['sleep', 'infinity']
'''
}
}
steps {
container('kubectl') {
withCredentials([file(credentialsId: 'kubeconfig-staging', variable: 'KUBECONFIG')]) {
sh """
kubectl set image deployment/${APP_NAME} \
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} \
-n staging
kubectl rollout status deployment/${APP_NAME} -n staging --timeout=5m
"""
}
}
}
}
stage('E2E Tests') {
when {
branch 'main'
}
agent {
docker { image 'cypress/included:latest' }
}
steps {
unstash 'source'
sh 'cypress run --config baseUrl=https://staging.example.com'
}
post {
always {
archiveArtifacts artifacts: 'cypress/screenshots/**', allowEmptyArchive: true
archiveArtifacts artifacts: 'cypress/videos/**', allowEmptyArchive: true
}
}
}
stage('Deploy Production') {
when {
allOf {
branch 'main'
expression { return params.DEPLOY_ENV == 'production' }
}
}
input {
message 'Deploy to Production?'
ok 'Deploy'
submitter 'admin,deployers'
parameters {
string(name: 'CONFIRM', defaultValue: '', description: 'Type "DEPLOY" to confirm')
}
}
steps {
script {
if (CONFIRM != 'DEPLOY') {
error('Deployment cancelled - confirmation not provided')
}
}
withCredentials([file(credentialsId: 'kubeconfig-prod', variable: 'KUBECONFIG')]) {
sh """
kubectl set image deployment/${APP_NAME} \
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} \
-n production
kubectl rollout status deployment/${APP_NAME} -n production --timeout=10m
"""
}
}
}
}
post {
always {
cleanWs()
}
success {
slackSend(
color: 'good',
message: "✅ *${JOB_NAME}* #${BUILD_NUMBER} succeeded\nVersion: ${VERSION}\nAuthor: ${GIT_AUTHOR}\n${BUILD_URL}"
)
}
failure {
slackSend(
color: 'danger',
message: "❌ *${JOB_NAME}* #${BUILD_NUMBER} failed\n${BUILD_URL}"
)
}
}
}
Récapitulatif du cours
Félicitations ! Vous avez complété le cours Jenkins.
Ce que vous avez appris
- ✅ Architecture et concepts Jenkins
- ✅ Installation (Docker, K8s, standalone)
- ✅ Interface et types de jobs
- ✅ Pipelines déclaratifs et scriptés
- ✅ Jenkinsfile et Pipeline as Code
- ✅ Plugins essentiels
- ✅ Agents et builds distribués
- ✅ Sécurité et authentification
- ✅ Bonnes pratiques
- ✅ Projets pratiques
Prochaines étapes
- Pratiquez avec les exercices
- Explorez Blue Ocean
- Configurez Jenkins sur Kubernetes
- Créez vos Shared Libraries