Rejoignez-Nous sur

Bootstrapping the Coinbase Monorepo – Le blog Coinbase

0*pKR7LQZL6jY3COBq

News

Bootstrapping the Coinbase Monorepo – Le blog Coinbase

Comment Coinbase utilise un monorepo pour fournir aux développeurs des outils, une sécurité et une fiabilité de classe mondiale

Coinbase

Par Luke Demi

Au cours de la dernière année, l'équipe de productivité des développeurs de Coinbase s'est concentrée sur la fourniture d'un monorepo Bazel pour permettre aux ingénieurs de collaborer plus facilement à travers l'entreprise et de positionner l'entreprise sur les outils standard de l'industrie.

Aujourd'hui, notre monorepo Bazel héberge plus de 100 projets et 22% des déploiements quotidiens de Coinbase dans quatre langues différentes (Go, Rubis, Node, Python). Notre objectif à long terme est de migrer tous les projets de l'entreprise vers le monorepo. Notre trajectoire actuelle nous permet d'atteindre cet objectif au début de 2021.

Le but de cet article est de:

  1. Décrivez notre processus de prise de décision pour choisir de suivre le chemin du monorepo
  2. Décrire certains des défis génériques rencontrés par les monorepos
  3. Décrivez certains des défis spécifiques auxquels nous avons été confrontés
  4. Extrapoler sur notre trajectoire

Pourquoi Coinbase a-t-il choisi de mettre une pause sur nos outils de productivité de développeur existants et de chemin controversé de monorepo?

La première raison majeure était de résoudre notre histoire autour de la gestion des dépendances et du partage de code en général. Comme beaucoup d'autres entreprises avec de nombreux services plus petits – nous créons fréquemment des bibliothèques qui sont partagées entre de nombreux (ou tous!) Services de l'entreprise. Au fur et à mesure que nous nous sommes développés, de plus en plus de ces types de bibliothèques (pensez: journalisation, serveur passe-partout, authn / authz) ont été créés et dépendaient des près de milliers de services de notre écosystème.

Ces relations de partage de code peuvent souvent devenir compliquées, les services dépendant d'autres services et les bibliothèques dépendant d'autres bibliothèques. Dans un monde polyrepo, ces relations sont encore compliquées par la nécessité de versionner et de publier des services internes.

Au fur et à mesure que le nombre de relations de service à service entre bibliothèques augmente au sein d'une entreprise, le besoin d'outils sophistiqués augmente pour que les bibliothèques soient à jour et fonctionnent dans toute l'entreprise. L'ajout d'une amélioration à une bibliothèque critique peut prendre des mois pour être déployé dans tous les services d'une organisation. Il existe un écart de responsabilité entre l'équipe qui effectue le changement de bibliothèque et les équipes clientes qui, par la suite, appréhendent leurs versions avec appréhension.

Un monorepo résout ces problèmes de version et de publication en supprimant simplement les concepts entièrement.

Par définition, dans un monorepo, il n'y a jamais qu'une seule version d'une bibliothèque donnée dans la base de code. Toute modification apportée à cette bibliothèque est immédiatement déployée dans l’ensemble de l’entreprise et leur impact est visible par le résultat de la suite de tests du monorepo après la modification.

De plus, il n’est plus nécessaire d’outils sophistiqués pour gérer la publication, l’importation et les bibliothèques de saut de version. Au lieu de cela, comme tout le code cohabite dans un même référentiel, il n'est jamais nécessaire de publier des packages vers un autre emplacement. Si vous souhaitez vous fier à du code écrit par une autre équipe, c'est aussi simple que de l'importer dans votre service. Si vous voulez écrire une nouvelle bibliothèque partagée, c'est aussi simple que d'écrire le code dans / partagé et en fonction de votre service.

