Table of Contents
- 1.1 Course Introduction
- 1.2 Jenkins Traditional vs. Jenkins Cloud-native
- 1.3 Introduction to GitOps
- 1.4 Jenkins Pipelines explained
- 1.5 Demo: Minikube Deployment
- 1.6 Demo: Deploying and customizing Jenkins with Helm
- 2.1 Scaling storage with PVCs
- 2.2 Traditional Agents vs. Cloud-native agents
- 2.3 Demo: Deploying a PVC on a Kubernetes cluster
- 2.4 Demo: Configuring Jenkins agents to use a PVC
- 3.1 Deployment with GitOps
- 3.2 Adding plugins to Jenkins
- 3.3 Demo: Adding a new Jenkins plugin with Helm
- 3.4 Kubernetes secrets as Jenkins credentials
- 3.5 Demo: Adding a Kubernetes secret to Jenkins
- 3.6 Demo: Build and push a Docker container with Jenkins
- 4.1 Les Shared Pipeline Libraries
- 4.2 Demo: Configuring a Shared Pipeline Library
- 4.3 Role-Based Access Control (RBAC) in Jenkins
- 4.4 Demo: Adding RBAC to Jenkins
- 4.5 Export of Jenkins metrics to Prometheus
- 4.6 Demo: Adding monitoring to Jenkins with Prometheus and Grafana
1. Configuring Jenkins Cloud native
1.1 Course Introduction
This course is intended for people who already have basic experience with Jenkins and want to move to a cloud-native setup in a Kubernetes cluster. If you are completely new to Jenkins, it is advisable to start with other Pluralsight courses, including the Continuous Integration with Jenkins course, and more specifically the Building a Modern CI/CD Pipeline with Jenkins and Running Jenkins in Kubernetes courses.
The four main skills covered in this course are:
- Configure Jenkins cloud-native: deploy Jenkins in a Kubernetes cluster (instead of a traditional on-premises installation).
- Scaling Jenkins cloud-native: Take advantage of Kubernetes’ ability to dynamically scale resources up or down based on load.
- Deploy full-stack applications with cloud-native Jenkins: deploy an application to a Kubernetes cluster from a Jenkins running in another Kubernetes cluster.
- Manage cloud-native Jenkins: configure Jenkins so that it best integrates into a corporate environment (RBAC, monitoring, shared libraries, etc.).
1.2 Jenkins Traditional vs. Jenkins Cloud-native
Traditional architecture
In a traditional Jenkins installation (often on-premises), the deployment follows this pattern:
- Separate controller and agents: first configure a Jenkins controller, then add build servers (agents) on separate machines.
- Agents running 24/7: in the days of everything on-prem, leaving instances running constantly was not considered a major problem.
- Fixed software versions per agent: if we need Python 3.10 and Python 3.12, we must maintain two separate agents with different labels so that Jenkins delegates the jobs to the correct server.
- Manual configuration: installation of plugins, management of their versions, and general configuration are done manually, which is time-consuming and prone to errors.
- Dedicated storage per agent: each agent manages its own workspaces, its own build pipelines and its own artifacts. If an agent runs out of storage or if too many jobs are running simultaneously, the limits can quickly be reached.
Cloud-native architecture
In comparison, a cloud-native Jenkins installation has the following advantages:
- Controller and agents configured in code: in this course, we use a Helm chart to deploy Jenkins on Kubernetes. The configuration of plugins, settings and agents is described programmatically.
- Automatic scaling: agents are created and destroyed depending on the jobs to be executed. We only pay for what we consume.
- Software version management via version control: no need to create a new agent to test a new software version; just change a tag or version number in the configuration file.
- Configuration entirely in version control: everything is controlled from Git (or SVN). Any change to Jenkins itself goes through a commit and review process.
- Shared storage between agents: thanks to Kubernetes and shared storage solutions, all agents have access to the same resources. No more problems with lack of space on one agent or unavailable resources on another.
Comparison Summary
| Appearance | Jenkins Traditional | Jenkins Cloud-native |
|---|---|---|
| Configuration | Manual, via the UI interface | In code (Helm, JCasC) |
| Agents | Permanent (24/7) | Dynamics (ephemeral) |
| Software versions | One agent per version | One label per configuration |
| Storage | Dedicated by agent | Shared (PVC Kubernetes) |
| Cost | Fixed cost (servers on) | Variable cost (pay as you go) |
| Scaling | Manual | Automatic via Kubernetes |
1.3 Introduction to GitOps
Jenkins cloud-native is part of a GitOps approach. GitOps is the concept that you never make manual changes to an application or infrastructure. Instead, any change is initiated by a commit in a version control system.
Benefits of GitOps
1. Mandatory review process
When a configuration change is submitted in version control (via a pull request), it must go through a review process. If the deployment pipeline is configured to only push to production from the main branch, no unreviewed changes can reach production.
2. Simplified rollback Any code (or infrastructure) change can be reverted by reverting a commit. This applies to both application code and infrastructure configuration (Terraform, AWS CDK, etc.).
3. Audit trail Commit history becomes a complete audit trail: every change is traced, timestamped and signed by a developer. This is particularly valuable during compliance audits.
4. Rebuilding non-production environments at any time If we need a QA, Dev or UAT environment, we can simply create a branch or copy of the application, deploy a new environment from this branch, commit the changes, then destroy the environment.
5. Production only updated by Jenkins In an environment with high compliance requirements, we can decide that only Jenkins has the right to make production deployments. Developers and operators do not have access to production: only changes that have passed CI and test procedures arrive in production.
Real story
The instructor reports that a developer accidentally deleted the production environment at a former company. Instead of spending the weekend manually rebuilding servers, his manager — who had built everything with Jenkins and Terraform — simply restarted the Jenkins pipeline. The entire infrastructure was described in Terraform, the data saved on S3. The pipeline took 1.5-2 minutes to restore everything. Result: 5 minutes to correct the developer’s permissions, the rest of the weekend free.
1.4 Jenkins Pipelines explained
Structure of a typical repository
A typical GitHub repository for a Jenkins-managed application typically contains:
my-app/
├── app/ # Code source de l'application (Python, Java, Go, etc.)
├── config/ # Fichiers de configuration (manifests Kubernetes, IaC, etc.)
├── docs/ # Documentation (fichiers Markdown, etc.)
├── Dockerfile # Pour containeriser l'application
└── Jenkinsfile # Instructions Jenkins pour tester, builder et déployer
The Jenkinsfile
The Jenkinsfile is located at the root of the repository and contains the instructions that Jenkins uses to test, build and deploy the application. Here is an example of a Jenkinsfile Hello World:
pipeline {
agent any
stages {
stage('Hello World') {
steps {
echo 'Hello World'
}
}
}
}
pipeline: root block which defines all instructionsagent: specifies where steps (or all steps) should runstages: contains the different stages (stages) of the pipelinestage: a specific stage with a descriptive namesteps: the commands to execute in this step
GitOps and Jenkinsfile
Any changes to the Jenkinsfile modify the build and deployment instructions given to Jenkins. In accordance with the GitOps philosophy, this file must be in version control, and any changes (additions or modifications) must be reviewed before reaching the QA or production environments.
1.5 Demo: Minikube Deployment
This demo shows how to install minikube, a local Kubernetes environment. It is intended for people who take the course locally and not on AKS (Azure Kubernetes Service), EKS (Amazon Elastic Kubernetes Service) or GKE (Google Kubernetes Engine).
The demonstration environment runs on an Ubuntu 22.04 VM.
Installing Docker
The first step is to install Docker Engine. There is a handy shell script at get.docker.com:
# Télécharger et inspecter le script d'installation Docker
curl https://get.docker.com | less
# Télécharger et exécuter le script d'installation Docker
curl https://get.docker.com | sh
# Activer le mode rootless (recommandé pour plus de sécurité)
dockerd-rootless-setuptool.sh install
# Vérifier que Docker fonctionne
docker run hello-world
Installing Minikube
From the official minikube documentation (for Linux):
# Télécharger le binaire minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
# Installer minikube
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Démarrer le cluster minikube
minikube start
Configuring kubectl
Minikube also installs kubectl, accessible via minikube kubectl. It is recommended to create an alias:
# Créer un alias pour kubectl
alias kubectl="minikube kubectl --"
# Vérifier les pods dans tous les namespaces
kubectl get pods -A
At this point, the local Kubernetes cluster is up and ready for Jenkins installation.
1.6 Demo: Deploying and customizing Jenkins with Helm
Installing Helm
Helm is the package manager for Kubernetes. It is available at helm.sh:
# Télécharger et exécuter le script d'installation Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Vérifier l'installation
helm version
Adding Helm Jenkins repository
# Lister les repositories Helm actuels (vide au départ)
helm repo list
# Ajouter le repository Jenkins
helm repo add jenkins-chart https://charts.jenkins.io
# Vérifier que le repository a été ajouté
helm repo list
# Lister les charts disponibles dans ce repository
helm search repo jenkins-chart
The helm search command returns the Jenkins chart available in this repository.
Exploring the Jenkins Helm Chart
The Jenkins chart GitHub repository is available at charts.jenkins.io. The values.yaml file contains all the configurable parameters. These parameters allow you to define:
- The list of plugins to install
- Configuring Kubernetes agents
- Storage configuration
- Configuring the Jenkins controller
- And many other options
The Jenkins Helm chart installs several essential plugins by default, including the Kubernetes plugin, which allows you to create Jenkins agents dynamically in the form of Kubernetes pods.
Installing Jenkins with Helm
# Vérifier l'état des services avant installation
kubectl get services
kubectl get pods
# Installer Jenkins avec le chart Helm
helm install jenkins jenkins-chart/jenkins --namespace default
# Suivre l'état des pods pendant le démarrage
kubectl get pods -w
Note: After running
helm install, Jenkins is deployed but not yet fully started. You must monitor the status of the pods withkubectl get pods -w.
Administrator password recovery
The chart deployment notes provide a command to recover the initial administrator password:
# Récupérer le mot de passe admin Jenkins
kubectl exec --namespace default -it svc/jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo
Accessing Jenkins via port-forward
In a minikube environment, the Jenkins service does not have an external IP. You must use port-forward:
# Port-forward pour accéder à Jenkins depuis la machine locale
# L'option --address permet d'exposer sur toutes les interfaces (utile pour les VMs)
kubectl port-forward --namespace default svc/jenkins 8080:8080 --address 0.0.0.0
Note: The
--address 0.0.0.0option is only necessary if Jenkins is running on a remote VM. If everything runs on the same machine, it is not essential.
Once the port-forward is active, you can access Jenkins via http://localhost:8080 (or the VM IP) and connect with the admin user and the password retrieved previously.
2. Scaling Jenkins Cloud native
2.1 Scaling storage with PVCs
When we talk about Jenkins scaling in Kubernetes, the first issue to address is that of storage. In Kubernetes, storage is defined in YAML files, notably via a Persistent Volume Claim (PVC).
Jenkins PVC Example
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
The way to deploy this PVC, in accordance with the GitOps approach, is simply:
kubectl apply -f jenkins-pvc.yaml
The Kubernetes Plugin and basic plugins
To use Kubernetes pods as Jenkins agents, the Kubernetes plugin must be installed. If we use the official Helm chart, it is already included by default. This is what the plugins section looks like in the chart (simplified):
controller:
installPlugins:
- kubernetes:4203.v1dd44f5b_1cf9
- workflow-aggregator:596.v8c21c963d92d
- git:5.2.2
- configuration-as-code:1775.v810dc950b_514
additionalPlugins: []
The additionalPlugins field will be used later to add additional plugins.
2.2 Traditional Agents vs. Cloud-native agents
Storage in a traditional Jenkins environment
In a traditional Jenkins installation:
- The Jenkins controller creates agents dynamically for each build
- Each agent has its own dedicated storage (attached virtual disk, EBS volume, etc.)
- The job workspace is created and the artifacts are saved in this dedicated storage
- At the end of the job, the agent is destroyed, but in a classic on-prem environment, the instances run 24/7
Problems:
- Loss of artifacts: when an agent is destroyed, its data may be lost
- Unable to share: an agent cannot access artifacts created by another agent
- Cost: if we migrate to the cloud, instances running 24/7 generate significant costs
Storage in a cloud-native Jenkins environment
In a Kubernetes environment:
- There is a shared Persistent Volume in the cluster
- When a new agent is created, it mounts this Persistent Volume
- All agents have access to the same shared storage
- When jobs are finished, agents are destroyed, but workspaces, artifacts and logs are kept on the persistent volume
Jenkins Controller
│
├─ Build Agent 1 ──┐
├─ Build Agent 2 ──┤── Persistent Volume (PVC)
└─ Build Agent 3 ──┘
This model also allows build dependencies to be shared between agents, avoiding re-downloading the same dependencies each time.
2.3 Demo: Deploying a PVC on a Kubernetes cluster
Inspection of existing PVCs
After deploying Jenkins via Helm chart, a PVC is already created by default. We can see this by looking at line 1241 of the values.yaml file of the Jenkins Helm chart on GitHub:
# Extrait du values.yaml du Helm chart Jenkins (ligne ~1241)
persistence:
enabled: true
# storageClass: "-"
accessMode: ReadWriteOnce
size: "8Gi"
This default PVC is 8 GB. We will create a new 10 GB PVC:
# jenkins-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
PVC deployment
# Déployer le PVC
kubectl apply -f jenkins-pvc.yaml
# Vérifier les PVCs disponibles
kubectl get pvc
After the application, we see two PVCs: the one created by the Helm chart and the new jenkins-pvc. However, this PVC is not yet used by Jenkins — it is simply available in the cluster. The next demonstration shows how to configure Jenkins to use it.
2.4 Demo: Configuring Jenkins Agents to Use a PVC
Inspecting the default configuration from the UI
Before applying the configuration by code, we can observe the existing configuration in the Jenkins interface:
- Go to Manage Jenkins > Clouds
- The Kubernetes cloud is already configured (using the Helm chart)
- Under Pod Templates, there is a default template
- In the Volumes section, no volumes are mounted
Configuration via values.yaml
We create a new values.yaml file with the agent configuration:
# values.yaml pour le Helm upgrade
controller:
installPlugins:
- kubernetes:4203.v1dd44f5b_1cf9
- workflow-aggregator:596.v8c21c963d92d
- git:5.2.2
- configuration-as-code:1775.v810dc950b_514
agent:
podTemplates:
customPod: |
- name: customPod
label: demo
containers:
- name: jnlp
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "1"
memory: "2Gi"
volumes:
- persistentVolumeClaim:
claimName: jenkins-pvc
readOnly: false
mountPath: /var/data
workspaceVolume:
persistentVolumeClaimWorkspaceVolume:
claimName: jenkins-pvc
readOnly: false
This template defines:
- Name:
customPod - Label:
demo(jobs that use this label will be executed on this type of pod) - Resources: 1 vCPU, 2 GB RAM
- Volume: PVC
jenkins-pvcmounted on/var/data - Working directory: in the PVC (
/var/data/agent)
Note: The detailed configuration of the agents is available from line 913 of
values.yamlof the Jenkins Helm chart on GitHub.
Applying configuration with Helm Upgrade
# Mettre à jour Jenkins avec le nouveau values.yaml
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml
# Vérifier l'état des pods après la mise à jour
kubectl get pods
The helm upgrade command is the standard idempotent command for applying changes to the Jenkins chart. It takes the Jenkins service (the name given during the helm install), the source chart, and the values.yaml file.
After the upgrade, Jenkins goes to revision 2. In the Jenkins interface, under Manage Jenkins > Clouds > Pod Templates, we now see the customPod template with:
- The
demolabel - PVC mounted on
/var/data - The working directory configured on
/var/data/agent
Testing data persistence between agents
To validate operation, we create two jobs:
Job demo-1 (label: demo):
pipeline {
agent { label 'demo' }
stages {
stage('Download') {
steps {
// Télécharger le fichier Jenkins war vers le PVC
sh 'curl -O https://get.jenkins.io/war-stable/latest/jenkins.war'
}
}
}
}
Job demo-2 (label: demo):
pipeline {
agent { label 'demo' }
stages {
stage('List PVC') {
steps {
// Lister le contenu du PVC
sh 'ls -la /var/data/'
}
}
}
}
Test scenario:
- Run
demo-2→ The PVC is empty (only anagent/folder) - Run
demo-1→ Downloadjenkins.warin PVC - Re-run
demo-2→ The filejenkins.waris visible in the PVC
During job execution, you can observe Kubernetes scaling in real time:
# Observer la création et destruction des pods
kubectl get pods -w
We see the agent pods being created (state Pending → Running) during the jobs and being destroyed (Terminating) after the end of the jobs. This is cloud-native scaling in action.
3. Deploying Applications with Jenkins Cloud native
3.1 Deployment with GitOps
Manual deployment (without CI/CD)
To understand the value of Jenkins CI/CD, let’s first look at what a typical manual deployment looks like:
| Step | Description | Estimated duration |
|---|---|---|
| Unit Testing | Executed manually by the engineer | < 1 second |
| Application build | Compilation, packaging (varies depending on the project) | 3 minutes to several hours |
| Deployment | Copying binaries, restarting services | 5 to 10 minutes |
| Post-deployment monitoring | Logs and traffic monitoring | ~10 minutes |
Manual deployment problems:
- Each step may be skipped or performed incorrectly
- The engineer can be absent between stages (coffee break, etc.)
- No traceability on time between steps
- Highly prone to human error
- Builds on individual machines → “it works on my machine!”
With CI/CD (Jenkins)
Introducing an orchestration tool like Jenkins significantly improves the process:
- Each step is automatically triggered by the previous one
- The engineer does not launch any steps manually; it only monitors failures
- Everything is built in a standardized build environment (no more “it works on my machine”)
- Entire process is traced and repeatable
With GitOps (Jenkins + Git)
By combining CI/CD with GitOps, we go even further:
Développeur push → Git (version control)
│
├─ Branche feature/bugfix → Tests + Build → Déploiement QA/UAT
└─ Branche main + release → Tests + Build → Déploiement Production
Conditional logic: you can define rules like:
- Any branch that receives a push → running unit tests and build
- Main branch → automatic deployment to QA or UAT environment
- Release branch → automatic deployment to production
Because the application code and pipeline code are in version control, changes are reviewed before being applied, and Jenkins executes them with the conditional logic defined.
Result: we go from a situation where 1 engineer spends 30 minutes typing commands manually, to a situation where everything runs automatically in a controlled build environment. The engineer is solely dedicated to pushing changes into version control, reviewing pull requests, and troubleshooting failures.
3.2 Adding plugins to Jenkins
The additionalPlugins section in values.yaml
In the Jenkins Helm chart, there is an additionalPlugins field in the values.yaml to install additional plugins:
controller:
installPlugins:
- kubernetes:4203.v1dd44f5b_1cf9
- workflow-aggregator:596.v8c21c963d92d
- git:5.2.2
- configuration-as-code:1775.v810dc950b_514
additionalPlugins:
- docker-workflow:580.vc0c340686b_54
- kubernetes-credentials-provider:1.262.v2670ef7ea_0c5
How to find the ID and version of a plugin
To find the exact values to use:
- Go to
plugins.jenkins.io(Jenkins Plugins Index) - Search for the desired plugin (e.g.: “Docker Pipeline”)
- Copy exactly:
- The plugin ID (e.g.:
docker-workflow) - The version (e.g.:
580.vc0c340686b_54)
- Use the format:
plugin-id:version
Important: You must copy and paste the exact values because the Helm chart is strict on the format.
Managing plugin updates via GitOps
Managing plugins via values.yaml means that:
- Any plugin version update requires a modification of
values.yaml - This change is submitted via a pull request and is going through the review process
- Once approved, the
helm upgradecommand applies the changes
This applies the GitOps principle not only to applications deployed by Jenkins, but also to the Jenkins installation itself.
3.3 Demo: Adding a new Jenkins plugin with Helm
In this demonstration, we add two new plugins:
- Docker Pipeline plugin (ID:
docker-workflow) - Kubernetes Credentials Provider (ID:
kubernetes-credentials-provider)
Prerequisite check in Jenkins UI
Before adding the plugins, check that they are not already installed:
- Manage Jenkins > Plugins > Installed plugins
- Search for “Docker” → no Docker plugins found
- Search for “Kubernetes Credentials” → the base
kubernetes-credentialsis present, but not thekubernetes-credentials-provider
Update values.yaml
controller:
installPlugins:
- kubernetes:4203.v1dd44f5b_1cf9
- workflow-aggregator:596.v8c21c963d92d
- git:5.2.2
- configuration-as-code:1775.v810dc950b_514
additionalPlugins:
- docker-workflow:580.vc0c340686b_54
- kubernetes-credentials-provider:1.262.v2670ef7ea_0c5
Application via Helm Upgrade
# Mettre à jour Jenkins avec les nouveaux plugins
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml
# Suivre l'état des pods
kubectl get pods
After a few seconds, the pods restart. Once at least one pod is in Ready state, you can access the Jenkins interface and check the installed plugins under Manage Jenkins > Plugins > Installed plugins.
3.4 Kubernetes secrets as Jenkins credentials
Why use Kubernetes secrets?
Whatever application we deploy, Jenkins will need secrets (API tokens, passwords, SSH keys, etc.). There are several ways to make these secrets available to Jenkins in Kubernetes.
Recommended method: Base64 encoded Kubernetes secrets
The most secure method is to create Kubernetes secrets with Base64 encoded values:
# Exemple de secret Kubernetes
apiVersion: v1
kind: Secret
metadata:
name: secret-name
type: Opaque
data:
key-name: <BASE64_ENCODED_VALUE>
To encode a value in Base64:
# IMPORTANT : l'option -n évite d'ajouter un caractère de nouvelle ligne
# ce qui est crucial pour éviter des erreurs d'authentification
echo -n "mon-secret" | base64
Warning: The
-noption is essential. Without it, a newline character is appended to the end of the encoded string, making the secret invalid during authentication.
Configuration in Jenkins via JCasC (Configuration as Code)
Once the Kubernetes secret has been created, we use the Jenkins Configuration as Code (JCasC) plugin to tell Jenkins to use this secret as a credential.
The JCasC configuration can be invoked directly from the Helm chart values.yaml file. It is recommended to create a separate YAML file for the JCasC configuration:
# casc-credentials.yaml (Configuration as Code)
credentials:
system:
domainCredentials:
- credentials:
- string:
scope: GLOBAL
id: "docker-hub-token"
secret: "${docker-secret/api-token}"
description: "Docker Hub API Token"
- usernamePassword:
scope: GLOBAL
id: "git-token"
username: "my-github-username"
password: "${git-token-secret/GIT_TOKEN}"
description: "GitHub Personal Access Token"
unclassified:
globalCredentialsConfiguration:
configuration:
providerFilter: "none"
typeFilter: "none"
jenkins:
clouds:
- kubernetes:
...
# Mapping entre les credentials Jenkins et les secrets Kubernetes
additionalExistingSecrets:
- name: docker-secret
keyName: api-token
- name: git-token-secret
keyName: GIT_TOKEN
Important key: The
nameinadditionalExistingSecretsand thekeyNamemust match exactly the names and keys of your Kubernetes secrets.
3.5 Demo: Adding a Kubernetes secret to Jenkins
Secrets files in the project
For this demonstration, two secret YAML files have been prepared:
docker-secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: docker-token
type: Opaque
data:
api-token: <TOKEN_DOCKERHUB_EN_BASE64>
secret.yaml (for the GitHub token):
apiVersion: v1
kind: Secret
metadata:
name: git-token-secret
type: Opaque
data:
GIT_TOKEN: <TOKEN_GITHUB_EN_BASE64>
Applying Kubernetes secrets
# Vérifier les secrets existants (avant)
kubectl get secrets
# Appliquer le secret Docker Hub
kubectl apply -f docker-secret.yaml
# Vérifier les secrets existants (après)
kubectl get secrets
Second method: kubectl create secret (less secure)
There is another way to create a Kubernetes secret, but it exposes the value in plain text:
# ATTENTION : cette méthode expose le secret en clair dans la ligne de commande
# et dans l'historique du shell. À éviter en production.
kubectl create secret generic git-token-secret \
--from-literal=GIT_TOKEN=mon-token-en-clair
Security Warning: The
kubectl create secretmethod with--from-literalexposes the secret in plain text. In a production environment, favor the method with the YAML file and the Base64 encoded value, or better yet, use a dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, etc.).
Verifying secrets
# Vérifier que les secrets sont bien créés
kubectl get secrets
# Décrire un secret pour voir ses métadonnées (sans voir la valeur)
kubectl describe secret docker-token
JCasC configuration in values.yaml
We update the values.yaml to point to the JCasC configuration:
controller:
JCasC:
configScripts:
credentials: |
credentials:
system:
domainCredentials:
- credentials:
- string:
scope: GLOBAL
id: "docker-hub-token-credential"
secret: "${docker-token/api-token}"
description: "Docker Hub API Token"
- usernamePassword:
scope: GLOBAL
id: "git-token"
username: "chris-blackden"
password: "${git-token-secret/GIT_TOKEN}"
description: "GitHub PAT"
additionalExistingSecrets:
- name: docker-token
keyName: api-token
- name: git-token-secret
keyName: GIT_TOKEN
Applying and fixing a naming problem
# Appliquer la configuration mise à jour
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml
During this demonstration, a typical error is illustrated: an inconsistency in the naming of the secret. The name docker-secret had been used in the JCasC configuration, but the Kubernetes secret was called docker-token. Jenkins cannot start correctly.
# Diagnostiquer le problème
kubectl describe pods
# → On voit une erreur : secret "docker-secret" non trouvé
Solution: Correct the name in the JCasC configuration to exactly match the Kubernetes secret name, then delete the failing pod to force the rebuild.
After correction and redeployment, the credentials appear under Manage Jenkins > Credentials:
docker-hub-token-credential(Secret text)git-token(Username with password)
3.6 Demo: Build and push a Docker container with Jenkins
Jenkins job configuration
In the Jenkins interface:
- Create a new pipeline
- Set the source as a Git repository
- Repository used: fork of
azure-voting-app-redis, branchkubernetes-helm - Use previously configured
git-tokencredentials for authentication
The Jenkinsfile for the Docker build
Here is the Jenkinsfile used in the demo (kubernetes-helm branch of the azure-voting-app-redis repository):
pipeline {
// IMPORTANT : on délègue à un agent avec le label 'docker'
// et non à un pod Kubernetes, car builder un container Docker
// dans un autre container Docker est une très mauvaise pratique
agent { label 'docker' }
stages {
stage('Build and Push') {
steps {
// Construire et pousser l'image vers Docker Hub
// en utilisant le credential Docker Hub configuré précédemment
docker.withRegistry('https://registry.hub.docker.com', 'dockerhub') {
def app = docker.build("my-dockerhub-username/azure-voting-app")
app.push('latest')
}
}
}
}
}
Architectural note: It is strongly recommended not to build a Docker container inside another container (Docker-in-Docker or DinD). This is why we delegate this job to a Jenkins node with the
dockerlabel, which is an agent configured with direct access to the host’s Docker daemon.
Troubleshooting: Bad credential type
A classic error is illustrated in the demonstration: the credential docker-hub-token-credential was configured as Secret text in JCasC, but the Docker Plugin expects a credential of type Username with password.
The Jenkins error message may be misleading:
ERROR: Could not find credentials matching dockerhub-token-credential
In reality, the credential does exist, but its type is incorrect. To check it:
- Manage Jenkins > Credentials
- Observe the type of credential (here:
Secret text) - Compare with what the Docker plugin expects (
Username with password)
Solution: create a second credential named dockerhub of type Username with password with the Docker Hub username and API token, then update the Jenkinsfile to reference this corrected credential.
Commit the fix and restart the build
// Version corrigée du Jenkinsfile
pipeline {
agent { label 'docker' }
stages {
stage('Build and Push') {
steps {
docker.withRegistry('https://registry.hub.docker.com', 'dockerhub') {
// 'dockerhub' est maintenant le bon credential (Username+Password)
def app = docker.build("my-dockerhub-username/azure-voting-app")
app.push('latest')
}
}
}
}
}
After this corrected commit (visible in the Changes tab of the Jenkins job), the build runs successfully:
- Docker Hub authentication successful (
Docker login successful) - Checkout of the repository with the credential
git-token - Build Docker image
- Push to Docker Hub
4. Managing Jenkins Cloud native
4.1 Shared Pipeline Libraries
What is a Shared Pipeline Library?
A Shared Pipeline Library is Groovy DSL code that defines deployment instructions. Instead of having a different copy of the pipeline in each application repository (each Jenkinsfile), we can externalize these instructions in a separate Git repository. Applications then reference this shared library.
Use cases
Separation of Concerns:
- Developers work on application code without needing to modify the pipeline
- Infrastructure/DevOps engineers maintain pipeline separately
- This is also a security check: developers cannot modify the infrastructure
DRY (Don’t Repeat Yourself) principle for microservices:
In a typical Kubernetes cluster, there are often many microservices deployed, each from its own repository. These services are often very similar (all containerized, all with a Kubernetes Deployment). Rather than having a different Jenkinsfile in each repository, we can have a single shared pipeline that all these repositories use.
Key advantage: If the deployment instructions need to be updated, it is done in one place (the shared library) rather than in each application repository.
Structure of a Shared Pipeline Library
demo-shared-pipeline/ (repository Git séparé)
├── vars/
│ ├── myPipeline.groovy # Pipeline réutilisable
│ └── anotherPipeline.groovy # Autre pipeline
└── src/
└── org/example/
└── Utils.groovy # Classes utilitaires
Configuration via Helm Chart (JCasC)
# Dans values.yaml, section JCasC
controller:
JCasC:
configScripts:
global-library: |
unclassified:
globalLibraries:
libraries:
- name: "shared-library"
retriever:
modernSCM:
scm:
git:
credentialsId: "git-token"
remote: "https://github.com/chris-blackden/demo-shared-pipeline.git"
traits:
- gitBranchDiscovery
defaultVersion: "test"
Important points:
name: the name that will be used to reference the library in the Jenkinsfilesremote: the Git URL of the shared librarycredentialsId: the credential to authenticate to the repositorydefaultVersion: the default branch, tag or commit to use
Versioning of Shared Pipeline Libraries
The defaultVersion field accepts:
- A branch name (
main,test,develop) - A Git tag (
v1.0.0,2024-01-15) - A commit hash
This allows you to manage library versions: you can test a new version on a test Jenkins before pushing it into production.
4.2 Demo: Configuring a Shared Pipeline Library
Browsing the library repository
In this demonstration, we use a demo-shared-pipeline repository on GitHub with two branches:
main: stable production branchtest: test branch for new features
We will configure Jenkins to use the test branch, to show that we can use any branch.
library.yaml file
We create a new library.yaml file for the library configuration:
# library.yaml
unclassified:
globalLibraries:
libraries:
- name: "shared-library"
retriever:
modernSCM:
scm:
git:
credentialsId: "git-token"
remote: "https://github.com/chris-blackden/demo-shared-pipeline.git"
traits:
- gitBranchDiscovery
defaultVersion: "test"
Application via Helm Upgrade
# Avant l'upgrade, s'assurer que le repo Helm est à jour
helm repo update
# Appliquer la configuration
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml -f library.yaml
# Surveiller l'état des pods
kubectl get pods
Tip: If warnings about Jenkins versions appear in the interface, run
helm repo updateto retrieve the latest version of the chart. This may also update the Jenkins version itself.
Checking in Jenkins interface
After the upgrade:
- Manage Jenkins > System > Search for “pipeline”
- The Global Pipeline Libraries section is now visible
- The
shared-librarylibrary is configured with:
- The default version
test(notmain) - SCM Git configuration with credentials and repository URL
- The
gitBranchDiscoverytrait
4.3 Role-Based Access Control (RBAC) in Jenkins
Why RBAC?
In an enterprise environment, it is essential to control who can do what in Jenkins. We don’t want any connected user to be able to:
- Change global Jenkins configuration
- Cancel or reconfigure jobs from other teams
- View or modify sensitive credentials
The Matrix Authorization Strategy Plugin
The recommended plugin is the Matrix Authorization Strategy (identifier: matrix-auth). This plugin:
- Supports Configuration as Code (JCasC)
- Offers Job DSL support
- Allows you to assign permissions to users or groups
- Allows you to manage fine-grained permissions on jobs, nodes, credentials, etc.
Configuration syntax via JCasC
Compact syntax (less flexible, can become obsolete quickly):
jenkins:
authorizationStrategy:
globalMatrix:
permissions:
- "Overall/Read:anonymous"
- "Overall/Read:authenticated"
- "Job/Build:authenticated"
- "Job/Read:authenticated"
Extended syntax (recommended, more control):
jenkins:
authorizationStrategy:
globalMatrix:
entries:
- group:
name: "authenticated"
permissions:
- "Credentials/View"
- group:
name: "anonymous"
permissions:
- "Overall/Read"
- user:
name: "test-user"
permissions:
- "Overall/Read"
- "Job/Read"
Available permission categories
| Category | Permissions |
|---|---|
| Overall | Administer, Read, SystemRead |
| Credentials | Create, Delete, ManageDomains, Update, View |
| Agent | Build, Configure, Connect, Create, Delete, Disconnect |
| Job | Build, Cancel, Configure, Create, Delete, Discover, Move, Read, ViewStatus, Workspace |
| Run | Delete, Replay, Update |
| View | Configure, Create, Delete, Read |
| SCM | Tag |
Note: The
Overall/Administerpermission is equivalent to full administrator access — use sparingly.
Supported identity types
anonymous: any unauthenticated userauthenticated: any authenticated user, regardless of their identity- Specific user: by username
- Group: per group (ideal with LDAP/Active Directory/Okta)
Best practice: In production, synchronize Jenkins with an Identity Provider (Active Directory, Google Workspace, Okta, etc.) and manage permissions by groups, not by individual users.
4.4 Demo: Adding RBAC to Jenkins
Installing the Matrix Auth plugin
The plugin is added to values.yaml:
controller:
additionalPlugins:
- docker-workflow:580.vc0c340686b_54
- kubernetes-credentials-provider:1.262.v2670ef7ea_0c5
- matrix-auth:3.2.2 # Nouveau plugin RBAC
rbac.yaml file
We create a separate rbac.yaml file for the RBAC configuration:
# rbac.yaml
jenkins:
authorizationStrategy:
globalMatrix:
entries:
- group:
name: "authenticated"
permissions:
- "Credentials/View"
- group:
name: "anonymous"
permissions:
- "Overall/Read"
- user:
name: "test-user"
permissions:
- "Overall/Read"
- "Job/Read"
securityRealm:
local:
allowsSignup: false
users:
- id: "test-user"
password: "Today123" # NE JAMAIS FAIRE ÇA EN PRODUCTION !
properties:
- mailer:
emailAddress: "test@example.com"
Security warning: Configuring a local user with a clear password in a configuration file is never to be done in production. This user is only for home lab testing.
Applying RBAC configuration
# Appliquer la configuration RBAC
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml -f library.yaml -f rbac.yaml
# Surveiller les pods
kubectl get pods
Testing behavior with permissions
Anonymous user:
- Can see the list of jobs (
Overall/Readpermission) - Cannot log in (no account)
Test user (permissions: Overall/Read + Job/Read):
- Can view jobs
- Cannot run a build (no
Job/Build) - Cannot modify Jenkins configuration (no
Overall/Administer) - Can see credentials (inherited from
Credentials/Viewfor authenticated users)
Dangerous behavior to be aware of:
In the demonstration, the trainer illustrates a trap case: if you apply RBAC permissions without explicitly including permissions for the admin user, you can lock yourself out of Jenkins.
Result observed with the admin:
- Access to Jenkins interface, but…
- Unable to access Manage Jenkins
- Unable to see agents
- Unable to see jobs
- Only action available: view credentials
Solution: add the Overall/Administer permission to the admin user in the RBAC file and reapply:
- user:
name: "admin"
permissions:
- "Overall/Administer"
- user:
name: "test-user"
permissions:
- "Overall/Read"
- "Job/Read"
- "Overall/Administer" # Ajouté pour le test
# Réappliquer après modification
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml -f library.yaml -f rbac.yaml
Lesson learned: always include permissions for the administrator user when configuring RBAC, and never rely solely on changes from the UI interface to correct permissions — because without access to the UI, you have to go through the command line.
4.5 Export of Jenkins metrics to Prometheus
Jenkins monitoring plugins
To export Jenkins metrics to Prometheus, two plugins are required:
- Prometheus plugin (ID:
prometheus):
- Expose a
/prometheusHTTP endpoint on Jenkins URL - Publish metrics in text-based exposition format (Prometheus)
- Depends on Metrics plugin
- Metrics plugin (ID:
metrics):
- Exposes a large number of internal Jenkins metrics
- Provide the data that the Prometheus plugin will export
Installing plugins
controller:
additionalPlugins:
- docker-workflow:580.vc0c340686b_54
- kubernetes-credentials-provider:1.262.v2670ef7ea_0c5
- matrix-auth:3.2.2
- prometheus:2.5.0 # Plugin Prometheus
- metrics:4.2.18-448.v1f6f1788f0d1 # Plugin Metrics (requis)
The Prometheus + Grafana ecosystem
The recommended monitoring stack is:
Jenkins (/prometheus endpoint)
│
└── Prometheus (scraping toutes les X secondes)
│
└── Grafana (dashboards, visualisations)
Why Prometheus and Grafana?
- These are open source tools
- Prometheus is one of the de facto standards for cloud-native monitoring
- Grafana offers ready-to-use dashboards for Jenkins via its public dashboard gallery
- If your organization already has a monitoring tool, you can use that instead
Find ready-to-use Grafana dashboards
On grafana.com/grafana/dashboards, you can search for “Jenkins” to find community dashboards. Dashboards with the Prometheus tag are compatible with our setup.
These dashboards can be imported into Grafana either by their numeric ID, or by downloading their configuration JSON file.
Examples of metrics visible in these dashboards:
- Number of Jenkins executors in use
- Number of jobs in queue
- Average build duration
- Number of builds by status (success, failure, unstable)
- Controller memory and CPU usage
4.6 Demo: Adding monitoring to Jenkins with Prometheus and Grafana
Verifying the Prometheus endpoint
Once the plugins are installed, the /prometheus endpoint is available:
# Vérifier que l'endpoint expose des métriques
curl http://localhost:8080/prometheus
# Filtrer une métrique spécifique (ex. : exécuteurs en cours d'utilisation)
curl http://localhost:8080/prometheus | grep "jenkins_executors_in_use"
The output is dense and not human readable — this is normal. It is intended to be ingested by Prometheus.
Configuring the Prometheus plugin in Jenkins
In Manage Jenkins > System > Search for “Prometheus”:
- Path:
prometheus(default, configurable) - Collecting metrics period:
120seconds by default → changed to10seconds for demonstration
Configuring Prometheus to scrape Jenkins
Prometheus runs in a separate container on the network. Its configuration file is typically /etc/prometheus/prometheus.yaml:
# prometheus.yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Nouvelle configuration pour Jenkins
- job_name: 'jenkins'
metrics_path: /prometheus
static_configs:
- targets: ['10.1.10.194:8080'] # IP et port du controller Jenkins
Note: The URL must match the path configured in the Prometheus plugin in Jenkins. If we changed the path in Jenkins, we must reflect it here also in
metrics_path.
After modifying the file, restart Prometheus:
# Dans le container Prometheus
service prometheus restart
# ou
systemctl restart prometheus
Verification in Prometheus interface
In the Prometheus interface (http://prometheus-server:9090):
- Status > Target health
- The Jenkins endpoint should appear with a green (UP) status
- You can click on it to see the raw metrics
Configuring Grafana
Step 1: Add Prometheus as Data Source
- Grafana menu (icon at top left) > Connections > Data sources
- Add new data source > Choose Prometheus
- Configure the Prometheus server URL (not the Jenkins endpoint directly!)
- Click on Save & test → “Successfully queried Prometheus server”
Step 2: Import a Jenkins Dashboard
- In Grafana, click on the + button or go to Dashboards
- Choose Import a dashboard
- Enter the ID of the Jenkins dashboard found on grafana.com (e.g.:
9964for “Jenkins: Performance and Health Overview”) - Select the Prometheus data source created in the previous step
- Click on Import
Real-time validation
To validate that monitoring works in real time:
- In Grafana, configure auto-refresh to 5 seconds and time range to Last 5 minutes
- Locate the Executor In-use metric in the dashboard
- In Jenkins, launch several jobs simultaneously
- Watch the Grafana dashboard update (with a delay of a few seconds due to scraping intervals)
The data collection chain:
- Jenkins updates its metrics every 10 seconds (configured in the plugin)
- Prometheus scrapes the Jenkins endpoint every 15 seconds
- Grafana refreshes the dashboard every 5 seconds
The overall delay can therefore reach 25-30 seconds between an event and its display in Grafana. This is normal in a standard configuration.
5. Summary and key concepts
Overall course architecture
Git Repository (version control)
│
├── Application Code
├── Dockerfile
├── Kubernetes Manifests
├── Jenkinsfile (référence à la shared library)
└── values.yaml (configuration Jenkins)
│
▼
Jenkins Controller (Kubernetes Pod)
│
┌─────────┼─────────────────────────┐
│ │ │
Plugins JCasC Config Shared Library
(via Helm) (credentials, (Git repo externe)
RBAC, libraries)
│
▼
Jenkins Build Agents (Kubernetes Pods, éphémères)
│
┌─────────┼──────────────┐
│ │ │
Tests Docker Build Deploy vers
& Push Kubernetes
│
Persistent Volume Claim (PVC)
(stockage partagé entre agents)
Key Tools and Technologies
| Tool | Role |
|---|---|
| Jenkins | CI/CD Orchestrator |
| Kubernetes | Container orchestration platform |
| Helm | Kubernetes Package Manager |
| minikube | Local Kubernetes cluster for development |
| Docker | Containerization of applications |
| JCasC (Configuration as Code) | Declarative configuration of Jenkins in YAML |
| Prometheus | Collection and storage of metrics |
| Grafana | Metrics visualization and dashboards |
| Groovy DSL | Language of Jenkinsfiles and Shared Pipeline Libraries |
Essential Helm Commands
# Ajouter un repository Helm
helm repo add jenkins-chart https://charts.jenkins.io
# Mettre à jour les repositories
helm repo update
# Lister les charts disponibles
helm search repo jenkins-chart
# Installer Jenkins
helm install jenkins jenkins-chart/jenkins
# Mettre à jour la configuration (idempotent)
helm upgrade jenkins jenkins-chart/jenkins -f values.yaml
# Lister les releases Helm
helm list
Essential kubectl commands in the context of this course
# Vérifier les pods
kubectl get pods
kubectl get pods -w # Watch mode
# Vérifier les services
kubectl get services
# Vérifier les PVCs
kubectl get pvc
# Vérifier les secrets
kubectl get secrets
# Appliquer une configuration
kubectl apply -f fichier.yaml
# Décrire un objet (détails et événements)
kubectl describe pod <nom-du-pod>
kubectl describe secret <nom-du-secret>
# Port-forward pour accéder à un service
kubectl port-forward svc/jenkins 8080:8080 --address 0.0.0.0
GitOps Principles to Remember
- All changes go through Git: never direct manual modifications in production
- Mandatory Pull Requests: each change (application, infrastructure, Jenkins config) goes through a review process
- Rollback = revert commit: rolling back to a previous state is as simple as rolling back to a previous commit
- Jenkins does not only manage apps: Jenkins itself is managed via GitOps (Helm chart + JCasC)
- Automatic audit trail: Git history is change traceability
Good security practices
- Secrets: use Base64 encoded Kubernetes secrets; avoid plain text in configuration files
- Option
-nwith Base64: always useecho -n "value" | base64to avoid newline characters - RBAC: do not give more permissions than necessary; manage permissions by groups
- Jenkins Administrator: Always include explicit permissions for administrator in RBAC configuration
- Credentials: never put clear credentials in Jenkinsfiles or in version control
- Docker-in-Docker: avoid building containers within containers; delegate to an agent with direct access to the Docker daemon
- Separation of responsibilities: use Shared Pipeline Libraries to separate application code from infrastructure code
Recommended configuration file structure
For a real project, this is the recommended file structure for managing Jenkins via GitOps:
jenkins-config/
├── values.yaml # Configuration principale du chart Jenkins
├── casc-credentials.yaml # Credentials via JCasC
├── rbac.yaml # Configuration RBAC
├── library.yaml # Shared Pipeline Libraries
├── secrets/
│ ├── docker-secret.yaml # Secret Docker Hub (NE PAS COMMITTER en prod !)
│ └── git-token-secret.yaml # Secret GitHub (NE PAS COMMITTER en prod !)
└── README.md # Documentation du projet Jenkins
Reminder: Files containing secrets (even Base64 encoded) should not be committed to a public repository. Use
.gitignore, solutions like AWS Secrets Manager, HashiCorp Vault, or tools likesealed-secretsfor Kubernetes.
Search Terms
jenkins · cloud-native · ci · cd · ci/cd · git · devops · helm · configuration · via · configuring · gitops · kubernetes · plugin · prometheus · installing · secrets · pipeline · shared · applying · cloud · deployment · grafana · jcasc