Intermediate

Using Jenkins for Cloud-native CI CD

jenkins · cloud-native · ci · cd · ci/cd · git · devops · helm · configuration · via · configuring · gitops · kubernetes · plugin · prometheus · installing · secrets · pipeline · shared ·...

Table of Contents

  1. Module 1 — Jenkins Cloud-native Configuration
  1. Module 2 — Scaling of Jenkins Cloud-native
  1. Deploying Applications with Jenkins Cloud native
  1. Managing Jenkins Cloud native
  1. Summary and key concepts

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:

  1. Configure Jenkins cloud-native: deploy Jenkins in a Kubernetes cluster (instead of a traditional on-premises installation).
  2. Scaling Jenkins cloud-native: Take advantage of Kubernetes’ ability to dynamically scale resources up or down based on load.
  3. Deploy full-stack applications with cloud-native Jenkins: deploy an application to a Kubernetes cluster from a Jenkins running in another Kubernetes cluster.
  4. 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

AppearanceJenkins TraditionalJenkins Cloud-native
ConfigurationManual, via the UI interfaceIn code (Helm, JCasC)
AgentsPermanent (24/7)Dynamics (ephemeral)
Software versionsOne agent per versionOne label per configuration
StorageDedicated by agentShared (PVC Kubernetes)
CostFixed cost (servers on)Variable cost (pay as you go)
ScalingManualAutomatic 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 instructions
  • agent: specifies where steps (or all steps) should run
  • stages: contains the different stages (stages) of the pipeline
  • stage: a specific stage with a descriptive name
  • steps: 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 with kubectl 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.0 option 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:

  1. Loss of artifacts: when an agent is destroyed, its data may be lost
  2. Unable to share: an agent cannot access artifacts created by another agent
  3. 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:

  1. Go to Manage Jenkins > Clouds
  2. The Kubernetes cloud is already configured (using the Helm chart)
  3. Under Pod Templates, there is a default template
  4. 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-pvc mounted 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.yaml of 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 demo label
  • 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:

  1. Run demo-2 → The PVC is empty (only an agent/ folder)
  2. Run demo-1 → Download jenkins.war in PVC
  3. Re-run demo-2 → The file jenkins.war is 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 PendingRunning) 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:

StepDescriptionEstimated duration
Unit TestingExecuted manually by the engineer< 1 second
Application buildCompilation, packaging (varies depending on the project)3 minutes to several hours
DeploymentCopying binaries, restarting services5 to 10 minutes
Post-deployment monitoringLogs 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:

  1. Go to plugins.jenkins.io (Jenkins Plugins Index)
  2. Search for the desired plugin (e.g.: “Docker Pipeline”)
  3. Copy exactly:
  • The plugin ID (e.g.: docker-workflow)
  • The version (e.g.: 580.vc0c340686b_54)
  1. 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 upgrade command 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:

  1. Manage Jenkins > Plugins > Installed plugins
  2. Search for “Docker” → no Docker plugins found
  3. Search for “Kubernetes Credentials” → the base kubernetes-credentials is present, but not the kubernetes-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.

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 -n option 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 name in additionalExistingSecrets and the keyName must 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 secret method with --from-literal exposes 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:

  1. Create a new pipeline
  2. Set the source as a Git repository
  3. Repository used: fork of azure-voting-app-redis, branch kubernetes-helm
  4. Use previously configured git-token credentials 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 docker label, 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:

  1. Manage Jenkins > Credentials
  2. Observe the type of credential (here: Secret text)
  3. 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 Jenkinsfiles
  • remote: the Git URL of the shared library
  • credentialsId: the credential to authenticate to the repository
  • defaultVersion: 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 branch
  • test: 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 update to retrieve the latest version of the chart. This may also update the Jenkins version itself.

Checking in Jenkins interface

After the upgrade:

  1. Manage Jenkins > System > Search for “pipeline”
  2. The Global Pipeline Libraries section is now visible
  3. The shared-library library is configured with:
  • The default version test (not main)
  • SCM Git configuration with credentials and repository URL
  • The gitBranchDiscovery trait

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

