En avril 2016, Jenkins est sorti dans sa version 2. L’objectif de cette version était entre autres de fournir la possibilité de décrire intégralement un job par du code pour ensuite représenter ce job sous forme de pipeline, et ce par l’intermédiaire du plugin Pipeline.

Chez LesFurets.com, nous avons commencé à utiliser nos premiers pipelines Jenkins 2 dès juin 2016. Petit à petit nous avons migré l’essentiel de nos jobs en pipelines.

Nous vous présenterons ici ce qu’apporte Jenkins 2 et ses pipelines. Deux autres articles sur Jenkins sont en préparation pour ce blog. Nous y aborderons l’utilisation qui est faite des pipelines Jenkins chez LesFurets.com, mais aussi notre manière de tester les pipelines Jenkins.

Qu’est ce qu’un pipeline ?

Un pipeline Jenkins, c’est un job qui va être décomposé sous forme :

  • d’étapes qui se succèdent (stage)
  • d’étapes s’exécutant en parallèle (parallel)

Les pipelines donnent également la possibilité de choisir précisément quels agents pourront exécuter telle partie de code.

Voilà pour illustrer à quoi ressemble le pipeline de notre déploiement en pre-production, dans la nouvelle interface graphique Blue Ocean :

Ces pipelines sont donc décrits par du code, dans un langage qui est un DSL (Domain Specific Language) de Groovy. Le choix de ce langage s’explique par le fait que c’est le langage historique de Jenkins. Il s’avère pratique pour du scripting et facilement interfaçable avec du Java.

Prenons un exemple simple de pipeline qui fait deux étapes au sein d’un agent Jenkins (son allocation s’effectue par l’instruction node) :

  • un clone d’un dépôt git suivi d’un clean du workspace,
  • une compilation par Gradle et la récupération des résultats des tests.
node() {
    stage('Checkout') {
        checkout(scm)
        sh 'git clean -xdf'
    }
    stage('Build and test') {
        sh './gradlew build'
        junit 'build/test-results/test/*.xml'
    }
}

Pourquoi des pipelines ?

Jadis, sans les pipelines

Il peut sembler simple et rapide de créer la configuration d’un job dans une interface graphique, mais avec le temps, on s’aperçoit des risques que cela entraîne.

Clicodrôme

Via une interface graphique, il est vite possible de changer un paramètre sans s’en rendre compte et de casser le job. Il est également impossible de vérifier que le job fera ce qu’on veut avant de le lancer, autrement dit, ces jobs ne sont pas testables.

Suivi de changements

Par défaut, les changements faits dans l’interface graphique de Jenkins ne sont pas historisés. Les besoins de cette historisation sont nombreux :

  • vouloir revenir à une version antérieure
  • comprendre ce qui a changé entre deux dates
  • savoir qui a fait tel ou tel changement

Il reste toujours possible de stocker tous les fichiers xml qui décrivent les jobs Jenkins dans un dépôt git pour en avoir une historisation. Mais cette manière de fonctionner n’est pas évidente et surveiller les diff de fichiers XML n’est pas aisé.

Réutilisation

Si on a du code commun entre deux jobs, il n’est pas simple de le mutualiser. Tout au plus, on peut mettre ce code dans un script bash qui sera utilisé par les deux jobs, ou développer un plugin Jenkins.

Aujourd’hui, avec les pipelines

Tout est dans le code

Comme les pipelines Jenkins sont déclarés par du code qui est dans un dépôt git, on peut clairement voir ce que l’on modifie, relire avant de pousser sur le dépôt, et le faire valider.

Désormais on peut placer le code d’un job d’intégration continue ou au sein même du projet git de l’application à tester. Cela apporte l’avantage indéniable de faire évoluer l’application en même temps que les jobs qui testent et déploient cette application.

Souplesse des pipelines multi-branches

Les pipelines multi-branches sont des jobs particuliers : Jenkins crée un job par branche de votre dépôt git qui contient un Jenkinsfile. Ce Jenkinsfile est un fichier à la racine du dépôt git. Il décrit l’exécution du pipeline avec la même syntaxe qu’un pipeline normal.

Ces pipelines multi-branches permettent ainsi de créer un job dès qu’une branche est créée. Ce job créé sera déclenché automatiquement à chaque changement sur cette branche. De plus, ce job étant spécifique à une branche bien précise, il disposera de son propre historique d’exécution dans Jenkins. L’historique du job ne sera donc pas pollué par les changements faits sur les autres branches.

On peut donc imaginer un job Continuous Integration qui exécutera les tests de l’application. Plutôt qu’avoir un job qu’on exécutera parfois sur master parfois sur develop et parfois sur d’autres branches, on aura un job par branche. Dès qu’une branche sera créée, si elle contient ce Jenkinsfile, Jenkins créera automatiquement un job pour cette branche et le lancera.

Imaginons maintenant qu’on travaille en feature branching, et qu’on veuille faire évoluer le job d’intégration continue. On travaille tranquillement sur notre branche et on fait passer sa validation. Les autres branches ne sont pas impactées car leur Jenkinsfile n’a pas été modifié. Une fois que la validation de notre branche est passée, on peut créer une pull request pour que ce soit intégré sur le master et que les autres branches bénéficient de ces changements. On peut donc profiter de ce fonctionnement pour modifier les étapes de validation ou pour modifier des scripts de déploiement, avec une sécurité maximale.

Réutilisation de code

Depuis un script Jenkins, il est possible de charger un autre script Jenkins. Pour cela Jenkins fournit une instruction load à laquelle il suffit de passer le nom du fichier à charger comme argument. Cela peut permettre d’accéder à des méthodes utilitaires, à des déclarations de configuration, etc.

Une autre possibilité est de définir une bibliothèque partagée par plusieurs jobs, dans un projet git distinct. Pour utiliser ces bibliothèques partagées, il suffit de :

  • déclarez le depôt git contenant les différentes bibliothèques partagées au sein de votre entreprise (ex : jenkins-shared-libraries)
  • ajouter @Library('jenkins-shared-libraries@master') pour charger les bibliothèques partagées de jenkins-shared-libraries, en utilisant la branche master. Il est possible de spécifier la version de la bibliothèque, pour taper sur une branche spécifique du projet git des bibliothèques.

Conclusion

Malgré un coût d’apprentissage pour prendre en main certaines subtilités des pipelines Jenkins, il s’agît d’une technologie très intéressante qui permet de donner la main aux développeurs pour faire évoluer les jobs d’une équipe IT, de fiabiliser ces jobs par leur historisation dans git. Cela pousse également à une meilleure collaboration entre développeurs et devops au sein de l’entreprise.

Et n’oubliez pas, d’autres articles sont à venir sur les pipelines Jenkins, restez aux aguets !

Liens et références