Microservices conteneurisés
Notes
Ce livre électronique a été publié au printemps 2017 et n’a pas été mis à jour depuis. Il y a beaucoup dans le livre qui reste précieux, mais une partie du matériel est obsolète.
Le développement d’applications client-serveur a conduit à mettre l’accent sur la création d’applications hiérarchisées qui utilisent des technologies spécifiques à chaque niveau. Ces applications sont souvent appelées applications monolithiques et sont empaquetées sur du matériel pré-mis à l’échelle pour les charges maximales. Les principaux inconvénients de cette approche de développement sont le couplage étroit entre les composants de chaque niveau, le fait que les composants individuels ne peuvent pas être facilement mis à l’échelle et le coût des tests. Une simple mise à jour peut avoir des effets imprévus sur le reste du niveau. Par conséquent, une modification d’un composant d’application nécessite que son niveau entier soit retesté et redéployé.
Particulièrement préoccupant à l’ère du cloud, il est que les composants individuels ne peuvent pas être facilement mis à l’échelle. Une application monolithique contient des fonctionnalités spécifiques à un domaine et est généralement divisée par des couches fonctionnelles telles que le serveur frontal, la logique métier et le stockage des données. Une application monolithique est mise à l’échelle en clonant l’application entière sur plusieurs machines, comme illustré dans la figure 8-1.
Figure 8-1 : Approche de mise à l’échelle des applications monolithiques
Microservices
Les microservices offrent une autre approche du développement et du déploiement d’applications, une approche adaptée aux impératifs d’agilité, de mise à l’échelle et de fiabilité des applications cloud modernes. Une application de microservices est décomposée en composants indépendants qui fonctionnent ensemble pour fournir les fonctionnalités globales de l’application. Le terme microservice souligne que les applications doivent être composées de services suffisamment petits pour refléter des préoccupations indépendantes, afin que chaque microservice implémente une seule fonction. En outre, chaque microservice a des contrats bien définis afin que d’autres microservices puissent communiquer et partager des données avec lui. Parmi les exemples typiques de microservices, citons les paniers d’achat, la gestion des stocks, les sous-systèmes d’achat et la gestion des paiements.
Les microservices peuvent effectuer un scale-out indépendamment, par rapport aux applications monolithiques géantes qui évoluent ensemble. Cela signifie qu’une zone fonctionnelle spécifique, qui nécessite plus de puissance de traitement ou de bande passante réseau pour prendre en charge la demande, peut être mise à l’échelle plutôt que d’effectuer un scale-out inutilement d’autres zones de l’application. La figure 8-2 illustre cette approche, où les microservices sont déployés et mis à l’échelle indépendamment, créant ainsi des instances de services sur plusieurs machines.
Figure 8-2 : Approche de mise à l’échelle des applications de microservices
Le scale-out des microservices peut être quasiment instantané, ce qui permet à une application de s’adapter aux changements de charge. Par exemple, un microservice unique dans la fonctionnalité web d’une application peut être le seul microservice de l’application qui doit effectuer un scale-out pour gérer le trafic entrant supplémentaire.
Le modèle classique de scalabilité des applications consiste à disposer d’un niveau sans état, à charge équilibrée, avec un magasin de données externe partagé pour stocker les données persistantes. Les microservices avec état gèrent leurs propres données persistantes, et les stockent généralement localement sur les serveurs où ils sont placés, pour éviter la surcharge liée à l’accès réseau et la complexité des opérations interservices. Cela permet de traiter les données le plus rapidement possible, et d’éviter les systèmes de mise en cache. En outre, les microservices avec état évolutif partitionnent généralement les données entre leurs instances, pour gérer la taille des données et transférer le débit au-delà duquel un serveur unique peut prendre en charge.
Les microservices prennent également en charge les mises à jour indépendantes. Ce couplage faible entre les microservices permet une évolution rapide et fiable des applications. Leur nature indépendante et distribuée prend en charge les mises à jour propagées, où seul un sous-ensemble d’instances d’un microservice unique sera mis à jour à un moment donné. Ainsi, si un problème est détecté, une mise à jour boguée peut être restaurée à son état antérieur, avant que toutes les instances ne soient mises à jour à partir du code ou de la configuration problématique. De même, les microservices utilisent généralement le versioning de schéma, ce qui permet aux clients d’accéder à une version cohérente quand les mises à jour sont appliquées, quelle que soit l’instance de microservice avec laquelle la communication a lieu.
Ainsi, les applications de microservices présentent de nombreux avantages par rapport aux applications monolithiques :
- Chaque microservice est relativement petit, facile à gérer et à faire évoluer.
- Chaque microservice peut être développé et déployé indépendamment des autres services.
- Chaque microservice peut faire l’objet d’un scale-out indépendant. Par exemple, un service de catalogue ou un service de panier d’achat peut faire l’objet d’un scale-out supérieur à celui d’un service de commande. Ainsi, l’infrastructure résultante consomme plus efficacement les ressources durant le scale-out.
- Chaque microservice isole les problèmes. Par exemple, s’il existe un problème dans un service, il impacte uniquement ce service. Les autres services peuvent continuer à gérer les requêtes.
- Chaque microservice peut utiliser les dernières technologies. Dans la mesure où les microservices sont autonomes et s’exécutent côte à côte, vous pouvez vous servir des dernières technologies et des frameworks les plus récents, au lieu de devoir employer un ancien framework susceptible d’être utilisé par une application monolithique.
Toutefois, une solution basée sur un microservice présente également des inconvénients potentiels :
- Le choix du partitionnement d’une application en microservices peut être un défi, car chaque microservice doit être complètement autonome, de bout en bout, notamment au niveau de la responsabilité de ses sources de données.
- Les développeurs doivent implémenter une communication interservice, ce qui accroît la complexité et la latence de l’application.
- Les transactions atomiques entre plusieurs microservices ne sont généralement pas possibles. Les besoins métier doivent donc adopter une cohérence finale entre les microservices.
- En production, il existe une complexité opérationnelle liée au déploiement et à la gestion d’un système résultant d’un compromis entre de nombreux services indépendants.
- La communication directe de client à microservice peut compliquer la refactorisation des contrats des microservices. Par exemple, avec le temps, la façon dont le système est partitionné en services peut être amenée à changer. Un seul service peut être divisé en deux services ou plus, et deux services peuvent fusionner. Quand les clients communiquent directement avec les microservices, ce travail de refactorisation peut rompre la compatibilité avec les applications clientes.
Mise en conteneur
La conteneurisation est une approche du développement logiciel dans laquelle une application et son ensemble versionné de dépendances ainsi que sa configuration d’environnement abstraite sous forme de fichiers manifeste de déploiement, sont packagés ensemble en tant qu’image conteneur, testés en tant qu’unité et déployés sur un système d’exploitation hôte.
Un conteneur est un environnement d’exploitation portable et isolé, dont les ressources sont contrôlées, où une application peut s’exécuter sans toucher aux ressources des autres conteneurs ou de l’hôte. Ainsi, un conteneur ressemble à un ordinateur physique ou à une machine virtuelle qui vient tout juste d’être installée.
Il existe de nombreuses similitudes entre les conteneurs et les machines virtuelles, comme illustré dans la figure 8-3.
Figure 8-3 : Comparaison des machines virtuelles et des conteneurs
Un conteneur exécute un système d’exploitation, comporte un système de fichiers et est accessible en réseau, comme s’il s’agissait d’une machine physique ou virtuelle. Toutefois, la technologie et les concepts utilisés par les conteneurs sont très différents de ceux des machines virtuelles. Les machines virtuelles incluent les applications, les dépendances nécessaires et un système d’exploitation invité complet. Les conteneurs incluent l’application et ses dépendances, mais partagent le système d’exploitation avec d’autres conteneurs. Ils s’exécutent en tant que processus isolés sur le système d’exploitation hôte (à l’exception des conteneurs Hyper-V qui s’exécutent à l’intérieur d’une machine virtuelle spéciale par conteneur). Ainsi, les conteneurs partagent des ressources et nécessitent généralement moins de ressources que les machines virtuelles.
Une approche de développement et de déploiement orientée conteneur présente l’avantage d’éliminer la plupart des erreurs liées à des configurations d’environnement incohérentes et aux problèmes qui les accompagnent. De plus, les conteneurs permettent un scale-up rapide des applications en facilitant l’instanciation de nouveaux conteneurs selon les besoins.
Les concepts clés liés à la création et à l’utilisation de conteneurs sont les suivants :
- Hôte de conteneur : machine physique ou virtuelle configurée pour héberger des conteneurs. L’hôte de conteneur exécute un ou plusieurs conteneurs.
- Image conteneur : une image se compose d’une union de systèmes de fichiers en couche empilés les uns sur les autres et est la base d’un conteneur. Une image n’a pas d’état et ne change jamais quand elle est déployée sur différents environnements.
- Conteneur : un conteneur est un instance d’exécution d’une image.
- Image de système d’exploitation de conteneur : les conteneurs sont déployés à partir d’images. L’image du système d’exploitation du conteneur est la première couche d’un nombre potentiellement important de couches d’images qui composent un conteneur. Un système d’exploitation de conteneur est immuable, et ne peut pas être modifié.
- Dépôt de conteneur : chaque fois qu’une image conteneur est créée, l’image et ses dépendances sont stockées dans un référentiel local. Ces images peuvent être réutilisées plusieurs fois sur l’hôte de conteneur. Les images conteneur peuvent également être stockées dans un registre public ou privé, par exemple Docker Hub, pour pouvoir être utilisées sur différents hôtes de conteneur.
Les entreprises adoptent de plus en plus de conteneurs lors de l’implémentation d’applications basées sur des microservices, et Docker est devenu l’implémentation de conteneur standard qui a été adoptée par la plupart des plateformes logicielles et des fournisseurs de cloud.
L’application de référence eShopOnContainers utilise Docker pour héberger quatre microservices principaux conteneurisés, comme illustré dans la figure 8-4.
Figure 8-4 : eShopOnContainers référence les microservices principaux d’application
L’architecture des services back-end dans l’application de référence est décomposée en plusieurs sous-systèmes autonomes sous la forme de microservices et de conteneurs qui collaborent entre eux. Chaque microservice fournit une seule zone de fonctionnalités : un service d’identité, un service de catalogue, un service de commande et un service de panier.
Chaque microservice a sa propre base de données, ce qui lui permet d’être complètement découplé des autres microservices. Le cas échéant, la cohérence entre les bases de données des différents microservices est assurée à l’aide d’événements au niveau de l’application. Pour plus d’informations, consultez Communication entre les microservices.
Pour plus d’informations sur l’application de référence, consultez Microservices .NET : Architecture des applications .NET conteneurisées.
Communication entre le client et les microservices
L’application mobile eShopOnContainers communique avec les microservices principaux en conteneur à l’aide d’une communication directe entre le client et le microservice , comme le montre la figure 8-5.
Figure 8-5 : Communication directe client-microservice
Avec la communication directe client-microservice, l’application mobile envoie des requêtes à chaque microservice directement via son point de terminaison public, avec un port TCP différent par microservice. En production, le point de terminaison est généralement mappé à l’équilibreur de charge du microservice, qui répartit les requêtes entre les instances disponibles.
Conseil
Pensez à utiliser la communication par passerelle API. La communication directe de client à microservice peut présenter des inconvénients lors de la création d’une application basée sur un microservice volumineux et complexe, mais elle est plus que suffisante pour une petite application. Lors de la conception d’une grande application basée sur des microservices avec des dizaines de microservices, envisagez d’utiliser la communication de passerelle API. Pour plus d’informations, consultez Microservices .NET : Architecture des applications .NET conteneurisées.
Communication entre les microservices
Une application basée sur des microservices est un système distribué, qui peut s’exécuter sur plusieurs machines. Chaque instance de service est généralement un processus. Ainsi, les services doivent interagir à l’aide d’un protocole de communication interprocessus, par exemple HTTP, TCP, AMQP (Advanced Message Queuing Protocol) ou de protocoles binaires, selon la nature de chaque service.
Les deux approches courantes pour la communication de microservice à microservice sont la communication REST basée sur HTTP lors de l’interrogation de données et la messagerie asynchrone légère lors de la communication de mises à jour entre plusieurs microservices.
La communication basée sur les événements basée sur la messagerie asynchrone est essentielle lors de la propagation des modifications entre plusieurs microservices. Avec cette approche, un microservice publie un événement quand un événement important se produit, par exemple quand il met à jour une entité métier. D’autres microservices s’abonnent à ces événements. Ensuite, lorsqu’un microservice reçoit un événement, il met à jour ses propres entités métier, ce qui peut à son tour entraîner la publication d’autres événements. Cette fonctionnalité de communication par publication-abonnement repose généralement sur un bus d’événements.
Un bus d’événements permet la communication entre la publication et l’abonnement entre les microservices, sans que les composants soient explicitement conscients les uns des autres, comme le montre la figure 8-6.
Figure 8-6 : Publier-s’abonner avec un bus d’événements
Du point de vue de l’application, le bus d’événements est simplement un canal de publication/d’abonnement exposé via une interface. Toutefois, la façon dont le bus d’événements est implémenté peut varier. Par exemple, une implémentation de bus d’événements peut utiliser RabbitMQ, Azure Service Bus ou d’autres bus de services tels que NServiceBus et MassTransit. La figure 8-7 montre comment un bus d’événements est utilisé dans l’application de référence eShopOnContainers.
Figure 8-7 : Communication asynchrone pilotée par les événements dans l’application de référence
Le bus d’événements eShopOnContainers, implémenté à l’aide de RabbitMQ, fournit une fonctionnalité de publication/d’abonnement asynchrone de type un-à-plusieurs. Cela signifie qu’après la publication d’un événement, plusieurs abonnés peuvent écouter le même événement. La figure 8-9 illustre cette relation.
Figure 8-9 : Communication un-à-plusieurs
Cette approche de communication de type un-à-plusieurs utilise des événements pour implémenter des transactions métier qui couvrent plusieurs services, ce qui garantit ainsi une cohérence à terme entre les services. Une transaction cohérente à terme se compose d’une série d’étapes distribuées. Ainsi, quand le microservice de profil utilisateur reçoit la commande UpdateUser, il met à jour les détails de l’utilisateur dans sa base de données et publie l’événement UserUpdated sur le bus d’événements. Le microservice de panier et le microservice de commande se sont tous deux abonnés pour recevoir cet événement et, en réponse, mettent à jour leurs informations d’acheteur dans leurs bases de données respectives.
Notes
Le bus d’événements eShopOnContainers, implémenté à l’aide de RabbitMQ, est destiné à être utilisé uniquement en tant que preuve de concept. Pour les systèmes de production, vous devez penser à d’autres implémentations du bus d’événements.
Pour plus d’informations sur l’implémentation du bus d’événements, consultez Microservices .NET : Architecture pour les applications .NET conteneurisées.
Résumé
Les microservices offrent une approche du développement et du déploiement d’applications adaptée aux impératifs d’agilité, de mise à l’échelle et de fiabilité des applications cloud modernes. L’un des main avantages des microservices est qu’ils peuvent être mis à l’échelle indépendamment, ce qui signifie qu’une zone fonctionnelle spécifique peut être mise à l’échelle qui nécessite plus de puissance de traitement ou de bande passante réseau pour prendre en charge la demande, sans mettre à l’échelle inutilement les zones de l’application qui ne connaissent pas de demande accrue.
Un conteneur est un environnement d’exploitation portable et isolé, dont les ressources sont contrôlées, où une application peut s’exécuter sans toucher aux ressources des autres conteneurs ou de l’hôte. Les entreprises adoptent de plus en plus de conteneurs lors de l’implémentation d’applications basées sur des microservices, et Docker est devenu l’implémentation de conteneur standard qui a été adoptée par la plupart des plateformes logicielles et des fournisseurs de cloud.