Comment Dark déploie le code en 50 ms

Comment Dark déploie le code en 50 ms

Plus le processus de développement est rapide, plus l'entreprise technologique se développe rapidement.

Malheureusement, les applications modernes travaillent contre nous - nos systèmes doivent être mis à jour en temps réel tout en étant invisibles et sans provoquer de temps d'arrêt ou d'interruptions. Le déploiement dans de tels systèmes devient une tâche complexe qui nécessite des pipelines de livraison continue même pour de petites équipes.

Ces pipelines ont souvent une application limitée, fonctionnent lentement et manquent de fiabilité. Les développeurs doivent d'abord les créer manuellement, puis les gérer, et les entreprises finissent souvent par embaucher des équipes DevOps entières pour cela.

La vitesse de ces pipelines détermine la vitesse de développement. Pour les meilleures équipes, le déploiement prend de 5 à 10 minutes, mais en général, tout se fait beaucoup plus lentement, et un seul déploiement peut prendre plusieurs heures.

Chez Dark, cela prend 50 ms. Cinquante. Millisecondes.. Dark - c'est une solution complète avec un langage de programmation, un éditeur et une infrastructure, spécialement conçue pour la livraison continue, et tous les aspects de Dark, y compris le langage lui-même, sont construits pour un déploiement instantané et sûr.

Pourquoi les pipelines de livraison continue sont-ils si lents ?

Supposons que nous avons une application web Python et que nous avons déjà créé un excellent et moderne pipeline de livraison continue. Pour un développeur qui travaille sur ce projet chaque jour, le déploiement d'un changement mineur ressemblerait à peu près à cela :

Effectuer les modifications.

  • Créer une nouvelle branche dans git.
  • Apporter des modifications avec le commutateur de fonctionnalités.
  • Test unitaire pour vérifier les modifications avec et sans le commutateur de fonctionnalités.

Demande de tirage.

  • Valider les modifications.
  • Envoyer les modifications dans le dépôt distant sur github.
  • Demande de tirage.
  • La compilation CI s'exécute automatiquement en arrière-plan.
  • Revue de code.
  • Quelques autres revues si nécessaire.
  • Fusionner les modifications avec la branche principale de git.

CI s'exécute sur la branche principale.

  • Installer les dépendances frontend via npm.
  • Compiler et optimiser les ressources HTML+CSS+JS.
  • Exécuter des tests unitaires et fonctionnels en frontend.
  • Installer les dépendances Python depuis PyPI.
  • Exécuter des tests unitaires et fonctionnels en backend.
  • Test d'intégration aux deux extrémités.
  • Envoyer les ressources frontend vers le CDN.
  • Construire le conteneur pour le programme Python.
  • Envoi du conteneur au registre
  • Mise à jour du manifeste Kubernetes

Remplacement de l'ancien code par le nouveau

  • Kubernetes lance plusieurs instances du nouveau conteneur
  • Kubernetes attend que les instances soient opérationnelles
  • Kubernetes ajoute les instances au répartiteur de charge HTTP
  • Kubernetes attend que les anciennes instances cessent d'être utilisées
  • Kubernetes arrête les anciennes instances
  • Kubernetes répète ces opérations jusqu'à ce que les nouvelles instances remplacent toutes les anciennes

Activation du nouveau commutateur de fonctionnalités

  • Le nouveau code est activé uniquement pour lui-même afin de s'assurer que tout fonctionne correctement
  • Le nouveau code est activé pour 10% des utilisateurs, les indicateurs opérationnels et commerciaux sont suivis
  • Le nouveau code est activé pour 50% des utilisateurs, les indicateurs opérationnels et commerciaux sont suivis
  • Le nouveau code est activé pour 100% des utilisateurs, les indicateurs opérationnels et commerciaux sont suivis
  • Enfin, vous répétez toute la procédure pour supprimer l'ancien code et le commutateur

Le processus dépend des outils, du langage et de l'utilisation d'architectures orientées services, mais en général, il ressemble à cela. Je n'ai pas mentionné les déploiements avec migration de bases de données, car cela nécessite une planification minutieuse, mais ci-dessous, je vais expliquer comment Dark s'en occupe.

Il y a de nombreux composants ici, et beaucoup d'entre eux peuvent facilement ralentir, tomber en panne, provoquer une concurrence temporaire ou faire exploser un système de travail.