À la suite de ce nouveau flux de travail, les modifications apportées aux bibliothèques largement dépendantes déclencheront immédiatement des suites de tests pour tous les services dépendants. Une fois ces tests exécutés, le propriétaire de la bibliothèque peut être sûr que sa modification a été apportée en toute sécurité à tous les services en aval sans tester manuellement ces services par saut de version ou attendre potentiellement des semaines pour que les propriétaires de services se mettent à jour et trouvent des incompatibilités.

La deuxième raison majeure pour aller de l'avant avec le monorepo est notre désir de positionner l'entreprise sur l'outillage standard de l'industrie.

Nos outils existants chez Coinbase s'appuient sur des outils internes exclusifs pour définir la relation entre le code et la configuration. Les développeurs utilisent un Dockerfile pour définir la relation entre le code dans leur référentiel et les images Docker résultantes (à déployer). Ces images Docker sont ensuite mappées vers un fichier de type docker-compose qui mappe la configuration aux images Docker générées. Enfin, un outil appelé Codeflow gère la création et le mappage de la relation entre ces images Docker et leur configuration associée avant d'envoyer une «version» à nos pipelines de déploiement.

Notre défi avec cette stratégie actuelle est que Codeflow a été initialement conçu avec l'hypothèse forte qu'un référentiel GitHub unique équivaut à une seule image Docker équivaut à un seul «déploiement» par compte AWS.

Au fil du temps, nous avons étendu (boulonné) les fonctionnalités Codeflow pour autoriser plusieurs images Docker par référentiel et plusieurs «configurations» par compte AWS. Cependant, le résultat final de ces améliorations boulonnées a été une chaîne rigide, propriétaire et opaque qui relie ces référentiels Github à leurs multiples images Docker résultantes, puis les lie aux multiples configurations «cloud» pour chacun des comptes AWS («environnements ») Dont ils sont déployés.

Même à la fin de tout ce travail, les ingénieurs souhaitaient toujours pouvoir mieux personnaliser la chaîne du code source à la «libération» (images Docker + métadonnées cloud). Chaque nouvelle fonctionnalité Codeflow a conduit à une courbe d'apprentissage encore plus abrupte permettant aux ingénieurs de comprendre ces relations propriétaires.

Ce dont nous avions besoin était un moyen de définir de manière explicite et flexible la relation entre le code et les images et métadonnées déployables résultantes.

Bazel fournit les blocs de construction nécessaires pour donner aux développeurs une personnalisation et une visibilité illimitées sur la façon dont leur code est déployé. Plutôt que de prédéfinir des relations opaques ou rigides entre le code source et une «version», une chaîne typique de «règles» Bazel du code source à une «version» déployable pourrait ressembler à ceci:

Les développeurs ont désormais la possibilité de déployer leur code avec la configuration qu'ils jugent appropriée. Le processus par lequel nous construisons ces relations est désormais flexible et transparent.

Nous investissons dans l'outillage monorepo car il nous permet de partager en toute transparence du code et de déployer plus rapidement les changements de bibliothèque et de sécurité dans toute l'organisation. De plus, il se positionne sur des outils standard de l'industrie qui définissent plus clairement la relation entre le code et ce qui est déployé dans nos environnements cloud.

La migration de tout votre code dans un monorepo conduit à un problème évident et flagrant: la majorité des outils publiés au cours de la dernière décennie ont été conçus avec l'hypothèse qu'un «référentiel git» == «une suite de tests» == «a ensemble discret d'artefacts construits ». Sur la base de cette hypothèse, dans un monde polyrepo, chaque fois qu'un changement est poussé vers un référentiel git, toutes les générations et les tests pour ce référentiel sont exécutés en intégration continue (CI).

Cependant, dans un monorepo, tous les services et bibliothèques d'une organisation vivent dans le même référentiel. Sur la base de l'hypothèse ci-dessus, chaque validation du monorepo nécessiterait de tester chaque test et de construire chaque génération.

