Intégration continue et “gating” multi-repo à grand échelle

Cet article est un récapitulatif avec nos impressions de la présentation de l’outil d’intégration continue d’OpenStack “Zuul” à la conférence OpenSource Summit 2017 à Prague par Monty Taylor (à ne pas confondre avec le projet Zuul de Netflix)

Contexte – Monty Taylor et l’OpenSource

Pour avoir une meilleure compréhension du sujet, commençons par introduire Monty.

Monty est un membre de RedHat qui a travaillé sur plusieurs gros projets OpenSource, et principalement OpenStack et Ansible. Il dirige une équipe qui travaille sur une architecture et infrastructure permettant d’exécuter de l’intégration continue et des outils de développement pour OpenStack.

Alors que ça paraît raisonnablement simple, l’intérêt du sujet est l’échelle à laquelle l’équipe de Monty opère.

OpenStack représente

  • environ 2 000 dépôts Git
  • 2 000 jobs par heure provenant de 14 régions, 5 clouds publiques et 2 privés
  • 10 000 changements mergés par mois
  • environ 2 000 committers et des centaines d’entreprises de tailles variable
  • des communautés qui dépendent d’OpenStack ou reposent dessus (ex: Ansible)

L’intégration continue (CI) et ses limitations

A son échelle actuelle, OpenStack a dû adopter des règles d’intégrations plus restrictives que des projets plus petits. Deux de celles-ci sont un “processus égalitaire”, qui empêche qui que ce soit de publier directement dans un dépôt, et un besoin de performance pour prendre en charge toutes les requêtes de changements.

Le premier est un point commun de la plupart des projets de plus de cinq committers réguliers. Des outils comme Hudson et Jenkins ont été développés pour supporter ce besoin, et ont été utilisés par le passé par OpenStack. Cependant, les utilisateurs demandaient de plus en plus de fonctionnalités personnalisées et ne pouvaient pas avoir les permissions pour les appliquer eux-même dans Hudson et Jenkins. C’est ainsi qu’un besoin pour un outil pouvant s’adapter à chaque besoin des utilisateurs est apparu.

Le second point peut ne pas sembler aussi évident qu’il n’y paraît. Lorsque des changements sont poussés sur un dépôt, ils passent pas une série de tests configurés dans le framework de CI. Si un nouveau changement est poussé sur le même dépôt ou que le dépôt en question est une dépendance, le framework doit attendre que les tests du changement précédent soient validés avant de lancer les nouveaux tests. Un framework de CI classique peut lancer une seconde vague de tests avant que la première soit terminée et attendre la validation des deux vagues avant de pousser les changements ou faire un rollback si un test a échoué.

Mais tout cela n’est pas suffisant pour un projet tel qu’OpenStack. Avec leur nombre conséquent de dépôts dont la plupart dépendent les uns des autres, leur 2 000+ committers réguliers, et les différents projets externes dépendants d’OpenStack, il y a un besoin de distribution de la charge de travail.

C’est pourquoi Zuul a été créé.

Workflows de développement d’OpenStack

Zuul n’est rien de plus qu’une étape du workflow de développement d’OpenStack, commençons donc par le décrire :

Zuul's developper workflow

Dans son environnement de développement (bleu), un utilisateur pousse les modifications sur un dépôt. Les changements sont interceptés dans le système de code review (orange), Gerrit (natif) ou Github (nouvelle alternative), et envoyés à Zuul pour test (vert). Les résultats de Zuul sont ensuite repoussés dans le système de versionning et rendus disponible à l’utilisateur qui a poussé les modifications à l’origine.

Pour plus d’informations à propos de Gerrit Code Review, voir le site officiel.

Cette architecture permet aux utilisateurs d’avoir accès aux feedbacks à propos des changements de code (explications de patch, résultats des tests, discussion sur le code, etc.) sans être impactés par la complexité de Zuul.

Qu’est-ce que Zuul ?

Comme expliqué dans leurs documentation :

Zuul is a program that drives continuous integration, delivery, and deployment systems with a focus on project gating and interrelated projects.

