Que savons-nous des microservices

Bonjour! Je m'appelle Vadim Madison, je dirige le développement de la plateforme système Avito. Il a été dit plus d'une fois comment nous, dans l'entreprise, passons d'une architecture monolithique à une architecture de microservices. Il est temps de partager comment nous avons transformé notre infrastructure pour tirer le meilleur parti des microservices et éviter de nous y perdre. Comment PaaS nous aide ici, comment nous avons simplifié le déploiement et réduit la création d'un microservice à un clic - continuez à lire. Tout ce que j'écris ci-dessous n'est pas entièrement implémenté dans Avito, cela dépend en partie de la façon dont nous développons notre plate-forme.

(Et à la fin de cet article, je parlerai de la possibilité d'assister à un séminaire de trois jours animé par Chris Richardson, expert en architecture de microservices).

Que savons-nous des microservices

Comment nous sommes arrivés aux microservices

Avito est l'un des plus grands sites de petites annonces au monde ; plus de 15 millions de nouvelles annonces y sont publiées chaque jour. Notre backend accepte plus de 20 XNUMX requêtes par seconde. Nous disposons actuellement de plusieurs centaines de microservices.

Nous construisons une architecture de microservices depuis plusieurs années maintenant. Comment exactement - nos collègues en détail dit dans notre section au RIT++ 2017. Au CodeFest 2017 (voir. vidéo), Sergey Orlov et Mikhail Prokopchuk ont ​​expliqué en détail pourquoi nous avions besoin de la transition vers les microservices et quel rôle Kubernetes a joué ici. Eh bien, nous faisons désormais tout pour minimiser les coûts de mise à l'échelle inhérents à une telle architecture.

Au départ, nous n'avons pas créé un écosystème qui nous aiderait de manière globale à développer et à lancer des microservices. Ils ont simplement rassemblé des solutions open source judicieuses, les ont lancées chez eux et ont invité le développeur à s'en occuper. Du coup, il s'est rendu dans une dizaine de lieux (tableaux de bord, services internes), après quoi il est devenu plus fort dans son envie de découper le code à l'ancienne, en monolithe. La couleur verte dans les diagrammes ci-dessous indique ce que le développeur fait d'une manière ou d'une autre de ses propres mains, et la couleur jaune indique l'automatisation.

Que savons-nous des microservices

Désormais, dans l'utilitaire PaaS CLI, un nouveau service est créé avec une commande, et une nouvelle base de données est ajoutée avec deux autres et déployée sur Stage.

Que savons-nous des microservices

Comment surmonter l'ère de la « fragmentation des microservices »

Avec une architecture monolithique, dans un souci de cohérence des évolutions du produit, les développeurs étaient obligés de comprendre ce qui se passait chez leurs voisins. Lorsque vous travaillez sur la nouvelle architecture, les contextes de service ne dépendent plus les uns des autres.

De plus, pour qu’une architecture de microservices soit efficace, de nombreux processus doivent être mis en place, à savoir :

• journalisation ;
• demander un traçage (Jaeger) ;
• agrégation d'erreurs (Sentry) ;
• statuts, messages, événements de Kubernetes (Event Stream Processing) ;
• limite de course / coupe-circuit (vous pouvez utiliser Hystrix) ;
• contrôle de la connectivité des services (nous utilisons Netramesh) ;
• suivi (Grafana) ;
• assemblage (TeamCity) ;
• communication et notification (Slack, email) ;
• suivi des tâches ; (Jira)
• préparation de la documentation.

Pour garantir que le système ne perd pas son intégrité et reste efficace au fur et à mesure de son évolution, nous avons repensé l'organisation des microservices dans Avito.

Comment nous gérons les microservices

Les éléments suivants aident à mettre en œuvre une « politique de parti » unifiée parmi de nombreux microservices Avito :

  • diviser l'infrastructure en couches ;
  • Concept de plateforme en tant que service (PaaS) ;
  • surveiller tout ce qui se passe avec les microservices.

Les couches d'abstraction de l'infrastructure comprennent trois couches. Allons de haut en bas.

A. Haut - maillage de service. Au début, nous avons essayé Istio, mais il s'est avéré qu'il utilisait trop de ressources, ce qui était trop coûteux pour nos volumes. Par conséquent, l'ingénieur principal de l'équipe d'architecture, Alexander Lukyanchenko, a développé sa propre solution : Netramesh (disponible en Open Source), que nous utilisons actuellement en production et qui consomme plusieurs fois moins de ressources qu'Istio (mais ne fait pas tout ce dont Istio peut se vanter).
B. Moyen - Kubernetes. Nous y déployons et exploitons des microservices.
C. Fond – métal nu. Nous n’utilisons pas de cloud ou d’autres choses comme OpenStack, mais nous nous appuyons entièrement sur du bare metal.

Toutes les couches sont combinées par PaaS. Et cette plateforme, à son tour, se compose de trois parties.

I. Générateurs, contrôlé via un utilitaire CLI. C'est elle qui aide le développeur à créer un microservice de la bonne manière et avec un minimum d'effort.

II. Collecteur consolidé avec contrôle de tous les outils via un tableau de bord commun.

III. Stockage. Se connecte aux planificateurs qui définissent automatiquement des déclencheurs pour des actions importantes. Grâce à un tel système, aucune tâche n’est manquée simplement parce que quelqu’un a oublié de configurer une tâche dans Jira. Nous utilisons pour cela un outil interne appelé Atlas.

Que savons-nous des microservices

La mise en œuvre des microservices dans Avito s'effectue également selon un schéma unique, ce qui simplifie leur contrôle à chaque étape de développement et de sortie.

Comment fonctionne un pipeline de développement de microservices standard ?

En général, la chaîne de création de microservices ressemble à ceci :

CLI-push → Intégration continue → Bake → Déploiement → Tests artificiels → Tests Canary → Tests de compression → Production → Maintenance.

Passons-le exactement dans cet ordre.

CLI-push

• Création d'un microservice.
Nous avons longtemps lutté pour apprendre à chaque développeur comment créer des microservices. Cela comprenait la rédaction d'instructions détaillées dans Confluence. Mais les schémas ont changé et ont été complétés. Le résultat est qu'un goulot d'étranglement est apparu au début du voyage : le lancement des microservices a pris beaucoup plus de temps, et pourtant des problèmes sont souvent survenus lors de leur création.

En fin de compte, nous avons créé un utilitaire CLI simple qui automatise les étapes de base lors de la création d'un microservice. En fait, il remplace le premier git push. Voici ce qu'elle fait exactement.

— Crée un service selon un modèle — étape par étape, en mode « assistant ». Nous avons des modèles pour les principaux langages de programmation dans le backend Avito : PHP, Golang et Python.

- Une commande à la fois déploie un environnement de développement local sur une machine spécifique - Minikube est lancé, les charts Helm sont automatiquement générés et lancés dans kubernetes local.

— Connecte la base de données requise. Le développeur n'a pas besoin de connaître l'adresse IP, le login et le mot de passe pour accéder à la base de données dont il a besoin - que ce soit localement, sur scène ou en production. De plus, la base de données est déployée immédiatement dans une configuration tolérante aux pannes et avec équilibrage.

— Il effectue lui-même l'assemblage en direct. Disons qu'un développeur corrige quelque chose dans un microservice via son IDE. L'utilitaire voit les modifications dans le système de fichiers et, en fonction de celles-ci, reconstruit l'application (pour Golang) et redémarre. Pour PHP, nous transmettons simplement le répertoire à l'intérieur du cube et le live-reload est obtenu « automatiquement ».

— Génère des autotests. Sous forme de flans, mais tout à fait adapté à l'utilisation.

• Déploiement de microservices.

Déployer un microservice était autrefois une corvée pour nous. Les éléments suivants étaient requis :

I. Fichier Docker.

II. Configuration.
III. Tableau de barre, qui est en soi fastidieux et comprend :

— les cartes elles-mêmes ;
— des modèles ;
— des valeurs spécifiques prenant en compte différents environnements.

Nous avons simplifié la refonte des manifestes Kubernetes et les avons désormais générés automatiquement. Mais surtout, ils ont simplifié le déploiement à l'extrême. À partir de maintenant, nous avons un Dockerfile et le développeur écrit l'intégralité de la configuration dans un seul court fichier app.toml.

Que savons-nous des microservices

Oui, et dans app.toml lui-même, il n'y a rien à faire pendant une minute. Nous précisons où et combien de copies du service créer (sur le serveur de développement, en staging, en production), et indiquons ses dépendances. Notez la taille de la ligne = "small" dans le bloc [engine]. C'est la limite qui sera allouée au service via Kubernetes.

Ensuite, en fonction de la configuration, toutes les chartes Helm nécessaires sont automatiquement générées et les connexions aux bases de données sont créées.

• Validation de base. Ces contrôles sont également automatisés.
Besoin de suivre :
— existe-t-il un Dockerfile ;
— y a-t-il app.toml ;
— existe-t-il de la documentation disponible ?
— la dépendance est-elle en règle ?
— si des règles d'alerte ont été définies.
Jusqu'au dernier point : le propriétaire du service détermine lui-même les métriques du produit à surveiller.

• Préparation de la documentation.
Cela reste un domaine problématique. Cela semble être le plus évident, mais en même temps c’est aussi un disque « souvent oublié », et donc un maillon vulnérable de la chaîne.
Il est nécessaire qu'il existe une documentation pour chaque microservice. Il comprend les blocs suivants.

I. Brève description du service. Littéralement quelques phrases sur ce qu'il fait et pourquoi il est nécessaire.

II. Lien vers le schéma d'architecture. Il est important qu'avec un rapide coup d'œil, il soit facile de comprendre, par exemple, si vous utilisez Redis pour la mise en cache ou comme magasin de données principal en mode persistant. Dans Avito, pour l'instant, il s'agit d'un lien vers Confluence.

III. Dossier d'exécution. Un petit guide sur le démarrage du service et les subtilités de sa gestion.

IV. FAQ, où il serait bon d'anticiper les problèmes que vos collègues pourraient rencontrer lorsqu'ils travaillent avec le service.

V. Description des points de terminaison pour l'API. Si du coup vous n'avez pas précisé les destinations, les collègues dont les microservices sont liés aux vôtres paieront presque certainement pour cela. Nous utilisons maintenant Swagger et notre solution appelée brief pour cela.

VI. Étiquettes. Ou des marqueurs qui indiquent à quel produit, fonctionnalité ou division structurelle de l'entreprise appartient le service. Ils vous aident à comprendre rapidement, par exemple, si vous supprimez des fonctionnalités que vos collègues ont déployées pour la même unité commerciale il y a une semaine.

VII. Propriétaire ou propriétaires du service. Dans la plupart des cas, il ou eux peuvent être déterminés automatiquement à l'aide du PaaS, mais par mesure de sécurité, nous demandons au développeur de les spécifier manuellement.

Enfin, c’est une bonne pratique de réviser la documentation, similaire à la révision du code.

Intégration continue

  • Préparation des référentiels.
  • Création d'un pipeline dans TeamCity.
  • Définition des droits.
  • Recherchez les propriétaires de services. Il existe ici un schéma hybride : marquage manuel et automatisation minimale du PaaS. Un système entièrement automatique échoue lorsque les services sont transférés pour assistance à une autre équipe de développement ou, par exemple, si le développeur du service quitte.
  • Enregistrer un service dans Atlas (voir au dessus). Avec tous ses propriétaires et dépendances.
  • Vérification des migrations. Nous vérifions si l'un d'entre eux est potentiellement dangereux. Par exemple, dans l'un d'eux, une table alter ou quelque chose d'autre apparaît qui peut rompre la compatibilité du schéma de données entre les différentes versions du service. Ensuite, la migration n'est pas effectuée, mais placée dans un abonnement - le PaaS doit signaler au propriétaire du service quand il peut l'utiliser en toute sécurité.

Cuire

L’étape suivante consiste à empaqueter les services avant le déploiement.

  • Construction de l'application. Selon les classiques - dans une image Docker.
  • Génération de graphiques Helm pour le service lui-même et les ressources associées. Y compris pour les bases de données et le cache. Ils sont créés automatiquement conformément à la configuration app.toml générée à l'étape CLI-push.
  • Création de tickets pour les administrateurs pour ouvrir des ports (si nécessaire).
  • Exécution de tests unitaires et calcul de la couverture du code. Si la couverture du code est inférieure au seuil spécifié, le service n'ira probablement pas plus loin - jusqu'au déploiement. S'il est à la limite de l'acceptable, alors le service se verra attribuer un coefficient « pessimisant » : puis, s'il n'y a pas d'amélioration de l'indicateur dans le temps, le développeur recevra une notification l'informant qu'il n'y a pas d'avancée en termes de tests ( et il faut faire quelque chose à ce sujet).
  • Prise en compte des limitations de mémoire et de processeur. Nous écrivons principalement des microservices en Golang et les exécutons dans Kubernetes. D'où une subtilité liée à la particularité du langage Golang : par défaut, au démarrage, tous les cœurs de la machine sont utilisés, si vous ne paramétrez pas explicitement la variable GOMAXPROCS, et lorsque plusieurs de ces services sont lancés sur la même machine, ils commencent rivaliser pour les ressources, en interférant les uns avec les autres. Les graphiques ci-dessous montrent comment le temps d'exécution évolue si vous exécutez l'application sans conflit et en mode course aux ressources. (Les sources des graphiques sont ici).

Que savons-nous des microservices

Temps d'exécution, moins c'est mieux. Maximum : 643 ms, minimum : 42 ms. La photo est cliquable.

Que savons-nous des microservices

C’est l’heure de la chirurgie, moins c’est mieux. Maximum : 14091 151 ns, minimum : XNUMX ns. La photo est cliquable.

Au stade de la préparation de l'assemblage, vous pouvez définir cette variable explicitement ou utiliser la bibliothèque procédures automatiques maximales des gars d'Uber.

Déployer

• Vérification des conventions. Avant de commencer à fournir des assemblages de services dans vos environnements prévus, vous devez vérifier les éléments suivants :
-Points de terminaison de l'API.
— Conformité des réponses des points de terminaison de l'API avec le schéma.
— Format du journal.
— Définition des en-têtes pour les requêtes au service (actuellement cela est effectué par netramesh)
— Définition du jeton propriétaire lors de l'envoi de messages au bus d'événements. Ceci est nécessaire pour suivre la connectivité des services à travers le bus. Vous pouvez envoyer au bus à la fois des données idempotentes, ce qui n'augmente pas la connectivité des services (ce qui est une bonne chose), et des données métiers qui renforcent la connectivité des services (ce qui est très mauvais !). Et lorsque cette connectivité devient un problème, comprendre qui écrit et lit le bus permet de bien séparer les services.

Il n'y a pas encore beaucoup de conventions à Avito, mais leur pool s'agrandit. Plus de tels accords sont disponibles sous une forme que l'équipe peut comprendre et comprendre, plus il est facile de maintenir la cohérence entre les microservices.

Tests synthétiques

• Tests en boucle fermée. Pour cela, nous utilisons désormais l'open source Hoverfly.io. Tout d’abord, il enregistre la charge réelle du service, puis – simplement en boucle fermée – il l’émule.

• Tests de résistance. Nous essayons d'amener tous les services à des performances optimales. Et toutes les versions de chaque service doivent être soumises à des tests de charge - de cette façon, nous pouvons comprendre les performances actuelles du service et la différence avec les versions précédentes du même service. Si, après une mise à jour du service, ses performances ont chuté d'une fois et demie, c'est un signal clair pour ses propriétaires : vous devez fouiller dans le code et corriger la situation.
Nous utilisons les données collectées, par exemple, pour mettre en œuvre correctement la mise à l'échelle automatique et, en fin de compte, comprendre généralement à quel point le service est évolutif.

Lors des tests de charge, nous vérifions si la consommation des ressources respecte les limites fixées. Et nous nous concentrons principalement sur les extrêmes.

a) Nous regardons la charge totale.
- Trop petit - il est fort probable que quelque chose ne fonctionne pas du tout si la charge chute soudainement plusieurs fois.
- Trop grand - optimisation requise.