C'est là que réside le principal défi de l'exploitation d'un monorepo: il n'est plus logique de tester et de construire l'intégralité du référentiel sur chaque commit.

Au lieu de cela, nous devons compter sur des outils comme Bazel pour organiser tous les services et bibliothèques en un «graphique de dépendance de construction» explicite que nous pouvons exploiter pour construire et tester exactement ce qui est requis à chaque validation.

Même avec des outils comme Bazel pour créer un graphe de dépendance des règles («blocs de construction») dans un référentiel, il existe inévitablement un nombre important de règles / bibliothèques / services dont toutes les autres bibliothèques et services dépendent au sein du monorepo. Ainsi, le simple clonage du référentiel et l'exécution des tests ou la création des sorties d'un seul projet peut encore nécessiter une heure de construction.

Pour contourner ce problème afin de ramener les temps de génération et de test à un niveau raisonnable, les générations doivent être hermétique de sorte que les sorties de construction peuvent être agressivement mis en cache. Dans le contexte d'un monorepo, «hermétique» signifie qu'une génération peut produire des sorties déterministes quels que soient le système ou l'heure de la journée où la génération est exécutée.

Le but d'une sortie de build hermétique déterministe est que la sortie de chaque build puisse être mise en cache et exploitée pour toutes les builds de l'entreprise, elles sont produites dans CI ou sur la machine d'un développeur.

La réalité est que de multiples défis font obstacle à une construction hermétique vraiment reproductible. D'une part, une version hermétique ne peut utiliser aucune partie de la configuration du système hôte, car même un petit changement dans une bibliothèque système ou une version du système d'exploitation sur les hôtes de génération pourrait entraîner une baisse complète du taux d'accès au cache. De plus, chaque dépendance requise doit être épinglée SHA ou avoir son code source inclus dans le monorepo.

Il n’ya pas de fin au nombre de petites influences externes qui peuvent jouer dans la pureté de l’herméticité d’un monorepo. Cependant, atteindre «suffisamment hermétique» là où la majorité des builds produisent les mêmes sorties peut considérablement améliorer le taux d'accès au cache sur les machines de build et réduire considérablement les temps de build.

Après avoir construit une histoire autour de la mise en cache, nous avons adapté nos outils existants pour récupérer et mettre à jour les caches entre les générations afin de réduire les temps de génération.

Même avec un cache, les outils nécessaires pour exploiter un monorepo Bazel sur les outils conçus avec la mentalité «git repository» == «une collection de builds / tests» peuvent être frustrants, surtout lorsque nous essayons de présenter les résultats des tests pour de nombreux projets ou bibliothèques dans l'espace conçu pour des projets uniques.

Chez Coinbase, de nombreux contrôles de sécurité conçus pour protéger les projets sensibles sont basés sur l'hypothèse qu'un référentiel Github unique == un projet unique et appliqués au niveau du référentiel Github. Placer chaque projet (le long de l'outillage de l'infrastructure pour se déployer) dans un référentiel unique nécessiterait alors une sécurité «taille unique» – contrôlant efficacement tout le monde au sein du monorepo au niveau du projet le plus sensible du référentiel.

Chez Coinbase, nous avons besoin consensus (sous la forme de + 1) pour que le code soit fusionné en master et déployé en production. Le contrôleur d'accès pour appliquer ce comportement est Heimdall – un service que nous avons construit pour garder une trace de +1 pour tout SHA de validation Git donné pour un référentiel Github spécifique.

Notre solution pour résoudre le problème de sécurité «taille unique» dans le monorepo était d'adapter notre outil interne Heimdall pour tirer parti d'une fonctionnalité Github appelée CODEOWNERS. Cette fonctionnalité vous permet de définir les individus ou les équipes qui sont tenus de réviser le code via un seul fichier CODEOWNERS à la racine du référentiel avec un format spécial `/ directory-name @ github / team-name`

Dans notre monorepo, nous générons le fichier CODEOWNERS avec un script qui regroupe le contenu des fichiers OWNER qui vivent à la racine de chaque répertoire du référentiel. Chaque fichier OWNERS contient simplement une liste d'équipes séparées par des espaces dont les révisions devraient être requises pour un répertoire donné.

Nous autorisons une syntaxe spéciale à côté des équipes dans les fichiers OWNERS pour indiquer le nombre total de révisions requises par une équipe spécifique afin que nous puissions exiger plusieurs révisions pour certains répertoires. Dans la capture d'écran ci-dessus, vous pouvez voir le {0} indiquant que zéro avis est requis pour une équipe, tandis que {2} nécessiterait deux avis de cette équipe. Les équipes sans syntaxe {} sont supposées nécessiter un examen.

Heimdall analyse toutes ces informations avec une syntaxe spéciale dans le fichier CODEOWNERS pour garantir que le nombre total d'examens requis est respecté avant qu'un déploiement puisse être déployé avec succès. Alors que Github marquera un commit comme révisé avec une seule approbation d'équipe, nous avons des statuts Heimdall GitHub distincts pour garantir que les RP ne sont pas fusionnés accidentellement sans suffisamment de commits des équipes propriétaires.

L'objectif de notre équipe d'infrastructure est de fournir aux développeurs de Coinbase des outils, une sécurité et une fiabilité de classe mondiale.

Bien que nous ayons fait de grands progrès vers la fourniture de la couche de base pour l'avenir de l'outillage de classe mondiale, il nous reste encore des lacunes à combler avant de pouvoir déployer le monorepo dans l'ensemble de l'entreprise.

Actuellement, les développeurs utilisent leurs propres ordinateurs portables pour construire et tester localement. Malheureusement, cela signifie que nous ne sommes pas en mesure de tirer parti des caches partagés que nous peuplons dans nos flottes de génération et de test, ce qui entraîne des temps de construction locaux longs et potentiellement frustrants (et des fans bruyants!). Notre solution consiste à fournir à chaque développeur sa propre machine Linux distante dans EC2 afin qu'il puisse facilement synchroniser ses modifications pour créer et tester rapidement en tirant parti des caches partagés.

Bien que nous ayons ajouté la prise en charge de toutes les principales langues de backend dans Coinbase, notre monorepo (et Bazel lui-même) ne dispose pas encore d'un support solide pour les équipes clientes utilisant React et React Native. Nous travaillerons dur pour ajouter la prise en charge de ces langues frontales pendant le reste de l'année.

Enfin, alors que nos outils de construction et de test actuels sont passés à environ 100 projets, nous nous attendons à ce que le monorepo se développe dans les projets et les lignes de code de plus d'un ordre de grandeur au cours de la prochaine année. Nous pensons que cette croissance nous amènera à rencontrer des limitations de mise à l'échelle dans nos stratégies actuelles de mise en cache, de contrôle de version et de génération.

Dans l'ensemble, c'est l'aperçu de haut niveau de la promesse, des défis et de l'avenir du monorepo Coinbase – attendez-vous à plus d'articles de blog discutant des composants en profondeur de notre monorepo au cours des prochains mois.

Si vous souhaitez résoudre des problèmes techniques complexes comme celui-ci, Coinbase recrute.



Traduction de l’article de Coinbase : Article Original

BlockBlog

Le Meilleur de l'Actualité Blockchain Francophone & Internationale | News, Guides, Avis & Tutoriels pour s'informer et démarrer facilement avec Bitcoin, les Crypto-Monnaies et le Blockchain. En Savoir Plus sur L'Équipe BlockBlog

Commenter cet Article

Commenter cet Article

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Plus dans News

Les Plus Populaires

Acheter des Bitcoin

Acheter des Alt-Coins

Sécuriser vos Cryptos

Vêtements et Produits Dérivés

Top
neque. Sed luctus commodo et, accumsan