Aller au contenu principal
JenkinsCI/CDDevOps

Jenkins #6 — Jenkins sur Kubernetes : Helm, Pod Templates et Scaling

30 min de lecture Jenkins — Chapitre 6

Déploie Jenkins sur Kubernetes avec Helm, configure des pod templates, des agents dynamiques et maîtrise le scaling pour les workloads CI/CD.

Jenkins sur VM, c’est des agents statiques qui tournent 24/7, du scaling manuel, et des machines qui dorment 80% du temps. Jenkins sur Kubernetes, c’est un pod par build, créé à la demande, détruit après. Zéro gaspillage, scaling automatique, isolation totale.

Ce cours couvre le triptyque de l’industrialisation : déploiement Helm, pod templates multi-conteneurs, et scaling dynamique des agents.

Déploiement avec Helm

Helm simplifie le déploiement de Jenkins à un fichier values.yaml et trois commandes. Le chart officiel gère le controller, les plugins, l’ingress, le storage et le monitoring.

# Installer Jenkins sur K8s
helm repo add jenkins https://charts.jenkins.io && helm repo update
kubectl create namespace jenkins

kubectl create secret generic jenkins-admin \
    --namespace jenkins \
    --from-literal=jenkins-admin-user=admin \
    --from-literal=jenkins-admin-password='S3cureP@ss!'

helm install jenkins jenkins/jenkins \
    --namespace jenkins --values jenkins-values.yaml --wait

Le fichier jenkins-values.yaml est le cœur de ta config. Les sections critiques — controller sans executor, plugins pré-installés, persistence sur un volume rapide :

controller:
  image: { repository: jenkins/jenkins, tag: "lts-jdk17" }
  resources:
    requests: { cpu: "500m", memory: "2Gi" }
    limits: { cpu: "2000m", memory: "4Gi" }
  numExecutors: 0  # Jamais de builds sur le controller !
  installPlugins:
    - kubernetes:latest
    - workflow-aggregator:latest
    - git:latest
    - configuration-as-code:latest
    - docker-workflow:latest
    - credentials-binding:latest
  ingress:
    enabled: true
    ingressClassName: nginx
    hostName: jenkins.example.com
    annotations:
      cert-manager.io/cluster-issuer: letsencrypt-prod
  persistence:
    enabled: true
    storageClass: "gp3"
    size: "50Gi"

⚠️ numExecutors: 0 est crucial. Le controller orchestre, les pods agents exécutent. Un build lourd sur le controller peut crasher toute l’instance — tous les jobs en file d’attente sont perdus.

💡 JCasC intégré : le chart supporte Configuration as Code via controller.JCasC.configScripts. Ta config Jenkins entière — clouds, credentials, global tools — est versionnée dans Git avec le chart Helm.

Pod Templates multi-conteneurs

Un pod template définit la structure du pod créé pour chaque build. Chaque conteneur a un rôle spécifique : JNLP pour la connexion au controller, Node pour le build, Docker pour les images, kubectl pour le déploiement.

pipeline {
    agent {
        kubernetes {
            yaml '''
            apiVersion: v1
            kind: Pod
            spec:
              serviceAccountName: jenkins-agent
              containers:
              - name: node
                image: node:20-alpine
                command: ['sleep', 'infinity']
                resources:
                  requests: { memory: "1Gi", cpu: "500m" }
                volumeMounts:
                - { name: npm-cache, mountPath: /root/.npm }
              - name: kubectl
                image: bitnami/kubectl:1.28
                command: ['sleep', 'infinity']
              volumes:
              - name: npm-cache
                persistentVolumeClaim:
                  claimName: jenkins-npm-cache
            '''
            defaultContainer 'node'
        }
    }
    stages {
        stage('Build') {
            steps { sh 'npm ci && npm run build && npm test' }
        }
        stage('Deploy') {
            steps {
                container('kubectl') {
                    sh 'kubectl set image deploy/app app=reg/app:${BUILD_NUMBER} -n prod'
                }
            }
        }
    }
}

Shared Library — factoriser les templates

Pour éviter de copier le YAML dans chaque pipeline, encapsule les templates dans une shared library. L’équipe n’a plus besoin de comprendre Kubernetes — elle appelle une fonction et c’est parti :

// vars/k8sAgent.groovy — dans ta shared library
def call(Map config = [:], Closure body) {
    def containers = [
        containerTemplate(name: 'jnlp', image: 'jenkins/inbound-agent:latest-jdk17')
    ]
    if (config.node) containers << containerTemplate(
        name: 'node', image: "node:${config.nodeVersion ?: '20'}-alpine",
        command: 'sleep', args: 'infinity')
    if (config.kubectl) containers << containerTemplate(
        name: 'kubectl', image: 'bitnami/kubectl:1.28',
        command: 'sleep', args: 'infinity')

    podTemplate(containers: containers, serviceAccount: 'jenkins-agent') {
        node(POD_LABEL) { body() }
    }
}