C’est à dire un programme gérant l’intégration continue, le delivery et le déploiement de système en mettant l’accent sur les projets interconnectés et le “gating”.

Nous avons déjà expliqué pourquoi l’équipe du projet a mis l’accent sur les projets interconnectés. Le “gating” de projet s’explique par “empêcher les changement introduisant des régressions d’être mergés” (source).

Monty nous expliques les trois rôles du “gating” :

  • Gating : Tout changement proposé pour à un dépôt est testé avant d’être mergé
  • Co-gating : Les changements d’un groupe de dépôts sont mergés de manière monolithique afin que chaque changement soit testé avec l’état actuel des autres dépôts liés avant le merge.
  • Parallel Co-gating : Les changements sont sérialisés afin que chaque changement soit testé avec tous les changements plus récents pour que les besoins du “gating” soient remplis tout en étant capable de tester plusieurs changements simultanément.

Zuul a été pensé spécifiquement pour supporter ces trois rôles.

Exemple applicatif

Monty présente une version très simplifiée des méchaniques interne de Zuul avec un exemple présentant quatre requêtes de changement sur deux dépôs. Cela ne peut bien sûr pas être comparé à la complexité des 2 000 dépôt et 10 000 requêtes de changement par mois d’OpenStack, mais c’était suffisant pour comprendre comment Zuul implémente les trois rôles du gating expliqués précédemment.

L’exemple de cet article est fortement inspiré de celui de Monty mais a été simplifié pour s’adapter au format blog.

Nous avons trois requêtes de changement et leurs dépôts respectifs sont ignorés car ils n’entrent pas en compte dans cette explication du workflow de Zuul. Les trois jobs de validation générés par Zuul pour tester chacun des changements sont comme suis :

  • A : le premier changement poussé à Zuul
  • A+B : les changements A pré-mergés avec les changements B, second set de modifications poussé à Zuul
  • A+B+C : les changements A et B pré-mergés avec les changements C, troisième set de changements reçus par Zuul

Avec ce fonctionnement, les deux premier rôles du gating sont pris en charge car chaque changement est testé avec ses dépôts liés. Zuul lance les jobs A+B et A+B+C plutôt que simplement un job pour B puis C afin de supporter le troisième rôle du gating: la parallélisation. Zuul devrait traiter chaque job un par un, alors qu’en pré-mergeant les changements Zuul peut se permettre d’exécuter les tests en parallèle.

Ci-dessous, les trois jobs sont représentés par les zones bleue, et chaque job a 15 tests à passer pour réussir le processus de validation de Zuul et être mergé dans le dépôt cible :

Les trois jobs sont exécutés en parallèle, à différentes vitesses en fonction des tests et de l’infrastructure sur laquelle a lieu l’exécution. Dans notre exemple, le premier job est en avance, cependant ce n’est pas un comportement garanti.

Si un test du job A+B échoue, les autres tâches en cours sont annulées, le job est marqué en échec, et le résultat est transmi à l’utilisateur qui a poussé les changements B dans le système de review. De plus, le job A+B+C est également annulé car les tests de B (inclus dans le job) ont échoués. Toutes les tâches en cours sont arrêtées et annulées également.

 

Le troisième job est regénéré sans les changements B pendant que le job A a validé tous ses checks, ce qui entraîne la validation des changements par Zuul et le merge dans le dépôt cible. Le nouveau job A+C doit recommencer sa série de test, y compris ceux déjà validés précédemment :

 

Tests personnalisés par un utilisateur

L’une des limitations de Hudson et Jenkins évoquée précédemment était leur incapacité à s’adapter aux 2 000+ commiteurs réguliers d’OpenStack avec des scénarios et cas d’usage spécifiques. Zuul utilise un système de configuration basé sur YAML permettant d’exécuter les jobs dans de multiples environnements définis par l’utilisateur, en un seul batch de tests.