Et puisque ces pipelines sont presque toujours créés pour des cas particuliers, il est difficile de s'y fier. Beaucoup de gens rencontrent des jours où le code ne peut pas être déployé, car il y a des problèmes dans le Dockerfile, un des dizaines de services a échoué ou le spécialiste nécessaire est en congé.

Pire encore, beaucoup de ces étapes ne font rien de utile. Elles étaient nécessaires auparavant, lorsque nous déployions le code immédiatement pour les utilisateurs, mais maintenant nous avons des commutateurs pour le nouveau code, et ces processus sont séparés. Au final, l'étape où le code est déployé (l'ancien étant remplacé par le nouveau) est devenue simplement un risque inutile.

Bien sûr, c'est un pipeline très bien pensé. L'équipe qui l'a créé n'a pas hésité à investir du temps et de l'argent pour un déploiement rapide. En général, les pipelines de déploiement sont beaucoup plus lents et moins fiables.

Mise en œuvre de la livraison continue chez Dark

La livraison continue est si importante pour Dark que nous avons visé dès le départ un temps de moins d'une seconde. Nous avons passé en revue chaque étape du pipeline pour éliminer tout ce qui était superflu, et nous avons perfectionné le reste. Voici comment nous avons supprimé des étapes.

Jessie Frazelle (Jessie Frazelle) a inventé le nouveau mot deployless (sans déploiement) lors de la conférence Future of Software Development à Reykjavik.

Nous avons immédiatement décidé que Dark serait basé sur le concept « deployless » (merci Jessie Frazelle pour le néologisme). Deployless signifie que tout code est déployé instantanément et prêt à être utilisé en production. Bien sûr, nous ne laisserons pas passer du code défectueux ou incomplet (je décrirai les principes de sécurité ci-dessous).

Lors de la démonstration de Dark, on nous a souvent demandé comment nous avions réussi à accélérer le déploiement. Une question étrange. Les gens pensent probablement que nous avons inventé une sorte de super technologie qui compare le code, le compile, l'emballe dans un conteneur, lance une machine virtuelle, démarre le conteneur à froid, et tout ça en 50 ms. Ceci est peu probable. Mais nous avons créé un moteur de déploiement spécial qui n'a pas besoin de tout cela.

Dark exécute des interprètes dans le cloud. Supposons que vous écriviez du code dans une fonction ou un gestionnaire HTTP ou d'événements. Nous envoyons un diff à un arbre syntaxique abstrait (représentation du code que notre éditeur et nos serveurs utilisent en interne) à nos serveurs, puis nous exécutons ce code lorsque des requêtes arrivent. Ainsi, le déploiement ressemble simplement à une modeste entrée dans la base de données — instantané et élémentaire. Le déploiement est si rapide car il inclut le strict minimum.

À l'avenir, nous prévoyons de faire de Dark un compilateur d'infrastructure qui créera et lancera l'infrastructure idéale pour une haute performance et une fiabilité des applications. Le déploiement instantané ne disparaîtra bien sûr pas.

Déploiement sécurisé

Éditeur structuré

Le code dans Dark est écrit dans l'éditeur Dark. L'éditeur structuré ne permet pas d'erreurs de syntaxe. En gros, il n'y a même pas d'analiseur dans Dark. Tant que vous saisissez du texte, nous travaillons directement avec l'arbre syntaxique abstrait (AST), comme Paredit, Sketch-n-Sketch, Tofu, Prune et MPS.

Tout code incomplet dans Dark a une sémantique d'exécution valide, un peu comme des holes typés dans Hazel. Par exemple, si vous modifiez un appel de fonction, nous conservons l'ancienne fonction jusqu'à ce que la nouvelle soit opérationnelle.

Chaque programme dans Dark a son propre sens, donc le code incomplet n'empêche pas le code complet de fonctionner.

Modes d'édition

Vous écrivez du code dans Dark dans deux cas. Premier cas : vous écrivez du nouveau code et êtes le seul utilisateur. Par exemple, il se trouve dans REPL, et d'autres utilisateurs n'y auront jamais accès, ou c'est une nouvelle route HTTP à laquelle vous ne faites référence nulle part. Vous pouvez travailler sans précautions, et c'est exactement comme cela que vous travaillez actuellement dans un environnement de développement.

