Microservices : découverte, load balancing, ... quelles solutions ?

Plantons le décor

Le mot microservice est de plus en plus présent dans les discussions traitant d’architecture orientée services. Sans revenir sur le bien fondé ou non d’une telle approche, qui, pour couper court à toute tentative de troll, dépendra surtout des usages concernés, il convient peut être de réintroduire tout d’abord quelques notions associées utiles à la compréhension de ce qui suit.

Finger framePour simplifier la présentation, voici une définition macroscopique de ce qu’est une architecture orientée micro services : un ensemble de services aux responsabilités ciblées, offrant une simplicité d’exécution et utilisant un protocole de communication simple et léger.

Un avantage avancé pour ce type d’architecture : offrir une évolutivité simplifiée en permettant de mettre à jour avec un minimum d’impact certaines fonctionnalités du SI. Cette approche permet par ailleurs d’apporter de l’élasticité pour répondre à un besoin de charge variant, et ce, de façon ciblée.

Mais voilà, il s’agit toujours de compromis. Une architecture orientée “micro services” va demander une gestion particulière du parc des services disponibles. On n’administre pas de la même façon une architecture constituée de 500 services qu’une comportant 4 applications monolithiques. Les déploiements, le référencement des services disponibles, le routage des requêtes, la répartition de la charge au sein des services… Tout prend une autre dimension.

Ces architectures doivent répondre à deux besoins, assez proches, mais pas forcément traités de façon identique : être en capacité de répondre aux requêtes externes avec toute l’élasticité nécessaire et router les échanges internes entre services.

Tout n’est pas simple

DifficultUn tel type d’architecture pose beaucoup de défis, lesquels peuvent être autant d’arguments, potentiellement valables, pour ne pas se lancer dans un projet de ce type. Les bénéfices de ces architectures sont contre balancés par un ticket d’entrée à ne pas négliger.

On peut citer :

  • Identifier les nouveaux services déployés
  • Connaitre l’état des services déployés
  • Etre en mesure de router les requêtes vers les instances opérationnelles
  • Gérer les problèmes de communication
  • Savoir équilibrer la charge sur les instances opérationnelles
  • Etre en mesure de collecter les métriques en provenance de tous les services

Et ceci sur un nombre d’instances des services pouvant varier rapidement si la charge l’exige.

L’arrivée providentielle des solutions de gestion de configuration et déploiement (Puppet & co) permet à une infrastructure modeste adoptant une approche orientée autour des microservices de pouvoir s’en sortir avec des éléments traditionnels. A l’aide de solutions éprouvées, comme HAProxy, il est possible de gérer un parc des services en proposant une qualité de service satisfaisante. Un nouveau serveur peut ainsi être déployé, déclaré dans la configuration d’HAProxy et entrer dans le pool des services opérationnels. Mais une telle approche atteindra ses limites avec un nombre croissant de services et/ou d’instances.

Il est possible de voir le problème sous un autre angle (et donc d’imaginer une autre approche). Le besoin primaire se résume à fournir à un service le moyen de contacter un autre service, opérationnel au moment de la demande, sans affinité de session particulière, et si possible le plus rapidement. L’idée alternative repose donc sur une approche décentralisée : fournir au service A des infos fraiches sur les services dont il dépend et uniquement ceux-ci. Ce faisant, le service A n’a pas à appeler un load balancer central et peut directement s’adresser à l’instance souhaitée.

Quels avantages cette approche peut apporter ?

  • une capacité à scaler plus importante
  • orientées cloud, ces solutions permettent de ne pas s’appuyer sur des dispositifs spécifiques (exemple un F5), mais sur des solutions applicatives adaptées
  • une résilience accrue : si un load balancer central fait défaut, ce changement impactera fortement une architecture, tandis que dans un cas décentralisé, ce SPoF disparait.
  • un chemin plus court vers le service appelé : le passage par un LB central n’est plus nécessaire. C’est une petite économie toujours bonne à prendre.
  • pas de rechargement fréquent d’une configuration centrale.

Quels inconvénients ?

  • une montée en compétence sur une solution de gestion particulière

Nous ne sommes pas seuls

We are not alone

Des solutions provenant d’acteurs du net, open sourcées, existent et peuvent d’ores et déjà être adoptées. AirBnB propose la sienne, Netflix aussi pour ne citer qu’eux. Les approches sont légèrement différentes entre ces deux acteurs mais la logique et la finalité restent les mêmes.

Dans le premier cas, il s’agit d’une solution peu intrusive d’un point de vue applicatif, nécessitant le déploiement de deux composants et d’un HAProxy local.

Les composants de la solution d’AirBnB, nommée SmartStack, sont :

Nerve

Sa responsabilité est de maintenir l’état des services à jour dans Zookeeper. Il s’appuie sur des points d’entrée de check exposés par le service permettant de déterminer si ce dernier est apte à recevoir des requêtes ou non.

Synapse

Ce composant est responsable de la consommation des informations présentes dans Zookeeper qui concernent les dépendances de votre service. Il récupère ainsi les informations sur les instances opérationnelles et met à jour la configuration de l’instance locale de HAProxy.