Afin de déployer ces environnements définis par les utilisateurs, Zuul utilise un autre outils de l’équipe de développement Infra d’OpenStack: Nodepool. La documentation le défini de la manière suivante :

Nodepool is a service used by the OpenStack CI team to deploy and manage a pool of devstack images on a cloud server for use in OpenStack project testing.

Once per day, for every image type (and provider) configured by nodepool, a new image with cached data is built for use by devstack. Nodepool spins up new instances and tears down old as tests are queued up and completed, always maintaining a consistent number of available instances for tests up to the set limits of the CI infrastructure.

Soit : Nodepool est un service utilisé par les équipes de CI OpenStack pour déployer et gérer un pool d’images devstack sur un serveur cloud afin de les utiliser dans le test des projets OpenStack.
Une fois par jour, chaque image (et provider) configuré par nodepool est regénérée avec les données cachées pour être utilisé par devstack. Nodepool lance de nouvelles instances et supprimes les anciennes une fois que les files de tests sont vidées, en maintenant toujours un nombre consistant d’instances disponibles pour les tests définie par les limites de l’infrastructure CI.

Cela permet par exemple à des utilisateurs d’exécuter les mêmes tests sur un dépôt python avec plusieurs versions du langages dans plusieurs pipelines. Nodepool hébergeant les images, les pipelines Zuul peuvent facilement être créés prou de multiples systèmes (RHEL 6/7, Debian X, …), configurations (Java 6/7/8, Python 2.6/2.7/3.2, …) et environnement définis pas l’utilisateur (logiciels pré-installés, avec/sans dépendances, …).

Dans l’exemple présenté précédemment, chaque tâche de chaque job (représenté par les petits carrés blanc / vert / rouge / gris) sont exécutés sur des instances d’images indépendantes fournies par Nodepool.

Voici à quoi ressemble un fichier de configuration YAML de Zuul pour des tests unitaires dans des environnements python 2.7 et 3.5, avec des dépendances vers d’autres dépôts :

Ces tests unitaires peuvent ensuite être ajoutés de manière modulaire à un template de projet :

Ce template peut ensuite être utilisé pour créer un projet :

Et ce n’est qu’un échantillon de tout ce que Zuul permet de configurer. On peut y ajouter d’autres couches telles que les informoations réeau, les logiciels à installer, les variables d’environnements, les méchanismes de reporting, etc.

Résumé

Voici comment Zuul fonctionne

  • Les jobs sont exécutés sur des noeuds de Nodepool (statiques ou dynamiques)
  • Les métadonnées sont définies dans la configuration de Zuul
  • Le contenu est exécuté sur des noeuds avec Ansible (et streamé en live ici)
  • Puppet peut être utilisé, main la fonctionnalité est encore en cours de développement
  • Les jobs peuvent être définis de manière centralisée ou dans le dépôt en cours de test
  • Les jobs ont des variables contextuelles simplifiant la configuration
  • Les dépôts de job de Zuul peuvent être partagés entre plusieurs instances

Remarques

Dans l’ensemble, la présentation de Monty était très informative.

L’intégration continue est un sujet abordé par toutes les organisations à un moment donné. Certes, la complexité et la diversité des processus d’intégration d’OpenStack n’est pas courante et dans la plupart des cas, des outils tel que Jenkins et Hudson suffiront amplement.

Cepedant, bien que Zuul soit trop complexe pour des usages de tout les jours, les besoins qui ont menés à sa création et sa manière de fonctionner sont quelque chose que nous pouvons utiliser. Les trois rôles du gating présentés en font partie et tout processus d’intégration devrait les prendre en compte. Un autre aspect est les environnements de test définis par l’utilisateur. Le fonctionnement de Nodepool de mise en cache des images de scénarios probables est vraiment intéressant et utiliser un environemment IaaS personnalisés avec d’autres outils OpenSource tel que Docker / Kubernetes est à la portée de toute organisation.

Par |2018-06-05T22:36:49+00:00October 28th, 2017|Open Source Summit Europe 2017|0 commentaire

À propos de l'auteur :

Laisser un commentaire