Quantcast
Channel: Publicis Sapient Engineering – Engineering Done Right
Viewing all articles
Browse latest Browse all 1865

Microservices – Des pièges

$
0
0

MicroServices.001

Microservices. C’est une architecture dont on entend beaucoup parler, mais que se cache-t-il derrière ce terme ?

Avec une série de trois articles, nous allons tenter de découvrir ce qu’est une architecture microservices et ce qu’elle change par rapport à une architecture "classique".

Le fil directeur de cette article sera une application imaginaire. Elle va nous aider à mettre en situation certaines erreurs qu’il faut éviter dans les architecture microservices. Cette application est particulièrement critique et se doit d’assurer un taux de disponibilité important. Par ailleurs, le client est conscient que le besoin est changeant et donc qu’il faut une architecture hautement évolutive. Ayant été séduit par une présentation de l’architecture microservices lors d’un événement, la décision est prise : l’application sera un système de microservices.

J’ai regroupé plusieurs services en un seul "macroservice"

Passé l’enthousiasme de la découverte, certains aspects de la mise en œuvre d’une architecture microservices jugés "difficiles" sont mis de coté. En particulier en ce qui concerne la granularité du découpage. Certaines fonctionnalités qui, bien que distinctes, se ressemblent, sont agrégées au sein de macroservices, d’une part pour avoir moins de services à déployer et d’autre part pour pouvoir factoriser des traitements communs. Deux mois après avoir débuté le développement de l’application, le comportement attendu d’une fonctionnalité doit changer radicalement. L’équipe se rend alors compte que cette fonctionnalité ne ressemble plus du tout aux autres fonctionnalités du macroservice. La factorisation que le macroservice apportait n’est plus d’actualité et on ne peut plus faire évoluer la fonctionnalité voulue indépendamment de celles présentent au sein du même macroservice.

L’un des principaux bénéfices des architectures microservices est l’évolutivité qu’elles apportent. Chaque service est totalement isolé des autres, ne communique qu’au travers d’un contrat d’interface souple, devant permettre à un service d’évoluer sans que les autres le sachent. C’est pour cela qu’il est important que chaque microservice puisse disposer de sa propre base de données, de ses propres API "utilitaires" et de ses propres schémas de données. Le principe d’isolation étant à la base des microservices, il est préférable d’éviter les dépendances transverses aux différents services. Dans l’idéal, l’application du principe DRY ne devrait donc se faire qu’au sein d’un même service.

Chaque méthode de mon application est devenue un microservice

Emballée par les principes des microservices, l’équipe de développement décide de les pousser au maximum. L’application est donc découpée et redécoupée en services toujours plus petits jusqu’au point où chaque méthode est isolée dans un "nanoservice". Les premiers tests donnent de mauvais résultats. Les échanges entre ces "nanoservices" sont extrêmement complexes à comprendre et à surveiller, les performances sont mauvaises et le nombre de serveurs nécessaire est important.

En effet, les nanoservices, qui sont un antipattern notoirement connu, poussent le principe des microservices tellement loin que les inconvénients finissent par surpasser les bénéfices. Découper l’application en différents services a un coût. Un coût en terme de ressources serveurs. Un nouveau service signifie au minimum un nouveau processus, sinon plusieurs, voire un nouveau serveur. Ce découpage présente aussi un coût au niveau des performances. En effet, la communication entre les services se fait au travers du réseau, ce qui implique une légère latence. Plus les services sont découpés finement, plus la latence se fait sentir. Enfin, la multiplication trop importante des services finit par rendre très difficile la surveillance du système.

Une question se pose alors. Comment trouver une taille de service présentant un bon compromis ? Il n’ y a pas de réponse définitive, mais des pistes peuvent être suivies.

Tout d’abord en faisant l’analogie entre une architecture microservices et un système UNIX. Dans un système UNIX, chaque commande fait une seule chose, mais le fait bien. On peut obtenir un résultat très complexe en "composant" plusieurs commandes à l’aide de pipes. Ici les commandes sont les microservices, et les pipes, le bus (ou les interfaces REST). Un service se définit alors par un verbe ou un nom. Il s’agit donc ni plus ni moins d’appliquer le "single responsibility principle" au niveau des services.

Mon équipe ne s’adapte pas aux microservices

L’équipe de développement, forte d’une trentaine de développeurs, a déjà réalisé d’autres applications pour le client. L’équipe est composée de sous équipes qui se répartissent les tâches. La première regroupe les spécialistes backend, la seconde les DBA et la dernière les spécialistes frontend. La réalisation de l’application est bien entamée, mais beaucoup de problèmes apparaissent. Il est difficile de déterminer qui est en charge d’un service donné dans chacune des sous équipes. Lors de la réalisation d’un service, tous les intervenants ne sont pas disponibles en même temps car soumis à des impératifs dans leur équipe et aucun suivi des services n’est fait après la réalisation.

En effet les microservices ne sont pas à aborder que du point de vue technique, mais aussi d’un point de vue organisationnel. La loi de Conway stipule que l’architecture d’une application est le reflet de l’organisation des équipes qui l’ont créée. Or les architectures microservices promeuvent un découpage en unités fonctionnelles ayant chacune leur propre pile technologique, plutôt qu’un découpage en couches. Cela impose donc d’avoir des équipes pluridisciplinaires, capables de résoudre des problèmes tant au niveau du middleware que de la base de données. Il faut donc adapter l’organisation.

Les microservices encouragent aussi une approche produit plutôt que projet. L’équipe de développement est alors responsable de chaque service de bout en bout, créant un lien entre le développeur et l’utilisateur. L’idée étant que l’équipe de développement ne pense plus l’application comme un ensemble de fonctionnalités, mais comme un moyen de faciliter le travail de l’utilisateur. La difficulté est ici que l’accroissement des responsabilités de l’équipe implique des compétences accrues elles aussi, des compétences DevOps.

Enfin, sur le même principe que la loi de Conway, la taille de l’équipe doit être proportionnelle à la taille des services. Il est donc fortement recommandé d’avoir des équipes relativement réduites, ceci pour faciliter l’appropriation du service.

Impossible de relancer ou ajouter rapidement des instances de mes services

Le système tourne désormais en production depuis deux semaines sans problème apparent. Les responsabilités sont bien découpées entre les différents services qui sont tous redondés. Cependant, une nuit où elle est particulièrement sollicitée, une instance d’un service critique tombe. Puisqu’il n’y aucun mécanisme de relance automatique d’instance de service suite à une alerte remontée par le monitoring, il faut lancer cette instance manuellement. Heureusement, une personne est d’astreinte et s’y attelle rapidement. Le problème est que la procédure manuelle de déploiement d’une nouvelle instance est longue de plusieurs dizaines de minutes. Trop long, la dernière instance du service déjà sinistré vient de tomber à son tour. Le système n’est plus opérationnel, il y a rupture de service.

Une architecture microservices est pourtant censée être résistante à la panne, alors pourquoi est-ce arrivé ?

Il faut tout d’abord ne pas perdre de vue que résistant à la panne ne veut pas dire infaillible. Ensuite, la résilience des architectures microservices se résume ainsi : des services éphémères pour un système durable. Parce que les services sont faillibles, ils failliront. Et quand ils tombent, d’autres doivent pouvoir reprendre leurs places immédiatement. Un mécanisme de surveillance doit permettre de détecter ces défaillances et lorsqu’elles se produisent, d’autres instances du service concerné doivent être lancées pour que le système continu à remplir sa mission.

Cette résilience des architectures microservices passe donc par :

  • Un mécanisme de surveillance suffisant
  • Un démarrage et une configuration des serveurs automatisés
  • Un déploiement des services automatisé

Pour l’automatisation des tâches de déploiement des serveurs et des services, des outils comme Docker, Puppet ou encore Ansible sont des solutions à étudier. Le Cloud est quant à lui l’infrastructure la plus "naturelle" pour une architecture microservices car c’est cet environnement qui est le plus adapté à la souplesse exigée par une architecture microservices. En ce qui concerne la surveillance, nous allons aborder le sujet dans le point suivant.

Il est très difficile de comprendre ce qu’il se passe entre mes services

Depuis une heure environ, le système connaît de forts ralentissements, le service est toujours assuré mais il est fortement perturbé. Une première analyse sur les serveurs ne donne rien. Un service est-il saturé ? Y-a t’il des problèmes de réseau ? Est-ce un bug applicatif ? Très difficile de le savoir. Les perturbations finissent par disparaître, mais sans que la cause du problème n’ait été identifiée. L’équipe est inquiète. Comment savoir si, ou plutôt quand, ce problème se reproduira ? Est-il le symptôme d’un problème plus global et plus grave?

La surveillance est un point critique des architectures microservices. Tout d’abord, comme nous l’avons vu précédemment, les services doivent pouvoir "mourir" sans compromettre le bon fonctionnement du système dans son ensemble. Cette capacité implique d’avoir une liste de tous les services fréquemment mise à jour. Il faut ensuite savoir si les services sont saturés ou non, pour pouvoir adapter la taille du système à la charge. Obtenir des informations fonctionnelles sur les services pourra aider à qualifier rapidement un problème rencontré. Par exemple, si un service indique qu’un grand nombre de données qu’il reçoit sont incomplètes ou erronées, cela peut indiquer que les producteurs de ces données rencontrent des difficultés ou ont un bug. Enfin, le fait que les services soient répartis sur des serveurs différents complexifie l’exploitation du système. Les flux réseaux entre les serveurs doivent être surveillés, de même que tous les serveurs actifs.

Sans être exhaustif, voilà un ensemble de caractéristiques importantes d’un système de surveillance pour une architecture microservices :

  • Automatisation importante
  • Bonne intégration avec les environnements techniques des différents services
  • Supervision de l’état "technique" des serveurs
  • Supervision fonctionnelle de chacun des services.
  • Détection de la "mort" ou de la "naissance" des services

Vous l’avez compris, plus la quantité de données obtenue est importante, plus facile et rapide sera la gestion des problèmes en production. Pour faciliter ce monitoring, des outils sont récemment apparus, tel que Marathon / Mesos.

Il est également possible de mettre en œuvre un bus secondaire, utilisé par les services pour remonter toutes les informations pouvant être utiles au monitoring. Couplées avec un mécanisme de keep alive, ces informations vont vous permettre de détecter les montées en charge de vos services et leurs crashs. Il est alors possible de relancer des instances d’un service venant de tomber, et d’adapter le nombre de services lancés au niveau de charge de l’application, et ce, sans dépendre des capacités d’auto scaling des fournisseurs de solutions Cloud.

Un autre usage intéressant d’un système de surveillance est de pouvoir fournir l’ossature d’un catalogue de services. Un tel outil devient très pratique lorsque le nombre de services devient important. Il peut alors être possible de connaître toutes les fonctionnalités offertes par le système à un moment donné.

La recherche d’information dans les logs est trop longue

Grâce au mécanisme de surveillance mis en place, un bug potentiel a été détecté dans le système. En effet un service a remonté l’information selon laquelle un service "appelé" répondait mal à un certain type de requête. Le service potentiellement défectueux ne remontant aucune information concernant les dites requêtes, une analyse des logs est nécessaire. Un membre de l’équipe va donc se connecter aux serveurs hébergeant les instances du service "appelant" pour récupérer les logs, puis fait de même pour les instances du service "appelé". Une fois les fichiers récupérés, il doit encore faire correspondre les traces du service "appelant" avec les traces correspondantes dans le service "appelé", le tout sans garantie que les logs donnent plus d’informations que ce que le mécanisme de surveillance a déjà signalé.

Cette façon de procéder est bien trop longue, et peut faire perdre un temps précieux. C’est pour cette raison qu’il est très recommandé d’avoir un mécanisme de centralisation des logs.

Ce mécanisme présente dans, l’idéal, les propriétés suivantes :

  • Récupération automatique du maximum de logs possible
  • Bonne intégration avec les environnements techniques des différents services
  • Consolidation des données pour faciliter leur exploitation

Flume, ELK et Kafka sont des outils fréquemment utilisés pour cet usage.

Conclusion

Les pièges sont donc nombreux, et ce, à toutes les étapes du cycle de vie du système. On peut néanmoins les classer en deux grandes catégories.

Tout d’abord les difficultés résultant du changement dans la façon de penser qu’impose l’architecture microservices. Changement dans la façon de concevoir des services bien sûr, mais aussi dans la façon de penser l’équipe de développement et le rôle du développeur. Les équipes deviennent transverses, DevOps également. Et les organisations doivent accompagner et suivre cette mutation. La responsabilisation des équipes de développement est au cœur des microservices. Il s’agit d’un gage de qualité, mais aussi d’une importante remise en question des organisations traditionnelles. Enfin, certaines pratiques communes telles que la mise en commun des schémas de données ou du code entre les services ne sont pas forcément compatibles avec l’esprit de l’architecture microservices.

Ensuite, la distributivité et la souplesse de cette architecture apportent plusieurs difficultés. En effet, si du point de vue de la résilience et de l’évolutivité, une telle approche apporte de grands bénéfices, elle est difficile à mettre en œuvre et demande une grande rigueur et un niveau d’exigence élevé. La maintenance de la cartographie des services et de la cohésion de l’ensemble peuvent poser problèmes.

Malgré ces risques, l’architecture microservices est une option de choix lorsqu’il y a un besoin d’évolutivité important, comme dans le cas des applications B to C où l’adaptation rapide au marché est un critère primordial, ou dans les applications soumises à une forte charge et devant assurer un service continu.

 

Références

http://martinfowler.com/articles/microservices.html

http://microservices.io/patterns/microservices.html

http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html

http://www.infoq.com/news/2015/01/microservices-sharing-code

 


Viewing all articles
Browse latest Browse all 1865

Trending Articles