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

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

Vous appréciez notre travail......nous recrutons !

Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.

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 Open Source Summit 2017 à Prague par Monty Taylor (à ne pas confondre avec le projet Zuul de Netflix)

Contexte - Monty Taylor et l’Open Source

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 Open Source, 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 variables
  • 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égration 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 par 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 :

Developpeur workflow avec Zuul

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 disponibles à 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 changements introduisant des régressions d’être mergé” (source).

Monty nous explique 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 capables 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écaniques internes de Zuul avec un exemple présentant quatre requêtes de changement sur deux dépôts. Cela ne peut bien sûr pas être comparé à la complexité des 2 000 dépôts 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 suit :

  • 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 premiers 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 :

Exemple applicatif de Zuul 1

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.

Exemple applicatif de Zuul 2

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 transmis à 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é. Toutes les tâches en cours sont arrêtées et annulées également.

Exemple applicatif de Zuul 3

Le troisième job est régé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 :

Exemple applicatif de Zuul 4

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 outil de l’équipe de développement Infra d’OpenStack : Nodepool. La documentation le définit 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ée par nodepool est régénérée avec les données cachées pour être utilisée par devstack. Nodepool lance de nouvelles instances et supprime 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éfinis 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 langage dans plusieurs pipelines. Nodepool hébergeant les images, les pipelines Zuul peuvent facilement être créées prou de multiples systèmes (RHEL 6/7, Debian X, …), configurations (Java 6/7/8, Python 2.6/2.7/3.2, …) et environnements définis par 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ée par les petits carrés blanc / vert / rouge / gris) sont exécutées 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 :

- job:
    name: shade-tox-py27-tips
    parent: openstack-tox-py27
    description: Run tox python 27 unittests against master of important libs
    required-projects:
      - openstack-infra/shade
      - openstack/os-client-config

- job:
    name: shade-tox-py35-tips
    parent: openstack-tox-py35
    description: Run tox python 35 unittests against master of important libs
    required-projects:
      - openstack-infra/shade
      - openstack/keystoneauth
      - openstack/os-client-config

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

- project-template:
    name: shade-tox-tips
    check:
      jobs:
        - shade-tox-py27-tips
        - shade-tox-py35-tips
    gate:
      jobs:
        - shade-tox-py27-tips
        - shade-tox-py35-tips

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

- project:
    name: openstack-infra/shade
    templates:
      - publish-to-pypi
      - publish-openstack-sphinx-docs
      - shade-tox-tips

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 informations réseau, les logiciels à installer, les variables d’environnements, les mécanismes 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.

Cependant, bien que Zuul soit trop complexe pour des usages de tous les jours, les besoins qui ont mené à 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 environnement IaaS personnalisé avec d’autres outils Open Source tel que Docker / Kubernetes est à la portée de toute organisation.

Partagez cet article

Canada - Maroc - France

Nous sommes une équipe passionnée par l'Open Source, le Big Data et les technologies associées telles que le Cloud, le Data Engineering, la Data Science le DevOps…

Nous fournissons à nos clients un savoir faire reconnu sur la manière d'utiliser les technologies pour convertir leurs cas d'usage en projets exploités en production, sur la façon de réduire les coûts et d'accélérer les livraisons de nouvelles fonctionnalités.

Si vous appréciez la qualité de nos publications, nous vous invitons à nous contacter en vue de coopérer ensemble.

Support Ukrain