Objectifs

  • Configurer une instance Jenkins entièrement dockerisée comprenant un Controller (Master) et un Agent (Slave).
  • Router l’ensemble via notre proxy inverse Traefik mis en place précédemment.

Introduction

Avant de passer au cœur de la configuration, introduisons quelques concepts clés.
Les pipelines CI/CD modernes font face à un défi majeur : créer un environnement de build cohérent, propre et reproductible. Les méthodes traditionnelles mènent souvent à des conflits de dépendances et au fameux problème “ça marche sur ma machine”.

Docker apporte la solution idéale en isolant les applications et leurs dépendances dans des conteneurs. En combinant Jenkins, le serveur d’automatisation de référence, avec Docker, nous créons un système CI/CD puissant et flexible.

Cette intégration peut se faire de deux manières principales :

  • Jenkins à l’intérieur d’un conteneur Docker qui possède son propre démon Docker (Docker-in-Docker ou DinD).

  • Jenkins à l’intérieur d’un conteneur Docker qui utilise le démon Docker de l’hôte (Docker-out-of-Docker ou DoD).

Voici une comparaison rapide entre DinD et DoD dans le contexte de Jenkins :

Caractéristique Docker-in-Docker (DinD) Docker-out-of-Docker (DoD)
Fonctionnement Le conteneur Jenkins exécute son propre démon Docker imbriqué. Le conteneur Jenkins utilise le démon Docker de l’hôte.
Sécurité ⚠️ Expertise requise ! Nécessite le drapeau --privileged. Direct. Pas de mode privilégié nécessaire.
Performance 🔻 Moyenne. Surcharge due aux démons multiples ; pas de cache partagé. Excellente. Démon unique et cache d’images partagé.
Isolation Totale. Chaque build est isolé de l’hôte et des autres jobs. 🔻 Limitée. Tous les conteneurs sont “frères” sur le même hôte.

Important !

Cet article couvrira exclusivement l’approche DoD. De plus, pour garder les choses simples, j’utiliserai le même hôte pour le Controller et l’Agent.


Code source

Vous trouverez ici le code source pour une installation Jenkins complète :


Configuration

Prérequis :

  • Docker et Docker Compose installés sur votre machine.
  • Un proxy inverse Traefik dockerisé tournant localement. Si ce n’est pas le cas, veuillez suivre mon article précédent Traefik reverse proxy .

Important !

Les configurations suivantes sont étroitement liées aux paramètres de mon article précédent. Pensez à les ajuster avec vos propres valeurs, en particulier le nom du réseau Docker externe.

Mise en place des conteneurs

  1. Clonez le code source vers votre répertoire de travail.

  2. Définissez les variables d’environnement

    • Créez une copie de .env.example nommée .env.

      Vous pouvez y définir vos propres valeurs, tant qu’elles restent cohérentes avec la suite de la configuration.

  3. Liez votre DOMAIN_NAME au localhost :

    • Ajoutez votre nom de domaine local au fichier hosts : C:\Windows\System32\drivers\etc\hosts (sur Windows) ou /etc/hosts (sur Linux).

      127.0.0.1 ci-cd-ctrl.my-local-domain.com
  4. Lancez le service via Docker Compose :

    • Dans votre terminal, cd vers votre répertoire de travail puis tapez simplement :
      docker compose up

Test du Controller

  • Vérifiez que le conteneur du controller est actif et sain. Récupérez le mot de passe généré par défaut.

    Jenkins container

  • Dans votre navigateur, ouvrez l’adresse configurée dans DOMAIN_NAME (ex: ci-cd-ctrl.my-local-domain.com). Saisissez le mot de passe récupéré à l’étape précédente.

    Jenkins getting started page

  • Choisissez votre méthode préférée pour l’installation des plugins. Notez que vous pourrez gérer les plugins plus tard.

    Jenkins plugins

  • Complétez votre profil administrateur.

    Jenkins admin

  • Configurez l’URL Jenkins avec la valeur de votre DOMAIN_NAME (ex: ci-cd-ctrl.my-local-domain.com).

    Jenkins URL


Quid de l’Agent (Node) ?

Il existe deux méthodes pour lier l’agent au controller via le Launch method. La première demande à l’agent de se connecter au controller, ce qui nécessite l’ouverture et la configuration d’un port entrant sur le controller. Veuillez consulter la documentation jenkins/ssh-agent pour ce cas.

Pour cet article, j’ai délibérément choisi la seconde option ! Celle-ci fonctionne à l’inverse : c’est le controller qui se connecte à l’agent via SSH. Pour y parvenir, nous devons créer ce que l’on appelle dans Jenkins des credentials (identifiants). Consultez la doc Jenkins pour en savoir plus sur la gestion des identifiants .

Parmi les options disponibles, j’ai choisi “SSH Username with private key”. Cela nécessite la création d’une paire de clés SSH à utiliser entre le controller et l’agent.

Maintenant que le contexte est posé, passons à la pratique :

Préparation de la configuration SSH

  1. Connectez-vous à la console du conteneur controller (bash est disponible dans l’image) :

    docker exec -it ci-cd-jenkins-ctrl bash
  2. Générez la clé (j’ai choisi l’algorithme ssh-ed25519) avec la commande suivante. Laissez la “passphrase” vide pour éviter toute complication inutile pour le moment.

    ssh-keygen -t ed25519 -C "jenkins"

    Cela générera la paire de clés (privée/publique). Le répertoire par défaut de sauvegarde devrait être ’/var/jenkins_home/.ssh’.

Ajout des identifiants

  1. Allez sur le tableau de bord du controller.
  2. Cliquez sur Administrer Jenkins > Credentials.
  3. Vous pouvez créer un domaine spécifique ou, pour faire simple, choisir le domaine par défaut Global credentials (unrestricted).
  4. Cliquez sur Add Credentials.
  5. Sélectionnez l’option SSH Username with private key.
  6. Entrez jenkins dans le champ Username.
  7. Pour la Private Key, sélectionnez Enter directly.
    Jenkins credentials
  8. Cliquez sur Add, puis copiez-collez l’intégralité de la clé privée générée précédemment pour l’utilisateur jenkins.

Ajout de l’Agent (Node)

  1. Retournez sur le tableau de bord.
  2. Dans le menu de gauche, cliquez sur le lien Build executor Status.
    Build executor Status
  3. Cliquez sur New Node, donnez-lui un nom, sélectionnez Permanent Agent et validez.
  4. Remplissez les champs requis, notamment :
    • “Launch method” : Launch agent via SSH.
    • “Host” : le nom du conteneur agent ci-cd-jenkins-ssh-agent.
    • “Remote root directory” : /home/jenkins/agent.
    • Sélectionnez les credentials créés précédemment.
    • Pour “Host Key Verification Strategy”, j’ai choisi Manually trusted key Verification Strategy.
      New Node Form

Lancement du conteneur Docker de l’agent

  1. Clonez le code source .
  2. Définissez les variables d’environnement (copie de .env.example vers .env).
    • Configurez notamment la valeur SSH_PUBKEY avec la clé publique générée précédemment depuis l’interface du controller.
    ## env ##
    1PORT_JENKINS_AGENT_SSH=22
    2COMPOSE_PROJECT_NAME=ci-cd-jenkins-ssh-agent
    3DOCKER_GROUP_ID=0
    4SSH_PUBKEY=<votre_clé_publique_ici>
  3. Lancez l’agent :
    docker compose up

Test de l’agent

  1. Allez sur le tableau de bord du controller.
  2. Sélectionnez votre agent.
  3. Cliquez sur le bouton Launch agent et voilà ! Votre agent est connecté.

Pièges courants

Basique

  • Clé publique erronée : Une erreur de frappe de la clé publique empêchera toute connexion. Il est crucial de respecter scrupuleusement le format attendu par le conteneur agent.
    • Solution : Vérifiez que la clé commence bien par son type (ex: ssh-ed25519) et qu’elle ne contient pas de retours à la ligne parasites dans votre fichier .env.
  • Format de la clé privée : Lors de la création des identifiants (Credentials) dans Jenkins, l’ajout d’espaces superflus ou de sauts de ligne incorrects invalidera la clé.
    • Solution : Copiez l’intégralité du bloc, y compris les balises -----BEGIN... et ...END-----, sans modification manuelle du contenu.
  • Configuration du Port SSH : Lors de la déclaration du Node sur le dashboard Jenkins, la connexion échouera si le port ne correspond pas à celui exposé par le conteneur de l’agent.
    • Solution : Assurez-vous que le champ “Port” dans Jenkins correspond exactement à la valeur PORT_JENKINS_AGENT_SSH définie dans votre configuration Docker (par défaut 22).

Avancé

  • Permissions du Socket Docker (Conflit de GID) : Dans une configuration DoD, l’agent doit pouvoir communiquer avec /var/run/docker.sock sur l’hôte. Si l’ID de groupe (GID) du groupe “docker” de l’hôte ne correspond pas au DOCKER_GROUP_ID de votre .env, vous ferez face à des erreurs “Permission Denied”.
    • Solution : Lancez stat -c '%g' /var/run/docker.sock sur votre hôte et mettez à jour votre .env avec cette valeur numérique.
  • Ports SSH non standards : Si vous avez modifié le PORT_JENKINS_AGENT_SSH dans le .env de l’agent pour une valeur autre que 22, Jenkins ne pourra pas se connecter.
    • Solution : Vérifiez que le champ “Port” dans la configuration du Node Jenkins correspond exactement au port SSH exposé par votre conteneur.
  • Conteneurs de Build “Zombies” : Comme les conteneurs de build sont des “frères” sur l’hôte, un crash de l’agent peut laisser des conteneurs orphelins en cours d’exécution.
    • Solution : Auditez périodiquement votre hôte avec docker ps pour vous assurer qu’aucun processus de build ne consomme de ressources inutilement après l’échec d’un pipeline.
  • Connectivité réseau Traefik : Le Controller Jenkins doit pouvoir atteindre le conteneur Agent. S’ils ne sont pas sur le même réseau Docker, la connexion SSH échouera (Time out).
    • Solution : Vérifiez que les deux services sont connectés au même réseau docker externe défini dans vos fichiers Compose.

En résumé

En routant votre Controller et votre Agent Jenkins via Traefik, vous avez dépassé le stade du simple lab pour mettre en place un environnement CI/CD professionnel avec proxy inverse. L’approche DoD garantit la rapidité de vos builds grâce au partage du cache d’images de l’hôte, tandis que la configuration de l’Agent via SSH maintient une architecture découplée et sécurisée.

Note finale:

  • Évolutivité : Vous pouvez désormais passer à l’échelle horizontalement en ajoutant d’autres agents via le même modèle SSH sur différents hôtes.
  • Maintenance : Puisque les conteneurs de build sont des “frères” sur votre hôte, pensez à exécuter un docker image prune occasionnellement pour garder votre environnement propre.

Vous disposez maintenant d’une stack d’automatisation robuste et dockerisée, prête à propulser vos pipelines les plus complexes. Bonne automatisation !