Microservices
Le principe du microservce est simple : une application est composé de différents petits services, fortement découplés, et orientés métier. Ces services communiquent entre eux pour composer une application. L’application, naturellement distribuée, gagne en souplesse et en robustesse, l’utilisation des ressources est optimisée, les évolutions (changements et croissance) sont simplifiées.
Les microservices sont une nouvelle étape dans les architectures serveurs. Créées en opposition aux applications monolithiques, les microservices utilisent pleinement les possibilités du Cloud computing et des conteneurs. Théorisés et mis en place par de très gros sites (Netflix, Twitter, Google …), les nouvelles abstractions, et les outils adéquats, qu’apportent les microservices sont maintenant utilisables par tout le monde.
Tout cela a bien sûr un coût : l’application est plus complexe, plus dure à tester et la gestion des latences devient une priorité. La robustesse n’existe que si les clients font l’effort d’implémenter une stratégie de tolérance aux pannes (refaire un appel échoué sur un nouveau noeud, gèrer les timeouts…).
Routage
Le point central de ce type d’application reste le routage, comme pour les classiques sites web.
Par contre, cette notion de routage est généralisée. Le traitement peut-être synchrone ou asynchrone, séquentiel ou parallèle, unitaire ou par lot, avec un ou plusieurs essais, ou un mix de tout ça.
Ces différentes approches existent actuellement de manière dispersée, avec le routage des applications webs, les services web, et les workers asynchrones avec ses files d’attente.
Une notion formelle de RPC peut être utilisé (thrift, finagle…) mais une simple sérialisation (JSON, msgpack, protobuff…) avec du classique REST, ou un serveur de message (AMQP, NSQ, Redis…) fonctionne tout aussi bien.
L’abstraction de message, bien connu dans le protocole HTTP, permet d’enrichir, de restreindre, de sécuriser, d’organiser, de compresser, de surveiller les échanges de messages indépendamment de leur contenu et de manière transparente pour l’application. Des outils classiques et éprouvés comme HAproxy ou Nginx ont tout à fait leur place dans le nouveau monde des microservices.
Dynamisme
Le découplage et la communication par message permettent de distribuer simplement les services (en faisant varier le nombre d’instances d’un même service). De manière similaire un service crashé peut être remplacé sans heurt. Les mises à jour peuvent être roulantes, ou même atomiques en refusant les nouvelles requêtes, attendant la fin de requêtes en cours, avant de basculer le routage sur la nouvelle version du service. Des choses plus complexes, comme du A/B testing sont réalisable simplement.
Le routage peut facilement devenir dynamique et même réactif, utilisant le concept de découvertes de service.
Pour garantir un tant soit peu de cohérence, il est possible d’utiliser un coordinateur comme le tant redouté ZooKeeper, ou une de ses alternatives comme Etcd ou Serf.
Des outils de plus haut niveau se basent sur ces outils, comme consul, fleet, vulcan ou Eureka.
Crée à l’origine comme base clef/valeur distribuée pour mettre à disposition une configuration pour un cluster, les produits ont évolué pour proposer du pub/sub (les clients intéressés sont prévenus d’un changement de clef et déclenche une action), puis du DNS (en exposant les valeurs de la base) et même du monitoring (la disparition d’un service devient un évènement, qui sera traité).
Un nouvel antagonisme apparait avec le routage dynamique : le choix entre l’orchestration et la chorégraphie. L’orchestration déclare les différentes routes de manière explicite, la chorégraphie donne des instructions pour ensuite laisser les différents éléments se débrouiller.
Pour valider la résilience de l’approche “chorégraphie”, il existe le mythique Chaos Monkey, que seuls les plus braves osent l’utiliser en production.
Inspecter
Un service peut être composé d’un ou plusieurs services, eux-mêmes composés de services. Le client discutant avec une façade, il ne connait (ni se soucis) de l’implémentation du service. Cette approche aide au découplage des différents services, mais rend plus complexes le débug et l’optimisation des différentes latences.
Comme à chaque fois, la réponse à été donnée par Google il y a quelque temps (en 2010), sous la forme d’un white paper : Dapper. Comme souvent, une implémentation en Java en est faite, par Twitter, cette fois-ci : Zipkin. Zipkin vise gros et il prévoit de travailler avec le reste de l’écosystème Twitter. Il existe heureusement un clone plus léger, Appdash, avec un protocole simple et bien spécifié.
Prévu initialement pour mesurer les latences sans perturber le service (avec de l’échantillonnage léger, 1 sur 1024), ces outils de tracing permettent aussi de voir simplement ce qu’il se passe, de repérer ce qu’il est pertinent de paralléliser, d’aider à optimiser et dimensionner.
Google insiste sur la notion d’ubiquité dans Dapper et instrumente tout son code à partir de quelques bibliothèques clefs (gestion de thread, du réseau…).
Avec du monkeypatch ou des hooks, il n’est pas compliqué d’instrumenter des frameworks RPC comme Nameko : j’avais fait un prototype en monkeypatch, j’ai eu droit à un commentaire proposant une meilleure approche avec un hook élégant.
Il est aussi possible de faire de l’instrumentation en boite noire avec des outils comme Packet beat.
Visualiser
L’ensemble des services forme des réseaux analysables et visualisables par des outils classiques comme Networkx. Ces réseaux peuvent être décrits de manière explicite, par introspection, ou en surveillant (en échantillonant) la communication engendrée par l’application ou des tests.
Plus sportif, il est possible de simuler et de tester le coté dynamique des routes avec Spigo, prometteur, mais pour l’instant très lié à l’écosystème de Netflix.
Isoler
Le découpage permet aussi d’isoler les services, pour simplifier la prise en main par différentes équipes, pour déployer chacun à son rythme.
Mais le découpage permet aussi de compartimenter l’accés aux ressources, pour éviter qu’un incident sur un service entraine l’ensemble et finisse par faire tomber l’application.
Le terme technique est disjoncteur. En cas d’abus, on coupe tout de suite, avant que ça casse tout.
Concrètement, une utilisation fine des timeouts permet d’écourter pas mal de drames. Une allocation fixe des ressources permet de garantir que chaque service n’ira pas perturber son voisin. Les cgroups de Linux ont été conçues pour ça, et sont un des piliers de la conteneurisation. Contraindre les accès aux disques durs et au réseau sera plus un poil plus complexe que le CPU et la RAM. Pour limiter la portée des bêtises (volontaire ou non), apparmor est simple à mettre en place.
Il est possible d’automatiser entièrement la mise à disposition de ressources matérielles avec Mesos, mais le ticket d’entrée est conséquent, Swarm semble plus économe, mais reste encore très jeune.
Les outils de containerisation sont conçus pour restreindre l’utilisation des services, mais aussi pour mesurer leurs usages. Les outils de mesures sont en pleine effervescence, dispersés, mais cAdvisor propose un produit complet, se connectant sur Influxdb ou le très prometteur Prometheus.
La suite
Les microservices sont bien entendu un buzzword de plus, mais ils existent bel et bien. Pour l’instant peu formalisés, les microservices sont un ensemble de principe à suivre, pour généraliser et étendre l’architecture orientée service qui est actuellement la norme. Les ennemis des microservices sont clairement identifiés (le couplage fort, les applications monolithiques, les latences, les problèmes de montée en puissance…), les drames possibles le sont un peu moins.
Toutes innovations amènent avec elles une catastrophe potentielle. Les gens qui ont fabriqué le Titanic ont aussi inventé le naufrage à grande échelle. Il est donc important d’anticiper les drames potentiels pour ne pas se laisser surprendre, et de surveiller les erreurs que font les autres, pour éviter de les reproduire.