Skip to main content

Mises à jour continues des dépendances : améliorer les processus en anticipant les difficultés

Écrit par
Headshot of James Bowes

James Bowes

24 mai 2021

0 minutes de lecture

Voici l’histoire d’une démarche qui consiste à anticiper les difficultés, à demander pardon plutôt que la permission et à s’améliorer en continu. Aux débuts de Manifold — bien avant notre arrivée chez Snyk — nous développions une marketplace indépendante de services pour les développeurs (comme des bases de données ou des services d’envoi d’e-mails transactionnels). Notre code avait une structure classique : une application front-end React et un ensemble de microservices Go communiquant avec une base de données. Une structure classique s’accompagnait aussi de problèmes classiques. L’un d’eux était de maintenir nos dépendances à jour. En tant que membre senior de l’équipe, et après avoir suffisamment longtemps travaillé dans l’entreprise pour être à l’origine de bon nombre de nos problèmes, j’ai entrepris de régler nos problèmes de dépendances, avec l’aide de quelques collègues volontaires.

« La mise à jour des dépendances demande beaucoup de travail ! »

Au fil des ans, et quels que soient les langages et les frameworks, les plaintes concernant la mise à jour des dépendances ne changent pas. À moins d’avoir besoin d’une mise à jour pour développer une nouvelle fonctionnalité ou corriger un nouveau bug (et souvent, même dans ces cas-là), le travail est fastidieux et apporte peu de bénéfices. Comment savoir qu’une mise à jour est disponible ? Est-elle utile pour vous, ou va-t-elle empêcher la compilation ou introduire des bugs ? Combien de conflits votre modification va-t-elle créer avec d’autres changements, lorsque vous mettrez à jour les signatures de fonctions dans des dizaines de fichiers ?

Quand le travail est fastidieux et que le bénéfice est faible, il est naturel de le remettre à plus tard, voire de l’ignorer complètement.

« Il nous faudra deux sprints pour mettre à jour notre routeur React. »

À force de remettre les choses à plus tard, vous finirez dans une situation digne d’un conte pour enfants. Vous avez repoussé la mise à jour de vos dépendances tout l’été, et voilà que l’hiver arrive. Vous avez besoin d’une petite mise à jour du package grasshopper-food, mais l’arbre de dépendances qui lui est propre entre tellement en conflit avec votre base de code que vous ne savez pas combien de temps cela prendra.

Chez Manifold, l’exemple le plus frappant concernait la mise à niveau du routeur que nous utilisions avec React. Nous ne maintenions pas à jour les dépendances de notre base de code front-end. Lorsque nous avons voulu passer à une nouvelle version majeure d’une bibliothèque de routage, les petits changements de signature de l’API se sont retrouvés mêlés à d’innombrables autres conflits et incompatibilités entre dépendances. D’autres packages avaient été mis à jour à plusieurs reprises depuis que nous avions commencé à les utiliser et jusqu’à notre première tentative de mise à niveau. Démêler les autres dépendances de la mise à niveau du routeur était laborieux, surtout lorsque nous cherchions des versions intermédiaires plus modestes pour avancer par étapes.

Au départ, la mise à niveau semblait représenter trop de travail, alors nous l’avons encore repoussée. Plus nous attendions sans changer nos habitudes, plus le problème s’aggravait, à mesure que de nouvelles dépendances étaient mises à jour. Et plus nous attendions, plus la mise à jour de la version du routeur allait prendre du temps.

« Anticiper les difficultés. »

La mise à jour des dépendances est pénible, mais c’est un candidat idéal pour « anticiper les difficultés » en mettant à jour les dépendances plus souvent et de manière explicite. Les dépendances évoluent au fil du temps : plus vous les mettez à jour souvent, plus chaque changement est limité, ce qui réduit l’effort global (c’est, en quelque sorte, le principe de l’intégration continue). Des changements plus modestes permettent de mieux cerner le problème : au lieu de dire « la mise à jour des dépendances est difficile », vous pouvez désormais préciser « la mise à jour du package X vers la version Y nécessite des changements dans ces 12 fichiers ». En vous confrontant au travail fastidieux qui reste, vous pourrez le réduire grâce à l’automatisation ou à d’autres moyens.

Chez Manifold, nous avons activé les mises à jour automatisées des dépendances dans tous nos dépôts. Avec tous les outils disponibles (comme Snyk !), cette modification était facile à mettre en place et pouvait être réalisée par une seule personne avec un impact significatif. Nous pouvions effectuer ce changement sans « demander la permission » aux propriétaires des dépôts, mais plutôt en « demandant pardon » : aucun consensus ni décision officielle n’était nécessaire. La personne à l’origine du changement a comparé son coût (un effort limité) à son impact positif potentiel (important) et à la difficulté de revenir en arrière (faible : il suffisait de désactiver l’option).

Cette personne a apporté les changements dans plusieurs de nos dépôts, puis a expliqué aux autres développeurs ce qui avait été fait, pourquoi et quels en seraient les bénéfices. Elle leur a ensuite proposé de les aider à revenir en arrière, ou de le faire eux-mêmes, s’ils ne souhaitaient pas recevoir de mises à jour des dépendances.

Petit à petit, d’autres développeurs ont activé les mises à jour automatisées des dépendances dans le reste de nos dépôts. Les mises à jour ont alors commencé à arriver chaque jour.

« Nous passons trop de temps à examiner ces pull requests automatiques de mise à jour des dépendances. »

Au début, il est étrange de voir se produire chaque jour quelque chose qui était auparavant rare, déstabilisant et inquiétant. Mais tout le monde finit par s’y habituer et par accepter que le problème initial (la mise à jour des dépendances) n’en soit plus un. À ce stade, les avis changent et la prochaine étape la plus pénible de la mise à jour des dépendances devient le nouveau grand problème.

Mettre à jour les dépendances chaque jour, même avec un processus automatisé de création de pull requests, demande encore beaucoup de travail. Chez Manifold, c’est devenu notre nouveau grand problème : les développeurs estimaient passer trop de temps à examiner et à approuver les pull requests de mise à jour des dépendances. Trois comportements se sont alors dessinés :

  • Ignorer les pull requests et les laisser s’accumuler dans la file d’attente des revues.

  • Se plaindre haut et fort de la situation, en demandant soit de désactiver les mises à jour automatisées des dépendances, soit de régler le problème par tous les moyens possibles.

  • Continuer à examiner (rapidement) les pull requests, puis à les approuver et à les fusionner.

Le deuxième comportement (se plaindre haut et fort) a alimenté l’amélioration continue de notre processus de mise à jour des dépendances.

Les personnes qui venaient de mettre en place une amélioration du processus avaient probablement déjà des idées pour la suite, et étaient tout aussi susceptibles de se satisfaire de la situation. Les autres développeurs, moins impliqués dans le changement, étaient mieux placés pour repérer la principale source de frustration du moment. Ceux qui en parlaient au lieu d’ignorer ou d’accepter le problème nous ont aidés à avancer (même si entendre que votre nouvelle amélioration du processus ne suffit pas peut être difficile).

Au fil de notre progression, nous avons veillé à ce que les changements et les améliorations restent progressifs, faciles à annuler et proposés avec humilité. Lorsque les plaintes concernant le volume élevé de pull requests de mise à jour des dépendances se faisaient suffisamment entendre, la personne à l’origine de l’amélioration rappelait à tout le monde qu’il était possible de désactiver les mises à jour automatisées, tout en demandant un peu de temps pour alléger la charge. Les propriétaires de la base de code gardaient toujours le contrôle, mais ils faisaient confiance à l’équipe pour apporter des améliorations : ils avaient déjà vu le résultat !

Au lieu de désactiver les mises à jour automatisées des dépendances ou d’en réduire la fréquence, nous avons mis en place un mécanisme pour les approuver et les fusionner automatiquement, à condition que les tests réussissent.

« Les changements automatiques des dépendances créent un arriéré dans notre pipeline de déploiement continu. »

Après plusieurs cycles d’amélioration, la difficulté que vous rencontrez n’a plus rien à voir avec celle que vous aviez décidé d’anticiper au départ. Vous vous retrouvez face à un « problème de luxe », un problème qui n’existe que parce que vous avez réussi. Ces problèmes peuvent demander plus d’efforts à résoudre, mais ils apportent souvent des bénéfices à un périmètre plus vaste.

Une fois les pull requests de mise à jour des dépendances fusionnées automatiquement, les services de Manifold mis à jour le plus fréquemment ont rencontré le problème suivant. Nous avions déjà un pipeline GitOps qui faisait passer les changements des pull requests à la préproduction, puis en production, en regroupant les changements en préproduction et en déployant le même ensemble en production. Comme tout déploiement en production devait être approuvé par le propriétaire du service et que nous ne modifiions pas l’ensemble de changements appliqués à la préproduction une fois ceux-ci intégrés, les changements des développeurs finissaient bloqués derrière un arriéré de pull requests automatisées de mise à jour des dépendances.

En réalité, les changements automatisés des dépendances n’étaient pas les seuls en cause. C’était le premier signe qu’un volume important de changements allait engorger notre pipeline de déploiement. Cela a incité les développeurs à accélérer leurs temps de compilation et de test, et notre équipe plateforme à réduire le délai nécessaire au système de déploiement pour détecter et appliquer les changements.

Pour les pull requests de mise à jour des dépendances, nous avons intégré à notre pipeline de déploiement une logique permettant d’approuver automatiquement leur mise en production, puis de fusionner et d’appliquer automatiquement les changements approuvés. Cela a aussi permis aux développeurs de se passer d’une étape pour tous les autres changements. En adoptant un processus sans intervention humaine pour certaines catégories de changements, nous avons dû faire pleinement confiance à nos tests : soit en renforçant ceux qui existaient, soit en reconnaissant explicitement qu’ils étaient aussi fiables que n’importe quelle vérification humaine.

L’amélioration continue, ça marche

Au départ, nous voulions éviter que les mises à jour des dépendances deviennent des tâches majeures inscrites au sprint. Nous avons obtenu cela, et bien plus encore : des déploiements plus rapides, un parcours entièrement automatisé des pull requests jusqu’à la production et un accord explicite sur l’efficacité de nos tests.

Nous avons fini par presque oublier les difficultés liées aux mises à jour des dépendances, remplacées par les problèmes plus vastes de notre pipeline de déploiement. Même si ce pipeline était performant, nous n’en étions pas satisfaits — et nous ne l’aurions jamais été : c’est tout le principe de l’amélioration continue !

Publié dans:

Lire la suite

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

illustration hero ai
Blog

L’ouragan de l’IA est là

L’IA accélère à la fois la création de logiciels et les cyberattaques. Les dirigeants doivent sécuriser les agents et le code dès leur conception, appliquer des contrôles à l’exécution et valider les défenses de manière indépendante.

feature insights context
Blog

La prévention est-elle essentiellement un problème résolu ?

La prévention dans le code généré par les agents est résolue sur le plan architectural, mais le défi reste de choisir des contrôles qui protègent la sécurité sans ralentir le développement.