La douleur et la souffrance lors du débogage des microservices dans le développement Web

En informatique, vous voyez rarement une personne qui n'a pas entendu parler des microservices. Il existe de nombreux articles sur Internet et sur des sites spécialisés à ce sujet qui expliquent généralement bien les différences entre le monolithe et, en fait, les microservices. Un développeur Java inexpérimenté, ayant lu des articles de la catégorie «Que sont les microservices pour les applications Web et ce avec quoi ils mangent», est plein de joie et de confiance que maintenant tout sera merveilleux. Après tout, l'objectif principal est de «voir à travers» le monolithe monstrueux (l'artefact final, qui, en règle générale, est un fichier de guerre / oreille), qui effectue un tas de tout, sur un certain nombre de services vivants séparément, chacun remplissant une fonction strictement définie qui ne lui est liée, et le fera bien. En plus de cela vient une évolutivité horizontale - il suffit de faire une mise à l'échellenœuds correspondants, et tout ira bien. Plus d'utilisateurs sont arrivés ou plus de capacités sont nécessaires - il suffit d'ajouter 5 à 10 nouvelles instances de service. En gros, en général, c'est ainsi que cela fonctionne, mais, comme vous le savez, le diable est dans les détails, et ce qui semblait initialement assez simple, à y regarder de plus près, peut se transformer en problèmes que personne n'a pris en compte au départ.

Dans cet article, des collègues de la pratique Java de Rexoft partagent leurs expériences sur la façon de déboguer des microservices pour le Web.



Comment atteindre l'intégrité des données transactionnelles


Lorsqu'elles tentent de transférer l'architecture d'un monolithe vers des microservices, les équipes qui n'avaient pas une telle expérience auparavant commencent souvent à diviser les services en objets de premier niveau du modèle de domaine, par exemple: utilisateur / client / employé , etc. À l'avenir, avec une étude plus détaillée, la compréhension apparaît, ce qui est plus pratique pour se diviser en blocs plus grands qui regroupent plusieurs objets du domaine en eux-mêmes. Pour cette raison, vous pouvez éviter les appels inutiles vers des services tiers.

Le deuxième point important est la prise en charge de l'intégrité des données transactionnelles. Dans le monolithe, ce problème est résolu via le serveur d'applications, où la guerre / l'oreille tourne, à l'intérieur de laquelle le conteneur, en fait, définit les limites des transactions. Dans le cas des microservices, les limites des transactions sont floues et il est nécessaire, en plus d'écrire du code logique métier, de pouvoir gérer l'intégrité des données et maintenir leur cohérence entre les différentes parties du système. C'est une tâche assez non triviale. Des recommandations pour résoudre ce type de problème architectural peuvent être trouvées sur Internet et dans les communautés techniques concernées.

Dans cet article, nous essaierons de décrire les difficultés techniques spécifiques qui surviennent lorsque les équipes essaient de travailler avec des microservices et les moyens de les résoudre. Je constate tout de suite que les options proposées ne sont pas les seules vraies. Il existe peut-être des services plus élégants, mais les recommandations que je donnerai sont testées dans la pratique et résolvent précisément les difficultés existantes, et leur utilisation est une affaire personnelle pour tous.

Le principal problème avec les microservices est qu'ils sont extrêmement faciles à exécuter localement (par exemple, en utilisant spring.io et intellij idea , cela peut être fait en seulement 5 minutes, voire moins). Cependant, lorsque vous essayez de faire de même dans Kubernetesle cluster (si vous en aviez peu d'expérience auparavant), un simple lancement du contrôleur imprimant «Hello World» lors de l'accès à un point de terminaison spécifique peut prendre une demi-journée. Dans le cas d'un monolithe, la situation est plus simple. Chaque développeur dispose d'un serveur d'applications local. Le processus de déploiement est également assez simple - vous devez copier manuellement l' artefact final de guerre / oreille au bon endroit dans le serveur d'applications ou à l'aide de l' EDI . Ce n'est généralement pas un problème.

DĂ©bogage des nuances


