CI vs CD : Que sont l’Intégration Continue et le Déploiement Continu ?
L’intégration continue (Continuous Integration – CI) et le Déploiement Continu (Continuous Delivery – CD) dans le développement des logiciels embarqués sont des pratiques qui automatisent le processus d’intégration des modifications de code dans un projet ainsi que sa distribution une fois le code modifié. Leur objectif principal est d’assurer une qualité constante du logiciel tout au long du cycle de développement, en intégrant continuellement de nouvelles fonctionnalités dans les livraisons (par opposition à une intégration en fin de cycle de projet) tout en détectant au plus tôt les régressions éventuelles.
Il est primordial pour une société qui développe des systèmes embarqués (IoT, Automobile, Aéronautique…) de pouvoir s’assurer de la qualité du produit délivré. En effet, une fois celui-ci livré et installé chez le client, il est plus contraignant et coûteux d’apporter des modifications logicielles pour corriger d’éventuelles erreurs ou régression. La qualité et l’expérience client sont primordiales et les changements apportés au code devraient majoritairement être de nouvelles fonctionnalités, pas des corrections de bugs.
C’est à cette problématique que l’Intégration Continue répond.
Comment définir un processus d’Intégration Continue efficace ?
Pour mettre en œuvre un processus de CI efficace, il est important :
- D’être exhaustif quant aux tests exécutés sur la cible désirée, à savoir les produits sur lesquels sont développés du code embarqué. De ce fait, l’architecture et les interfaces matérielles sont strictement conformes à celles des produits qui seront distribués. Il est aussi possible d’utiliser des machines virtuelles, qui permettent de tester d’autres aspects du logiciel, non reproductibles par des systèmes temps réel, parfois à moindre coût. Pour être exhaustif, une bonne pratique est de faire passer des tests unitaires : L’objectif principal est de vérifier si chaque unité de code (comme une fonction, une méthode ou une classe) produit les résultats escomptés dans différentes situations. On peut ainsi monitorer chaque unité/bloc constitutif de la carte, par exemple chaque capteur qu’elle intègre.
- De limiter les interactions entre produit/carte testés : il est nécessaire d’évaluer le code sur un matériel configuré tel qu’il serait livré tout en gardant la possibilité de débugger (qui nécessite d’être intrusif).
- De tester toute la chaîne de développement : Ne pas se restreindre uniquement au comportement du produit. Il faut aussi s’assurer que toute la chaîne de développement, de déploiement et le matériel sont fonctionnels. Des vérifications à chaque étape garantissent un livrable robuste, fiable et conforme.
- D’éviter les régressions lors de l’intégration de nouvelles fonctionnalités sur certaines cibles : les nouvelles cibles n’entraînent pas d’effet de bord ou de régression sur les autres cibles supportées dans l’écosystème de développement.
Comment la CI répond aux attentes de qualité de livraison ?
La CI peut répondre à ces attentes de qualité en réalisant, entre autres, les actions suivantes :
- Evaluation de la qualité du code en lui-même grâce à des outils d’analyse statique : style de code, bonnes pratiques d’implémentation, etc.
- Automatisation des compilations : la CI automatise la compilation du code source à chaque modification, garantissant que le code peut être correctement transformé en une version exécutable. La CI peut aussi générer des artefacts (binaires, exécutables, rapports de tests…), pouvant être utilisés par la suite dans d’autres tests.
- Tests automatisés : Des suites de tests automatisés sont exécutées à chaque intégration pour détecter rapidement d’éventuels problèmes. Cela inclut souvent des tests unitaires, d’intégration et des tests système pour s’assurer que les nouvelles modifications n’entraînent pas de régressions. Ils sont passés à chaque étape de l’intégration, et peuvent être configurés selon les besoins. Lors du développement, il est nécessaire d’avoir un retour rapide pour corriger les bugs au plus vite. Il est possible d’en faire passer lors des week-ends pour être plus complet dans les vérifications.
- Automatisation des distributions, le déploiement continu (CD) : il s’agit du processus de gestion et de déploiement automatisé des nouvelles versions d’une application logicielle.
Cela permet ainsi la détection précoce des erreurs, ce qui facilite leur correction rapide . De ce fait, on réduit le risque d’accumulation de bugs ou de bugs persistants et améliore la stabilité globale du logiciel.
L’Intégration Continue encourage une collaboration régulière entre les membres de l’équipe de développement. Les modifications fréquentes nécessitent une communication efficace et une résolution rapide des conflits de code, par la mise en place entre autres de processus de revue pour s’assurer de la qualité du code avant de pousser les changements.
Les difficultés de mise on œuvre d’une Intégration Continue
Cependant, la mise en place d’un tel processus n’est pas sans difficultés. En effet, il faut d’abord choisir les outils adaptés aux besoins auxquels la CI doit répondre. Puis vient la mise en place d’une architecture de test fiable, configurable et la plus exhaustive possible : plus les systèmes/logiciels testés sont complexes et comportent de dépendances, plus cette étape requiert une réflexion et une planification importante. Si cette étape est mal réalisée, il devient bien plus complexe d’apporter des modifications aux suites de test de la CI : on minimise la possibilité d’évolution du processus, réduisant par la même occasion les points clefs de ce processus que sont fiabilité, configurabilité et exhaustivité.
Il est aussi crucial de mettre en évidence les résultats des tests passés par l’Intégration Continue, pour que l’équipe de développement pallie aux bugs identifiés rapidement.
Enfin, le maintien d’une documentation à propos de la CI, de l’architecture et des tests est primordial pour que toutes les personnes qui sont amenées à être intégrées à ce processus de développement puissent se familiariser avec les outils et tests mis en place.
Image : Lien de référence
Les outils utilisés en CI
Comme expliqué auparavant, des outils sont nécessaires pour mettre en place un processus d’Intégration Continue.
Pour le développement du logiciel embarqué, il est primordial d’utiliser un programme de contrôle de version (Version Control Software). On peut citer Git, Mercurial, Preforce, Subversion… Ce type d’outil permet d’effectuer des changements/modifications/ajouts sur le code tout en conservant l’historique et la chronologie de ces derniers. Cela permet aussi aux contributeurs de pouvoir paralléliser leur travail ; plusieurs parties du code peuvent être modifiées en même temps et de façon indépendante.
Git est souvent utilisé avec des logiciels de « DevOps » tels que GitLab, Github, Azure DevOps… Ces derniers permettent le suivi des bugs ou encore la mise en place de processus de revue. Aujourd’hui, Git s’est largement imposé dans l’industrie du développement logiciel et web, et est utilisé par une grande partie des développeurs.
Pour la mise en place de l’environnement et des tests, GitLab CI/CD (faisant partie de la plateforme GitLab) est un bon outil qui permet de mettre en place des « pipelines » d’Intégration et déploiement pour n’importe quel projet. Une « pipeline » est une série d’étapes (nettoyage, mise en place de l’environnement, compilation du code, programmation de la carte testée, tests divers…).
GitLab CI/CD permet aussi d’afficher l’état des pipelines et d’envoyer les notifications voulues (configurables elles aussi) aux développeurs/mainteneurs. On peut aussi citer des outils comme Jenkins, Travis CI, GitHub Actions, Circle CI…
La vérification du code, en dehors du processus de revue sur GitLab, peut être facilitée par l’utilisation d’outils monitorant le formatage du code. Par exemple, en C/C++, clang format permet de s’assurer que le code développé correspond bien aux règles et conventions de programmation pour ces langages. Ainsi le code soumis à revue est plus uniforme et lisible.
En ce qui concerne la création de tests logiciels, pytest est un exemple de framework pouvant permettre l’exécution de tests unitaires, de tests d’intégration, de tests fonctionnels… développés en langage Python. Pytest permet aussi de les configurer facilement pour s’adapter à chaque produit/plateforme auquel le test sera confronté.
Pour toute la mise en place de l’environnement de test, nous pouvons être amenés à utiliser des paquets ou bien des artefacts provenant d’anciens tests ou de dépendances. Pour les stocker, il est nécessaire d’avoir des dépôts dédiés : Nexus est une plateforme qui permet de faire cela.
Comme décrit auparavant, il est nécessaire que les tests et l’environnement dans lequel ils se déroulent soient contenus : le reste de la machine sur laquelle se déroule les tests n’est pas influencée par cet environnement. Pour cela, la plateforme Docker est tout à fait adaptée. Elle permet d’intégrer en un seul conteneur tout l’environnement de développement, avec ses paquets et ses dépendances, affranchi de la machine sur laquelle il tourne. Cela permet la stabilité de l’environnement pour éviter des régressions liées à des dépendances externes qui évoluent et la reproductibilité et de l’environnement, nécessaire pour être en mesure d’investiguer les problèmes remontés dans un contexte maîtrisé. Ces « Dockers » peuvent eux aussi être stockés dans des dépôts comme Nexus.
La problématique d’intégration des capteurs
La validation des capteurs nécessite une intégration physique, et elle peut s’avérer difficile dans un environnement CI/CD. Les serveurs CI s’exécutent généralement dans un environnement virtualisé ou conteneurisé, ce qui rend difficile la connexion directe aux capteurs physiques.
Différents capteurs peuvent utiliser différents protocoles de communication (SPI, SDI-12, Modbus, I²C…) et formats de données. L’adaptation des processus CI/CD, pour gérer cette diversité peut prendre du temps.
Les données des capteurs peuvent être influencées par des facteurs environnementaux autres que ceux qu’ils mesurent (température, humidité, etc.). C’est le cas par exemple des capteurs de CO₂ ou de comptage de personnes. Il peut être difficile de reproduire/contrôler ces conditions dans un environnement CI/CD, et les variations des relevés des capteurs dues aux changements environnementaux peuvent avoir une incidence sur la cohérence des résultats de la validation. C’est pour cela qu’intégrer la CI à un banc de test, dans lequel toutes les cartes sont dans une configuration similaire et proches physiquement permet dans un premier temps d’uniformiser les conditions de test des capteurs.
Certains capteurs nécessitent aussi un étalonnage manuel ou des étapes de configuration, ce qui rend difficile l’automatisation complète du processus de validation. L’intégration de ces étapes manuelles dans un pipeline CI/CD peut nécessiter des outils supplémentaires ou une intervention humaine.
Exemple d’un setup d’Intégration Continue
Dès qu’un développeur modifie le code, il utilise Git pour pousser ses changements sur le dépôt GitLab. La partie CI/CD de GitLab va ensuite vérifier si certaines conditions sont requises, et lancer ou non un « pipeline ». Le cas échéant, les « runners » (qui servent à exécuter les tâches du pipeline sur les machines sur lesquelles ils tournent) vont réaliser dans l’ordre les tâches (« jobs ») énumérées par le fichier de configuration du pipeline.
Ces tâches seront les suivantes :
- Nettoyage de l’environnement de compilation : tous les fichiers résultant de l’exécution précédente du pipeline sont supprimés.
- Reconfiguration de l’environnement, que ce soit par l’installation ou la mise à jour des paquets, des librairies ou des conteneurs Dockers. On s’assure que l’environnement dans lequel les tests se dérouleront est strictement identique d’un pipeline à un autre.
- On vient récupérer le code modifié, que l’on vérifie grâce aux outils de formatage.
- Le code est compilé, et les éventuels exécutables sont passés aux étapes suivantes du pipeline.
- On déploie le code sur les cartes de test (D.U.T. : Device Under Test) ; Ce déploiement peut se faire de nombreuses manières, que ce soit par un protocole de programmation filaire (SWD, JTAG…) ou bien en FOTA (Firmware Over The Air), qui peut se faire par différents types de connectivité sans fil. Par ailleurs, utiliser le FOTA pour programmer les cartes de test permet d’évaluer le bon fonctionnement de ce dernier.
- Ensuite on vient réaliser des tests pour vérifier le comportement du D.U.T. : on peut par exemple récupérer les valeurs renvoyées par les capteurs. On peut aussi si nécessaire envoyer des commandes à la carte pour vérifier sa configuration, son état…
Si l’un de ces « jobs » échoue, le pipeline arrête son exécution (si les « jobs » suivants dépendent de celui qui vient d’échouer). Le développeur est notifié, et peut faire les modifications nécessaires.
Le développeur est aussi notifié si le pipeline réussit. Certaines actions, comme l’intégration du code modifié peuvent être conditionnées par la réussite d’un pipeline associé.
Ci-dessous, un exemple de résultat de plusieurs pipelines (En cours, Echouée, Bloquée, Validée), sous GitLab :
Ce qu’apporte la CI/CD à Wiifor
Grâce aux outils développés en interne ainsi que l’architecture mise en place pour avoir un processus d’Intégration Continue fiable et configurable, le développement du logiciel embarqué sur les cartes des produits est grandement accéléré : intégrer à notre installation CI une nouvelle carte de la plateforme Produitypage© prend moins de 3h (configuration des tests capteur + mise en place sur le banc + installation des dépendances sur la Raspberry + configuration de la pipeline GitLab CI/CD). On gagne à minima 4-5h par rapport à une installation purement manuelle.
Il est techniquement possible de tester en un seul pipeline tous les capteurs du catalogue Wiifor, et ce en moins de 2h. Les fonctionnalités des interfaces/capteurs/connectivités couvertes par les tests de CI sont « bug-free » et ne comportent pas de régression.
La complexité des interactions entre tous les éléments d’un système embarqué nécessite de faire des compromis, notamment au niveau de l’architecture de la CI, en fonction du temps que nous pouvons nous permettre d’y passer selon le gain en retour.
Dans la CI mise en place en interne, nous implémentons les configurations de test correspondants à nos déploiements clients. Cela sécurise nos livrables et permet de s’assurer du bon fonctionnement des mises à jour quand elles sont déployées chez eux.
Au sein de Wiifor, nous utilisons ces outils pour mettre en place un processus d’Intégration Continue lors du développement sur notre plateforme Produitypage©.
La plateforme Produitypage© propose une gamme de produits configurables sur mesure, à partir d’un catalogue de capteurs, de modules pour la connectivité. La versatilité de l’Intégration Continue, dès sa mise en place et son architecture, permet de ne pas modifier drastiquement les séries de test, mais plutôt de configurer en fonction des choix client. L’encapsulation des environnements de test permettent aussi de mettre le produit dans un état proche voire identique à celui dans lequel le client le recevra. La qualité du produit vendu et distribué est ainsi préservée.
Théo Tami Ingénieur systèmes embarqués HW/SW Wiifor