Deuxième situation : le code est déjà utilisé. Si le code reçoit du trafic (fonctions, gestionnaires d'événements, bases de données, etc.), il faut faire attention. Pour cela, nous bloquons tout code utilisé et exigeons que des outils plus structurés soient utilisés pour l'édition. Je parlerai des outils structuraux ci-dessous : des commutateurs de fonctionnalités pour les gestionnaires HTTP et d'événements, une puissante plateforme de migration pour les bases de données et une nouvelle méthode de gestion des versions pour les fonctions et types.

Commutateurs de fonctionnalités

Une des manières d'éliminer la complexité inutile dans Dark est de résoudre plusieurs problèmes avec une seule solution. Les commutateurs de fonctionnalités exécutent de nombreuses tâches différentes : remplacer l'environnement de développement local, créer des branches git, déployer le code et, bien sûr, le lancement traditionnel lent et contrôlé de nouveau code.

La création et le déploiement d'un commutateur de fonctionnalité se font dans notre éditeur en une seule opération. Il crée un espace vide pour le nouveau code et fournit des contrôles d'accès au code ancien et nouveau, ainsi que des boutons et des commandes pour passer progressivement au nouveau code ou l'exclure.

Les commutateurs de fonctionnalités sont intégrés au langage Dark, et même les commutateurs inachevés accomplissent leur tâche : si la condition du commutateur n'est pas remplie, le code ancien bloqué sera exécuté.

Environnement de développement

Les commutateurs de fonctionnalités remplacent l'environnement de développement local. Aujourd'hui, il est difficile pour les équipes de s'assurer que tout le monde utilise les mêmes versions des outils et des bibliothèques (outils de formatage de code, linters, gestionnaires de paquets, compilateurs, préprocesseurs, outils de test, etc.). Avec Dark, il n'est pas nécessaire d'installer les dépendances localement, de gérer une installation locale de Docker ou de prendre d'autres mesures pour garantir un semblant d'égalité entre l'environnement de développement et la production. Étant donné que cette égalité est de toute façon impossible,nous ne ferons même pas semblant de viser cela.

Au lieu de créer un environnement local cloné, les commutateurs dans Dark créent un nouveau bac à sable en production, remplaçant l'environnement de développement. À l'avenir, nous prévoyons également de créer des bacs à sable pour d'autres parties de l'application (comme des clones instantanés de bases de données), même si cela ne semble pas encore si important.

Branches et déploiements

Il existe actuellement plusieurs façons d'introduire du nouveau code dans les systèmes : branches git, étapes de déploiement et commutateurs de fonctionnalités. Elles résolvent un même problème à différentes étapes du flux de travail : git - aux étapes avant le déploiement, le déploiement - au moment de la transition de l'ancien code vers le nouveau, et les commutateurs de fonctionnalités - pour le déploiement contrôlé de nouveau code.

Le moyen le plus efficace est d'utiliser des commutateurs de fonctionnalités (qui sont en même temps les plus simples à comprendre et à utiliser). Cela permet de se passer complètement des deux autres méthodes. En particulier, il est utile de supprimer le déploiement - si nous utilisons déjà des commutateurs de fonctionnalités pour activer le code, alors l'étape de transition des serveurs vers le nouveau code ne fait qu'ajouter des risques inutiles.

Git est difficile à utiliser, surtout pour les débutants, et cela le limite énormément, mais il a l'avantage d'avoir des branches pratiques. Nous avons atténué de nombreux inconvénients de git. Dark permet l'édition en temps réel et offre des possibilités de collaboration à la Google Docs, ce qui évite d'avoir à envoyer du code et rend plus rare le besoin de rebaser et de fusionner.

Les commutateurs de fonctionnalités sont essentiels pour un déploiement sécurisé. Couplés aux déploiements instantanés, ils permettent de tester rapidement des concepts en petits morceaux à faible risque, plutôt que d’appliquer un seul gros changement qui pourrait faire planter le système.

Versioning.

Pour modifier les fonctions et les types, nous utilisons la versioning. Si vous souhaitez changer une fonction, Dark crée une nouvelle version de cette fonction. Vous pouvez ensuite appeler cette version avec un commutateur dans le gestionnaire HTTP ou d'événements. (Si cette fonction est profondément ancrée dans le graphe d'appels, une nouvelle version de chaque fonction sera créée en cours de route. Cela peut sembler excessif, mais les fonctions ne posent pas de problème si vous ne les utilisez pas, donc vous ne le remarquerez même pas.)

