Integration continue
Un article de Agile-Swiss.
Sommaire |
Contexte
Avant de commencer avec l'intégration continue, il faut comprendre l'architecture "idéale" de développement d'une application. Elle se compose de cinq environnements :
- les postes des développeurs (dits "locaux"), avec les différents outils traditionnels (IDE, outils de modèlisation de base de données, éditeurs XML, ...)
- l'environnement de développement. Il est réservé aux développeurs qui en ont tous les droits (administrateurs). On verra que c'est l'environnement cible de l'intégration continue. Il est ainsi toujours à jour avec la dernière version disponible de l'application. De plus son état est souvent plus ou moins stable (redémarrage fréquent des applications, données volatiles insérées par les développeurs dans le cadre de leurs tests, ...)
- l'environnement de test à destination du client (par exemple l'équipe marketing). Ce dernier valide le bon développement de l'application par rapport à ses besoins. Dans le cadre d'un développement itératif, il permet surtout de découvrir à temps les besoins réels du client qui sont trop souvent mal exprimés ou incomplets. Une nouvelle version de l'application est déployée depuis l'environnement de développement dès que l'appli est estimée stable et qu'elle contient suffisement de nouveautés ou corrections de bugs par rapport à la version précédente. Ces déploiements sont tout de même fréquents (toutes les 1 à 2 semaines) et sont généralement effectués manuellement à la demande du chef de projet. On offre alors généralement un fichier changes.txt qui décrit les différentes évolutions et corrections de bugs apportées depuis la version précédente.
- l'environnement de pré-production pour tester la version finale de l'application. Il reproduit à l'identique l'environnement de production (nombre de machines, processeurs, mémoires, versions des applications, ...). Il permet de réaliser les tests de charge et de valider la bonne exécution de l'application lors du passage en production.
- l'environnement de production accessible par les clients.
Hormis les machines locales, ces environnements sont indépendants entre eux et exécutent leurs propres serveurs (conteneur J2EE, base de données, serveur JMS...). Seules les machines locales et l'environnement de développement se partagent quelques applications :
- le serveur SCM (Source Control Management) comme CVS ou Subversion. Il centralise toutes les sources des différents projets et gère les notions liées au versionning (branche, différences entre deux versions d'un même fichier, ...).
- le remote repository dans le cadre de l'utilisation de maven
- les applications lourdes à mettre en place sur les machines locales (bases de données, serveurs JMS, ...)
Les environnements de test, de préproduction et de production ne sont pas gérés par l'équipe de développement et ne rentrent donc pas dans le cadre de cet article.
Objectifs
L'intégration continue est un processus d'automatisation de tâches récurrentes liées à l'environnement de développement. Les plus connues sont :
- construction ("build") de l'application à partir des données contenues dans le SCM. Cela comprend la compilation des sources et la construction des releases (JAR, WAR, EAR, ...)
- déploiement de l'application sur l'environnement de développement (copie des librairies, configuration et redémarrage)
- exécution des tests unitaires et d'intégration
- génération de rapports, par exemple la javadoc ou les rapports maven
Tout l'intérêt de cette automatisation réside dans sa fréquence d'exécution, qui doit être au minimum quotidienne (voir le best practice pour plus de détail). On dispose alors régulièrement d'un environnement mis à jour avec la toute dernière version de l'application, mais surtout de l'état du projet (succès de la compilation, des tests et du déploiement). Et c'est là un des gros avantages de l'intégration continue : le fait que l'on sache immédiatement qu'une tâche a échouée apporte une certaine discipline au développeur. Finis les "ca compile pas chez moi!" (mais chez le voisin oui), "t'as archivé ?", "t'as.... (fait n'importe quoi)" propres au travail collaboratif. Ces problèmes prennent toujours plus de temps à trouver leur origine qu'à les corriger (oubli d'archivage de fichier, suppression d'un fichier encore utilisé quelque part ailleurs, modification de code impactant un autre composant, ...). Avec l'intégration continue on connaît tout de suite la nature et l'origine du problème.
Un autre gros avantage est la réduction du coût de correction des bugs, à condition bien entendu de l'existence de tests... La fréquence élevée d'intégration diminue le délai de détection d'un bug. Le contexte est alors encore d'actualité pour les développeurs et les modifications de code depuis l'insertion du bug sont peu nombreuses et encore connues. En effet quoi de plus coûteux qu'un bug découvert longtemps après son insertion ! (5% des bugs représentent 95% du coût de correction).
On peut encore trouver d'autres avantages à l'intégration continue :
- gain de temps pour ceux qui avaient la longue et laborieuse tâche de déployer en dev
- connaissance plus concrête du chef de projet de l'état d'avancement du développement.
Bien entendu vous aurez compris que l'intégration continue n'a d'intérêt que dans le cadre d'un développement itératif. Et elle peut être mise en place quelque soit la taille du projet, même si il n'y a qu'un développeur...
Fonctionnement
Il faut distinguer les outils de gestion de projet et les outils d'intégration continue. Les premiers permettent de définir les différentes ressources (arborescence des répertoires, dépendances de librairies), de compiler les sources, de construire les releases, d'executer les tests unitaires et d'intégration et de réaliser de nombreuses autres tâches. Les plus connus sont ant, maven et makefile. Les seconds ordonnancent l'exécution de toutes ces tâches. On peut les comparer à des processus de type cron. Mais ce ne sont pas que des 'simples' ordonnanceurs comme le montrent les différentes étapes d'intégration d'un projet :
- détection du besoin d'intégration : soit à intervalles réguliers (toutes les 10 minutes, toutes les nuits...), soit dès la détection d'une modification dans le SCM (ajout/suppression/modification de fichiers). Etant donné que certains SCM ne sont pas atomiques (VSS, CVS), le développeur peut mettre plusieurs minutes avant d'archiver toutes ses modifications. Durant ce temps l'état du projet est instable dans le SCM. Ce cas est géré en temporisant le début de l'intégration. Après chaque commit on laisse par exemple 5 minutes au développeur pour archiver un second fichier, et cela jusqu'à ce qu'aucune modification n'ait eu lieu dans les 5 dernières minutes.
- intégration. Chaque tâche dépend du succès de la précédente. Toutes ne sont pas obligatoires mais on trouve en général les tâches suivantes :
- chargement de la dernière version du projet depuis le SCM
- compilation
- exécution des tests unitaires
- construction des releases
- déploiement sur l'environnement de développement (+ sur le remote repository dans le cadre de maven)
- exécution des tests d'intégration
- génération des rapports (javadoc, rapports maven)
- historisation et publication des résultats. En cas d'échec d'intégration du projet, on peut par exemple envoyer un mail aux développeurs concernés et publier un message d'erreur sur une page web de synthèse. Les moyens disponibles varient selon les outils d'intégration continue.
Best practices
- opter pour une compilation complète ("clean build") plutôt qu'une compilation incrémentale, c'est à dire en supprimant toutes les classes déjà compilées, même si elles n'ont pas été modifiées depuis la dernière intégration. Cela évite pas mal de problèmes, d'autant plus que la performance de l'intégration continue n'est que secondaire.
- fréquence élevée des intégrations, toutes les nuits est le strict minimum. Je conseillerai même toutes les 10 minutes. Le coût de correction d'un problème est proportionnel à son délai de détection, même au niveau de la journée... Simple exemple : il est des fois plus rapide de corriger un bug le jour même plutôt que d'attendre le lendemain matin. Se replonger dans le code n'est jamais instantanné !
- commits fréquents dans le SCM en évitant les commits massifs en fin de journée. Un SCM n'est pas qu'un outil d'archivage. Les données doivent toujours être cohérentes ! Je conseille le commit après chaque évolution mineure (je ne parle pas de l'ajout d'un commentaire !). Archiver une journée de travail en une fois peut vite tourner à la catastrophe (conflit avec d'autres commits, modifications de l'architecture trop importantes, ...)
- être réactif aux échecs d'intégration. Tout l'intérêt est perdu si personne ne se sent concerné. Le but est de corriger les problèmes tant qu'ils sont récents et donc (plus) faciles à résoudre.
Mauvais exemple : le SCM est utilisé comme simple support d'archivage. Le projet n'est pas dans un état stable et ceci est la dernière préoccupation des développeurs.

Bon exemple : les échecs sont immédiatement corrigés, commits fréquents.

- ne pas submerger les développeurs d'informations inutiles, par exemple en envoyant le mail de synthèse seulement en cas d'échec d'intégration et qu'aux personnes concernées (pas forcément à toute l'équipe, mais seulement à ceux ayant modifiés le SCM depuis la dernière intégration).
- écrire des tests et encore des tests...
A venir
Cet article a présenté l'intégration continue dans ses concepts, sans expliquer comment la mettre en oeuvre. Viendront donc bientôt des présentations et des comparatifs des différents outils disponibles - open-source bien entendu - comme cruisecontrol ou luntbuild.