Votre service, lorsqu’il souhaite accéder à une de ses dépendances, passe par l’instance locale de HAProxy, laquelle, régulièrement mise à jour, permettra d’atteindre une instance opérationnelle.

Cette décentralisation offre l’avantage de ne pas être fortement liée à l’implémentation des services, outre l’exposition des points d’entrée de check nécessaires à Nerve.

Les composants proposés par AirBnB, et éprouvés sur leur infrastructure, sont développés en Ruby.
La seconde solution provient de Netflix. Netflix a mis à disposition un grand nombre de solutions, couvrant un périmètre très large. Certains composants peuvent être utilisés indépendamment, tandis que d’autres tireront profit d’un fonctionnement conjoint.
C’est notamment le cas des composants de gestion des microservices.
Sensiblement plus intrusive dans le code, la solution proposée par Netflix a été développée initialement pour des déploiements sur le cloud d’Amazon.
Son fonctionnement repose sur plusieurs composants aux responsabilités précises.

Le principal est Eureka.

Pierre angulaire, puisque responsable de l’enregistrement des états des services dans Zookeeper, Eureka se compose d’une partie serveur et d’une partie cliente, toutes deux écrites en Java et mises à dispositions depuis maintenant 2 ans.
Les (micro) services, qu’ils soient clients d’autres services ou non, se présentent à Eureka Server via un composant Eureka Client. Ce dernier va enregistrer l’instance du service au démarrage auprès du serveur et tiendra à jour l’état via un système d’heartbeats réguliers. Le client constitue aussi un cache des informations nécessaires à la communication inter services, récupérées auprès du serveur. Ce faisant, les services ne sont pas tributaires d’Eureka Server et résisteront à une interruption de service de ce dernier.

Ribbon, optionnel, s’inscrit particulièrement bien avec Eureka.

Cette librairie fournit au service un client pour les communications avec ses dépendances. Les fonctionnalités offertes sont :

  • le load balancing, selon plusieurs stratégies (Round robin, Random, Weighted Response Time, et, plus spécifique à AWS : Zone Aware Round Robin)
  • des **stratégies de **rejeu des requêtes en erreur
  • une gestion du cache
  • des possibilités de batch

Beaucoup d’autres composants peuvent être mis en oeuvre, ou le sont indirectement par ces deux composants (Hystrix par exemple, offrant la tolérance aux erreurs de communication ou à la latence).

Mais où tout cela peut il nous mener ?

Chez Ippon, nous proposons également de l’hébergement et de l’infogérance, et Cloud Foundry est une des briques essentielles de notre offre Cloud. Récemment, Pivotal a annoncé Spring Cloud : une solution à base de Spring Boot facilitant la création de services déployables sur le cloud et particulièrement sur du Cloud Foundry. Et Spring Cloud propose justement une intégration simplifiée avec la stack de Netflix. Une telle solution mérite donc notre attention.

RailAvec Spring Cloud, la déclaration d’un client Eureka, l’utilisation de Ribbon, mais aussi l’intégration avec d’autres composants de la stack Netflix comme Zuul ou Archaius sont ainsi proposées au travers une configuration minimaliste et quelques jeux d’annotations. Comme souvent avec Spring, il s’agit de mettre à disposition des fonctionnalités avancées avec un minimum d’effort.

Voici quelques exemples récemment publiés par Pivotal suite à l’annonce autour de Spring Cloud faite par Josh Long mi septembre.

La création d’un serveur Eureka tout d’abord, se résume à une simple classe décorée de l’annotation @EnableEurekaServer.

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

On peut difficilement faire plus simple. Il conviendra bien sûr de lui apporter la configuration nécessaire, mais d’un point de vue ligne de code, c’est réduit au maximum.

Coté client (ou service, ce dernier pouvant être aussi client d’un autre service) l’utilisation du client Eureka est rendue possible via l’annotation @EnableEurekaClient, laquelle permettra au service de s’enregistrer automatiquement auprès du serveur Eureka.

@Configuration
@EnableAutoConfiguration
@ComponentScan
@EnableConfigurationProperties
@EnableHystrix
@EnableEurekaClient
public class AccountApplication extends RepositoryRestMvcConfiguration {

    @Override
    protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(Account.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(AccountApplication.class, args);
    }
}

Spring Cloud est encore jeune et va continuer à s’enrichir, en fournissant d’autres modules “spring cloud” pour Spring Boot, et offrir le même type de facilités pour les autres composants. Hystrix est d’ores et déjà proposé, Ribbon est en approche.

Spring est dans son rôle ici : apporter l’abstraction maximale au développeur pour le décharger du code technique afin qu’il ne se concentre que sur les aspects métier.

Nous croyons chez Ippon en la valeur de ces solutions pour nos clients, lesquelles permettent la création d’applications résolument orientées pour le cloud. Ne soyez donc pas surpris si vous entendez encore parler de ces solutions lors de nos prochains événements !

Références :
http://nerds.airbnb.com/smartstack-service-discovery-cloud/
http://netflix.github.io/
http://projects.spring.io/spring-cloud/

Crédit photo :

https://www.flickr.com/photos/d_pham/
https://www.flickr.com/photos/68502717@N08/
https://www.flickr.com/photos/photography-andreas/
https://www.flickr.com/photos/khem-pravesvuth/