Le deuxième point important est le débogage . Dans les situations avec un monolithe, on suppose que le développeur a un serveur d'applications sur sa machine, sur lequel sa guerre / oreille est déployée. Vous pouvez toujours déboguer, car tout ce dont vous avez besoin est à portée de main. Avec les microservices, tout est un peu plus compliqué, un service est généralement une chose en soi. En règle générale, il a son propre schéma de base de données, dans lequel ses données sont situées, exécute des fonctions spécifiques qui lui sont propres, toutes les communications avec d'autres services sont organisées via des appels HTTP synchrones (par exemple via RestTemplate ou Feign), asynchrones (par exemple Kafka ou RabbitMQ). Par conséquent, la tâche essentiellement simple de sauvegarder ou de valider un certain objet qui a été précédemment implémenté en un seul endroit, à l'intérieur d'un seul fichier war / ear, dans le cas général avec une approche microservice, devient représentable sous la forme: aller à un ou N services adjacents, que ce soit des opérations d'acquisition de données , par exemple, certaines valeurs de référence, ou l'opération d'enregistrement des entités adjacentes,dont les données sont nécessaires pour exécuter la logique métier dans notre service. L'écriture de la logique métier dans ce cas devient beaucoup plus difficile.

En conséquence, les options de solution sont les suivantes :

  1. Écrivez votre code de logique métier. Dans le même temps, tous les appels externes sont moqués - les contrats externes sont émulés, les tests sont écrits sous l'hypothèse que les contrats externes sont exactement comme ça, après quoi il y a un déploiement sur le circuit pour vérification. Parfois, c'est chanceux et l'intégration fonctionne tout de suite, parfois c'est malchanceux - vous devez refaire le code de logique métier une nième fois, car pendant le temps que nous avons implémenté la fonctionnalité, le code dans le service adjacent a été mis à jour, les signatures d'API ont changé et nous devons le refaire une partie de la tâche est de son côté.
  2. . , , Kubernetes, . . , — , remote debug . , runtime , , . -, , 2–5 , . . , Kubernetes , . -, (Per thread), , .

Kubernetes


En fait, une solution à ce problème est la téléprésence . Il existe probablement d'autres programmes de ce type, mais l'expérience personnelle n'a été qu'avec lui et il s'est établi positivement. En général, le principe de fonctionnement est le suivant:

Sur la machine locale, le développeur installe telepresenc e, configure kubectl pour accéder au cluster Kubernetes correspondant (ajoute la configuration de boucle à ~ / .kube / config ). Après cela, la téléprésence démarre , qui agit en fait comme un proxy entre l'ordinateur du développeur local et Kubernetes. Il existe différentes options de lancement, il est préférable de regarder plus en détail dans le guide officiel, mais dans le cas le plus basique cela se résume à deux étapes:

  1. Sudo telepresence (, Linux- , sudo . , root/). Kubernetes deployment telepresence . deployment Kubernetes.
  2. Le démarrage de votre instance de service s'effectue comme d'habitude sur l'ordinateur local du développeur. Cependant, dans ce cas, il aura accès à l'ensemble de l'infrastructure du cluster Kubernetes, que ce soit Service Discovery (Eureka, Consul), Api Gateway (Zuul), Kafka et ses files d'attente, le cas échéant, etc. Autrement dit, nous avons accès à tout l'environnement de cluster dont nous avons besoin, mais localement. Le bonus est la possibilité de débogage local, mais dans un environnement de cluster, et ce sera déjà beaucoup plus rapide, car nous sommes en fait à l'intérieur de Kubernetes (via le tunnel), et n'y accédons pas de l'extérieur via le port pour le débogage à distance.

Cette solution présente plusieurs inconvénients:

  1. Telepresence Linux Mac, Windows VFS, , issue GitHub. . , - Linux/Mac, .
  2. , Service Discovery (Eureka, Consul) — Round Robin , endpoint , , , :

  • kubernetes -> . telepresence deployment , «» Eureka ip-address:port/service-name dns-name:port/service-name , . . Kubernetes , timeout;
  • deployment - Kubernetes , ( ) (Round Robin), ;
  • endpoint, feature, HTTP 404 endpoint Gateway, Service Discovery , Round Robin . Service Discovery endpoint , HTTP 404.
  • , , .


Par routage dynamique d'une demande, nous entendons que la passerelle API (Zuul) a la possibilité de choisir parmi plusieurs instances du même service dont nous avons besoin. Dans le cas général, ce problème peut être résolu en ajoutant un prédicat qui vous permet de sélectionner le service souhaité dans le pool de services communs avec le même nom au stade du traitement de la demande. Naturellement, chaque service parmi ceux avec lesquels nous voulons pouvoir router dynamiquement, devra avoir une sorte de méta-information contenant des données qui seront utilisées pour déterminer si ce service est nécessaire ou non. Spring Cloud (dans le cas d'Eureka), par exemple, vous permet de le faire en spécifiant dans un bloc de métadonnées spécial dans application.yml :

eureka:
  instance:
    preferIpAddress: true
    metadata-map:
      service.label: develop

Après avoir enregistré un tel service dans Service Discovery dans son com.netflix.appinfo.InstanceInfo # getMetadata, il y aura une étiquette avec la clé service.label et la valeur develop , qui peut être obtenue au moment de l'exécution. Un point important au stade du démarrage d'un service consiste à vérifier si une instance de service existe dans Service Discovery avec de telles méta-informations ou non afin d'éviter les collisions potentielles.

Options de routage


Après cela, la solution du problème peut être réduite à deux options:

  1. API Gateway . , , , , Headers: DestionationService: feature/PRJ-001. , , Header . , — - API Gateway.
  2. API Gateway, , . ., , , Zuul 1 endpoint- /api/users/… user, feature/PRJ-001, Zuul 2 endpoint- /api/users/… user, feature/PRJ-002. , N API Gateway N , . . , . . feature — , , , , , , . API Gateway, , . ., , , — , .






Dans le cadre de l'API Gateway, il est également utile de fournir un mécanisme qui vous permet de modifier les règles de routage lors de l'exécution. Il est préférable de placer ces paramètres dans config-map . Dans ce cas, il suffira de réécrire les nouvelles routes et soit de redémarrer l'API de passerelle dans Kubernetes pour mettre à jour le routage, soit d'utiliser le Spring Boot Actuator (à condition qu'il existe une dépendance correspondante dans l'API de passerelle) - appelez endpoint / refresh, qui relit essentiellement données de config-map et mettra à jour les itinéraires.

Un point important est qu'il devrait y avoir, relativement parlant, une instance de référence du service (par exemple, impression de développer, qui sera collecté auprès de la branche principale du développement du service) et d'une API de passerelle principale distincte, qui sera toujours spécifiée dans les paramètres d'accès au service. En substance, nous nous fournissons un environnement de transfert indépendant qui sera toujours opérationnel dans le contexte du routage dynamique.

Un exemple de bloc config-map pour l'API Gateway qui contient des paramètres de routage (ici, c'est juste un exemple de son apparence, pour un fonctionnement correct, il nécessite une liaison correspondante sous forme de code sur le côté backend du service API Gateway) :

{
  "kind": "ConfigMap",
  "apiVersion": "v1",
  "metadata": {
    ...
  },  
"data": {
    ...        
    "rules.meta.user": "develop",
    "rules.meta.client": "develop",
    "rules.meta.notification": "feature/PRJ-010",
    ...    
  }
}

rules.meta est une carte contenant des règles de routage pour les services.
utilisateur / client / notification - le nom du service sous lequel il est enregistré dans Eureka.

develop / feature / PRJ-010 - étiquette de service à partir du fichier application.yml du service correspondant, sur la base de laquelle le service souhaité sera sélectionné parmi tous les services disponibles portant le même nom dans Service Discovery , s'il existe plusieurs instances d'un tel service.

Conclusion


Comme tout dans ce monde, les outils et solutions informatiques ne sont pas parfaits. Ne pensez pas que si vous modifiez l'architecture, tous les problèmes se termineront en même temps. Seule une immersion détaillée dans les technologies utilisées et votre propre expérience vous donneront une image réelle de ce qui se passe.

J'espère que ce matériel vous aidera à résoudre votre problème. Tâches intéressantes et vendez sans bugs!

All Articles