Pour les mêmes raisons, nous faisons du versioning pour les types. Nous avons déjà parlé en détail de notre système de types. dans le post précédent.

Grâce au versioning des fonctions et des types, vous pouvez modifier l'application progressivement. Il est possible de s'assurer que chaque gestionnaire fonctionne avec la nouvelle version sans avoir à apporter tous les changements à l'application en même temps (mais nous avons des outils pour le faire rapidement si vous le souhaitez).

C'est beaucoup plus sûr que de déployer tout d'un coup, comme cela se fait actuellement.

Nouvelles versions des paquets et bibliothèque standard

Lorsque vous mettez à jour un paquet dans Dark, nous ne remplaçons pas immédiatement chaque utilisation de fonction ou de type dans toute la base de code. Ce serait dangereux. Le code continue d'utiliser la même version qu'il utilisait, et vous mettez à jour les utilisations des fonctions et des types vers la nouvelle version pour chaque cas individuel à l'aide des commutateurs.

Comment Dark déploie le code en 50 ms
Capture d'écran d'une partie du processus automatique dans Dark, montrant deux versions de la fonction Dict::get. Dict::get_v0 retournait le type Any (que nous abandonnons), tandis que Dict::get_v1 retourne le type Option.

Nous fournissons souvent une nouvelle fonction dans la bibliothèque standard et excluons les anciennes versions. Les utilisateurs avec d'anciennes versions dans leur code pourront y accéder, mais les nouveaux utilisateurs ne pourront pas les obtenir. Nous prévoyons de fournir des outils pour transférer les utilisateurs des anciennes versions vers les nouvelles en une seule étape, encore une fois avec des commutateurs de fonctionnalités.

Dark offre également une opportunité unique : étant donné que nous exécutons votre code de travail, nous pouvons tester nous-mêmes les nouvelles versions, en comparant les résultats pour les nouvelles et anciennes requêtes afin de vous informer des changements. Au final, la mise à jour des paquets, qui est souvent effectuée à l'aveugle (ou nécessite des tests approfondis pour des raisons de sécurité), représente beaucoup moins de risques et peut se faire automatiquement.

Nouvelles versions de Dark

La transition de Python 2 à Python 3 s'est étalée sur une décennie et reste un problème aujourd'hui. Étant donné que nous développons Dark pour une livraison continue, ces changements de langage doivent être pris en compte.

Lorsque nous apportons de petites modifications au langage, nous créons une nouvelle version de Dark. L'ancien code reste dans l'ancienne version de Dark, tandis que le nouveau code est utilisé dans la nouvelle version. Pour passer à la nouvelle version de Dark, vous pouvez utiliser des bascules ou des versions de fonctionnalités.

C'est particulièrement utile, étant donné que Dark est apparu tout récemment. De nombreux changements dans la langue ou la bibliothèque peuvent échouer. Le versionnage progressif du langage nous permet de procéder à des mises à jour mineures, ce qui signifie que nous pouvons prendre notre temps et différer de nombreuses décisions concernant le langage tant que nous n'avons pas plus d'utilisateurs, et donc plus d'informations.

Migrations de bases de données

Pour une migration sécurisée de la base de données, il existe une formule standard:

  • Réécrire le code pour prendre en charge les nouveaux et anciens formats
  • Convertir toutes les données dans le nouveau format
  • Supprimer l'ancien accès aux données

En fin de compte, la migration de la base de données s'éternise et exige beaucoup de ressources. Et nous avons des schémas obsolètes qui s'accumulent, car même des tâches simples, comme corriger le nom d'une table ou d'une colonne, ne valent pas l'effort engagé.

Dark dispose d'une plateforme de migration de bases de données efficace qui (nous l'espérons) simplifiera tellement le processus que vous cesserez de le craindre. Tous les stockages de données dans Dark (les magasins de paires « clé-valeur » ou les tables de hachage persistantes) ont un type. Pour transférer un stockage de données, il vous suffit de lui attribuer un nouveau type et une fonction de retour en arrière et de retour vers l'avant pour convertir les valeurs entre les deux types.