CategoryPermissions
OverallAdminister, Read, SystemRead
CredentialsCreate, Delete, ManageDomains, Update, View
AgentBuild, Configure, Connect, Create, Delete, Disconnect
JobBuild, Cancel, Configure, Create, Delete, Discover, Move, Read, ViewStatus, Workspace
RunDelete, Replay, Update
ViewConfigure, Create, Delete, Read
SCMTag

Note: The Overall/Administer permission is equivalent to full administrator access — use sparingly.

Supported identity types

  • anonymous: any unauthenticated user
  • authenticated: 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/Read permission)
  • 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/View for 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:

  1. Prometheus plugin (ID: prometheus):
  • Expose a /prometheus HTTP endpoint on Jenkins URL
  • Publish metrics in text-based exposition format (Prometheus)
  • Depends on Metrics plugin
  1. 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: 120 seconds by default → changed to 10 seconds 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):

  1. Status > Target health
  2. The Jenkins endpoint should appear with a green (UP) status
  3. You can click on it to see the raw metrics

Configuring Grafana

Step 1: Add Prometheus as Data Source

  1. Grafana menu (icon at top left) > Connections > Data sources
  2. Add new data source > Choose Prometheus
  3. Configure the Prometheus server URL (not the Jenkins endpoint directly!)
  4. Click on Save & test → “Successfully queried Prometheus server”

Step 2: Import a Jenkins Dashboard

  1. In Grafana, click on the + button or go to Dashboards
  2. Choose Import a dashboard
  3. Enter the ID of the Jenkins dashboard found on grafana.com (e.g.: 9964 for “Jenkins: Performance and Health Overview”)
  4. Select the Prometheus data source created in the previous step
  5. Click on Import

Real-time validation

To validate that monitoring works in real time:

  1. In Grafana, configure auto-refresh to 5 seconds and time range to Last 5 minutes
  2. Locate the Executor In-use metric in the dashboard
  3. In Jenkins, launch several jobs simultaneously
  4. Watch the Grafana dashboard update (with a delay of a few seconds due to scraping intervals)

The data collection chain:

  1. Jenkins updates its metrics every 10 seconds (configured in the plugin)
  2. Prometheus scrapes the Jenkins endpoint every 15 seconds
  3. 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

ToolRole
JenkinsCI/CD Orchestrator
KubernetesContainer orchestration platform
HelmKubernetes Package Manager
minikubeLocal Kubernetes cluster for development
DockerContainerization of applications
JCasC (Configuration as Code)Declarative configuration of Jenkins in YAML
PrometheusCollection and storage of metrics
GrafanaMetrics visualization and dashboards
Groovy DSLLanguage 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

  1. All changes go through Git: never direct manual modifications in production
  2. Mandatory Pull Requests: each change (application, infrastructure, Jenkins config) goes through a review process
  3. Rollback = revert commit: rolling back to a previous state is as simple as rolling back to a previous commit
  4. Jenkins does not only manage apps: Jenkins itself is managed via GitOps (Helm chart + JCasC)
  5. Automatic audit trail: Git history is change traceability

Good security practices

  1. Secrets: use Base64 encoded Kubernetes secrets; avoid plain text in configuration files
  2. Option -n with Base64: always use echo -n "value" | base64 to avoid newline characters
  3. RBAC: do not give more permissions than necessary; manage permissions by groups
  4. Jenkins Administrator: Always include explicit permissions for administrator in RBAC configuration
  5. Credentials: never put clear credentials in Jenkinsfiles or in version control
  6. Docker-in-Docker: avoid building containers within containers; delegate to an agent with direct access to the Docker daemon
  7. Separation of responsibilities: use Shared Pipeline Libraries to separate application code from infrastructure code

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 like sealed-secrets for 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

Interested in this course?

Contact us to book it or get a custom training plan for your team.