// Jenkinsfile — simple et propre
@Library('ci-lib') _
k8sAgent(node: true, kubectl: true) {
    stage('Build') { container('node') { sh 'npm ci && npm run build' } }
    stage('Deploy') { container('kubectl') { sh 'kubectl apply -f k8s/' } }
}

🔥 Ce pattern est un game-changer. La complexité K8s est cachée dans la library. Les développeurs écrivent des pipelines de 5 lignes au lieu de 50 lignes de YAML. Et si tu changes de version d’image ou de stratégie de cache, tu modifies un seul fichier pour 200+ pipelines.

Scaling et autoscaling

Le cycle de vie d’un agent K8s est simple : build déclenché → pod créé → JNLP se connecte → steps exécutés → pod détruit. Le scaling se configure à deux niveaux.

Niveau Jenkins

Le plugin Kubernetes expose des paramètres critiques dans le JCasC :

jenkins:
  clouds:
    - kubernetes:
        name: "kubernetes"
        containerCap: 50          # Max 50 pods agents simultanés
        retentionTimeout: 5       # Garde le pod 5 min après le build
        waitForPodSec: 600        # Timeout de démarrage du pod
        podRetention: "onFailure" # Conserve les pods en échec (debug)

Niveau cluster

Quand Jenkins lance 30 pods d’un coup, le cluster doit suivre. Deux options principales : le Cluster Autoscaler classique (AWS/GCP/Azure) qui scale les node groups, et Karpenter (AWS) qui provisionne des instances spot automatiquement — plus rapide et moins cher.

🎯 Configuration recommandée : containerCap: 50 pour limiter la facture, podRetention: onFailure pour debugger les builds cassés, instances spot pour diviser le coût par 3.

💡 Cache de dépendances : utilise un PersistentVolumeClaim partagé (ReadWriteMany via EFS ou NFS) pour le cache npm/Maven. Sans ça, chaque pod re-télécharge les dépendances — c’est 5 minutes perdues par build sur un projet lourd.

RBAC et sécurité

Jenkins a besoin de permissions Kubernetes précises. Applique le principe du moindre privilège avec des ServiceAccounts dédiés et des Roles scoped au namespace.

apiVersion: v1
kind: ServiceAccount
metadata: { name: jenkins-agent, namespace: jenkins }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: jenkins-agent-role, namespace: jenkins }
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "pods/exec"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: jenkins-agent-binding, namespace: jenkins }
subjects:
  - { kind: ServiceAccount, name: jenkins-agent, namespace: jenkins }
roleRef:
  kind: Role
  name: jenkins-agent-role
  apiGroup: rbac.authorization.k8s.io

⚠️ Erreur fatale : donner un ClusterRoleBinding avec cluster-admin à Jenkins. Ça marche, mais un Jenkinsfile malveillant pourrait supprimer n’importe quelle ressource du cluster. Utilise un Role par namespace, jamais un ClusterRole global.

⚠️ Network Policies : isole le namespace Jenkins. Les pods agents ne doivent communiquer qu’avec le controller Jenkins, le registry Docker et les API endpoints nécessaires. Tout le reste doit être bloqué par défaut.

Pièges courants et monitoring

Les erreurs classiques lors de la migration Jenkins vers Kubernetes :

⚠️ Pods agents qui timeout — le waitForPodSec par défaut (600s) peut être insuffisant si l’image est lourde ou si le cluster doit scaler un nœud. Monitore le temps de démarrage et ajuste.

⚠️ Mémoire JVM du controller — les OutOfMemoryError sont le crash le plus courant. Configure les limites K8s 20% au-dessus du heap JVM max. Si le heap est à 2G, mets limits.memory: 2.5Gi.

🔥 Monitoring Prometheus : active les métriques avec le plugin prometheus et surveille ces compteurs :

  • jenkins_queue_size_value — builds en attente (si > 10, tu manques d’agents)
  • jenkins_executor_in_use_value — agents occupés vs disponibles
  • vm_memory_heap_usage — mémoire JVM (alerte à 80%)

Le dashboard Grafana #9964 affiche tout ça. Si la queue grossit pendant plus de 5 minutes, c’est que ton autoscaler ne suit pas ou que containerCap est trop bas.

Résumé

Jenkins sur Kubernetes se résume à trois piliers :

Helm pour un déploiement reproductible. Values versionnées dans Git, JCasC intégré, plugins pré-installés. Un helm upgrade et c’est déployé en 3 minutes.

Pod Templates pour des agents sur mesure. Multi-conteneurs avec container('name'), shared libraries pour l’abstraction, PVC pour le cache. Les devs écrivent des pipelines simples, la complexité K8s est cachée.

Scaling automatique à deux niveaux. containerCap côté Jenkins pour limiter la charge, Cluster Autoscaler ou Karpenter côté K8s pour provisionner les nœuds. Avec des instances spot, la facture CI/CD baisse de 60-70% par rapport aux VM statiques.

🖥️ Pratique sur ton propre serveur

Pour suivre Jenkins en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.

Obtenir 200$

Articles liés