b) Nous regardons le seuil selon RPS.
Nous examinons ici la différence entre la version actuelle et la précédente ainsi que la quantité totale. Par exemple, si un service produit 100 rps, alors soit il est mal écrit, soit c'est sa spécificité, mais en tout cas, c'est une raison pour regarder le service de très près.
Si, au contraire, il y a trop de RPS, alors il y a peut-être une sorte de bug et certains points de terminaison ont cessé d'exécuter la charge utile, et un autre est simplement déclenché return true;

Tests des Canaris

Après avoir réussi les tests synthétiques, nous testons le microservice sur un petit nombre d'utilisateurs. Nous commençons prudemment, avec une infime part de l’audience visée du service – moins de 0,1 %. À ce stade, il est très important que les mesures techniques et produits correctes soient incluses dans la surveillance afin qu'elles révèlent le problème dans le service le plus rapidement possible. La durée minimale d'un test canari est de 5 minutes, la durée principale est de 2 heures. Pour les services complexes, nous réglons l’heure manuellement.
Nous analysons:
— des métriques spécifiques au langage, en particulier les Workers php-fpm ;
— des erreurs dans Sentry ;
— les statuts des réponses ;
— temps de réponse, exact et moyen ;
— la latence ;
— les exceptions, traitées et non traitées ;
— les métriques du produit.

Test de compression

Les tests de compression sont également appelés tests de « compression ». Le nom de la technique a été introduit dans Netflix. Son essence est que nous remplissons d'abord une instance avec du trafic réel jusqu'au point d'échec et fixons ainsi sa limite. Ensuite, nous ajoutons une autre instance et chargeons cette paire - encore une fois au maximum ; on voit leur plafond et leur delta au premier « pressage ». Nous connectons donc une instance à la fois et calculons le modèle de changements.
Les données de test par « compression » sont également acheminées vers une base de données de métriques commune, où nous enrichissons les résultats de charge artificielle avec elles, ou même remplaçons les « synthétiques » par elles.

Production

• Mise à l'échelle. Lorsque nous déployons un service en production, nous surveillons son évolution. D'après notre expérience, surveiller uniquement les indicateurs du processeur est inefficace. La mise à l'échelle automatique avec l'analyse comparative RPS dans sa forme pure fonctionne, mais uniquement pour certains services, tels que le streaming en ligne. Nous examinons donc d'abord les métriques de produits spécifiques à l'application.

En conséquence, lors de la mise à l'échelle, nous analysons :
- Indicateurs CPU et RAM,
— le nombre de demandes dans la file d'attente,
- Temps de réponse,
— prévision basée sur des données historiques accumulées.

Lors de la mise à l'échelle d'un service, il est également important de surveiller ses dépendances afin que nous ne mettions pas à l'échelle le premier service de la chaîne et que ceux auxquels il accède échouent sous la charge. Pour établir une charge acceptable pour l'ensemble du pool de services, nous examinons les données historiques du service dépendant « le plus proche » (basées sur une combinaison d'indicateurs CPU et RAM, couplées à des métriques spécifiques à l'application) et les comparons avec les données historiques. du service d'initialisation, et ainsi de suite tout au long de la « chaîne de dépendances », de haut en bas.

Service

Une fois le microservice mis en service, nous pouvons lui attacher des déclencheurs.

Voici des situations typiques dans lesquelles des déclencheurs se produisent.
— Migrations potentiellement dangereuses détectées.
— Des mises à jour de sécurité ont été publiées.
— Le service lui-même n'a pas été mis à jour depuis longtemps.
— La charge sur le service a sensiblement diminué ou certaines de ses mesures de produits sont en dehors de la plage normale.
— Le service ne répond plus aux exigences de la nouvelle plateforme.

Certains déclencheurs sont responsables de la stabilité du fonctionnement, d'autres - en fonction de la maintenance du système - par exemple, certains services n'ont pas été déployés depuis longtemps et leur image de base a cessé de passer les contrôles de sécurité.

Tableau de bord

En bref, le tableau de bord est le panneau de contrôle de l'ensemble de notre PaaS.

  • Un point d'information unique sur le service, avec des données sur sa couverture de tests, le nombre de ses images, le nombre d'exemplaires de production, les versions, etc.
  • Un outil de filtrage des données par services et labels (marqueurs d'appartenance aux business units, fonctionnalités des produits, etc.)
  • Un outil d'intégration avec les outils d'infrastructure pour le traçage, la journalisation et la surveillance.
  • Une documentation de point de service unique.
  • Un point de vue unique de tous les événements dans tous les services.

Que savons-nous des microservices
Que savons-nous des microservices
Que savons-nous des microservices
Que savons-nous des microservices

En tout

Avant d'introduire le PaaS, un nouveau développeur pouvait passer plusieurs semaines à comprendre tous les outils nécessaires pour lancer un microservice en production : Kubernetes, Helm, nos fonctionnalités internes TeamCity, la mise en place de connexions aux bases de données et aux caches avec tolérance aux pannes, etc. prend quelques heures pour lire le démarrage rapide et créer le service lui-même.

J'ai fait un reportage sur ce sujet pour HighLoad++ 2018, vous pouvez le regarder vidéo и présentation.

Piste bonus pour ceux qui lisent jusqu'au bout

Chez Avito, nous organisons une formation interne de trois jours pour les développeurs de Chris Richardson, expert en architecture de microservices. Nous aimerions donner l'opportunité d'y participer à l'un des lecteurs de cet article. il est Le programme de la formation a été affiché.

La formation aura lieu du 5 au 7 août à Moscou. Ce sont des journées de travail qui seront pleinement occupées. Le déjeuner et la formation auront lieu dans nos bureaux, et le participant sélectionné paiera lui-même ses frais de déplacement et d'hébergement.

Vous pouvez demander à participer dans ce formulaire Google. De votre part - la réponse à la question pourquoi vous devez assister à la formation et des informations sur la manière de vous contacter. Réponse en anglais, car Chris choisira lui-même le participant qui assistera à la formation.
Nous annoncerons le nom du participant à la formation dans une mise à jour de ce post et sur les réseaux sociaux Avito pour les développeurs (AvitoTech en Фейсбуке, Vkontakte, Gazouillement) au plus tard le 19 juillet.

Source: habr.com

Ajouter un commentaire