L'accès aux stockages de données dans Dark se fait par des noms de variables versionnés. Par exemple, le stockage de données Users sera initialement nommé Users-v0. Lorsqu'une nouvelle version avec un type différent est créée, le nom change en Users-v1. Si des données sont sauvegardées via Users-v0, et que vous y accédez via Users-v1, une fonction de restauration est appliquée. Si des données sont sauvegardées via Users-v1, et que vous y accédez via Users-v0, une fonction de rollback est appliquée.

Comment Dark déploie le code en 50 ms
L'écran de migration de base de données affiche les noms de champs de l'ancienne base de données, les expressions de restauration et de rollback, ainsi que des instructions pour activer la migration.

Utilisez des commutateurs de fonctionnalités pour rediriger les appels de Users-v0 vers la version Users-v1. Cela peut se faire un gestionnaire HTTP à la fois, afin de réduire les risques, et ces commutateurs fonctionnent pour des utilisateurs individuels, vous permettant de vérifier que tout fonctionne comme prévu. Une fois que les utilisateurs de Users-v0 ne seront plus, Dark convertira toutes les données restantes en arrière-plan de l'ancien format vers le nouveau. Vous ne remarquerez même pas.

Test

Dark est un langage de programmation fonctionnel avec typage statique et des valeurs immuables, ce qui réduit considérablement la surface de test par rapport aux langages orientés objet avec typage dynamique. Mais il faut quand même tester.
Dans Dark, l'éditeur exécute automatiquement des tests unitaires en arrière-plan pour le code modifiable, et exécute par défaut ces tests pour tous les commutateurs de fonctionnalités. À l'avenir, nous souhaitons utiliser des types statiques pour exécuter automatiquement le fuzzing du code afin de repérer les bugs.

De plus, Dark exécute votre infrastructure en production, ce qui ouvre de nouvelles possibilités. Nous conservons automatiquement les requêtes HTTP dans l'infrastructure Dark (pour l'instant, nous conservons toutes les requêtes, mais nous souhaitons passer à un échantillonnage plus tard). Nous testons le nouveau code sur celles-ci et réalisons des tests unitaires, et si vous le souhaitez, vous pouvez facilement transformer les requêtes intéressantes en tests unitaires.

Ce dont nous nous sommes débarrassés

Étant donné que nous n'avons pas de déploiement, mais des commutateurs de fonctionnalités, environ 60 % du pipeline de déploiement est laissé de côté. Nous n'avons pas besoin de branches git ou de pull requests, de construction de ressources backend et de conteneurs, d'envoi de ressources et de conteneurs dans des registres ou d'étapes de déploiement dans Kubernetes.

Comment Dark déploie le code en 50 ms
Comparaison entre un pipeline de livraison continue standard (à gauche) et la livraison continue de Dark (à droite). Dans Dark, la livraison se compose de 6 étapes et d'un cycle, tandis que la version traditionnelle comprend 35 étapes et 3 cycles.

Dans Dark, il n'y a que 6 étapes et 1 cycle dans le déploiement (étapes qui se répètent plusieurs fois), tandis que le pipeline moderne de livraison continue comprend 35 étapes et 3 cycles. Dans Dark, les tests se lancent automatiquement, et vous ne le voyez même pas ; les dépendances s'installent automatiquement ; tout ce qui est lié à git ou Github n'est plus nécessaire ; la construction, les tests et l'envoi des conteneurs Docker ne sont plus requis ; le déploiement sur Kubernetes n'est plus nécessaire.

Même les étapes restantes dans Dark sont devenues plus simples. Comme les fonctionnalités peuvent être gérées par une seule action, il n'est plus nécessaire de passer à nouveau par tout le pipeline de déploiement pour enlever le vieux code.

Nous avons simplifié la livraison de code autant que possible, réduisant le temps et les risques associés à la livraison continue. De plus, nous avons considérablement simplifié la mise à jour des paquets, les migrations de bases de données, les tests, la gestion des versions, l'installation des dépendances, l'égalité entre l'environnement de développement et de production et des mises à jour rapides et sécurisées des versions de langage.

Je réponds aux questions à ce sujet sur HackerNews.

Pour en savoir plus sur l'appareil Dark, lisez l'article sur Dark, suivez-nous sur Twitter (ou sur moi) ou inscrivez-vous à la version bêta et recevez des notifications sur les prochains articles. Si vous assistez à StrangeLoop en septembre, venez nous voir pour le lancement.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster