Ce document est le troisième d'une série en quatre parties sur la conception, la création et le déploiement de microservices. Cette série décrit les différents éléments d'une architecture à microservices. La série inclut des informations sur les avantages et les inconvénients du modèle d'architecture à microservices et sur la façon de l'appliquer.
- Présentation des microservices
- Refactoriser un monolithe en microservices
- Communication entre services dans une configuration à microservices (le présent document)
- Traçage distribué dans une application à microservices
Cette série est destinée aux développeurs et aux architectes d'applications qui conçoivent et mettent en œuvre la migration pour refactoriser une application monolithique en application à microservices.
Ce document décrit les différences entre la messagerie asynchrone et les API synchrones pour les microservices. Il décrit la déconstruction d'une application monolithique et vous montre comment convertir une requête synchrone dans l'application d'origine en un flux asynchrone dans la nouvelle configuration basée sur des microservices. Cette conversion inclut la mise en œuvre de transactions distribuées entre les services.
Exemple d'application
Dans ce document, vous allez utiliser une application e-commerce prédéfinie appelée Online Boutique. L'application met en œuvre des flux e-commerce de base tels que la consultation d'articles, l'ajout de produits à un panier et le règlement. L'application propose également des recommandations et des publicités en fonction de la sélection de l'utilisateur.
Séparation logique du service
Dans ce document, vous isolez le service de paiement du reste de l'application. Tous les flux de l'application Online Boutique d'origine sont synchrones. Dans l'application refactorisée, le processus de paiement est converti en flux asynchrone. Par conséquent, lorsque vous recevez une demande d'achat, vous fournissez à l'utilisateur une confirmation "demande reçue" plutôt que de la traiter immédiatement. En arrière-plan, une requête asynchrone est déclenchée afin de traiter le paiement par l'intermédiaire du service de paiement.
Avant de déplacer les données et la logique de paiement dans un nouveau service, vous devez isoler les données et la logique de paiement du monolithe. Lorsque vous isolez les données et la logique de paiement du monolithe, il est plus facile de refactoriser votre code dans la même codebase si les limites du service de paiement sont incorrectes (logique métier ou données).
Les composants de l'application monolithique présentée dans ce document sont déjà modulaires, c'est-à-dire qu'ils sont isolés les uns des autres. Si votre application présente des interdépendances plus strictes, vous devez isoler la logique métier et créer des classes et des modules distincts. Vous devez également dissocier toutes les dépendances de base de données dans leurs propres tables et créer des classes de dépôt distinctes. Lorsque vous dissociez les dépendances de base de données, des relations de clés étrangères peuvent exister entre les tables fractionnées. Cependant, une fois que vous avez complètement dissocié le service du monolithe, ces dépendances cessent d'exister et le service interagit exclusivement via des API ou des contrats RPC prédéfinis.
Transactions distribuées et échecs partiels
Une fois que vous avez isolé le service et que vous l'avez séparé du monolithe, une transaction locale dans le système monolithique d'origine devient une transaction distribuée entre plusieurs services. Dans la mise en œuvre monolithique, le processus de paiement suit la séquence illustrée dans le schéma suivant.
Figure 1 : Séquence du processus de paiement dans une mise en œuvre monolithique.
Dans la figure 1, lorsque l'application reçoit un bon de commande, le contrôleur de paiement appelle le service de paiement et le service de commande pour traiter le paiement et enregistrer la commande, respectivement. Si une étape échoue, la transaction de base de données peut faire l'objet d'un rollback. Prenons un exemple dans lequel la requête de commande est bien enregistrée dans la table des commandes, mais le paiement échoue. Dans ce scénario, l'intégralité de la transaction fait l'objet d'un rollback et l'entrée est supprimée de la table des commandes.
Une fois le paiement dissocié dans son propre service, le processus de paiement modifié ressemble au schéma suivant :
Figure 2. Une séquence de traitement survenant après le paiement est dissociée dans son propre service.
Dans la figure 2, la transaction implique désormais plusieurs services ainsi que les bases de données respectives de ces services. Il s'agit donc d'une transaction distribuée. À la réception d'une requête de commande, le contrôleur de paiement enregistre les détails de la commande dans sa base de données locale et appelle d'autres services pour finaliser la commande. Ces services, tels que le service de paiement, peuvent utiliser leur propre base de données locale pour stocker les détails de la commande.
Dans l'application monolithique, le système de base de données garantit que les transactions locales sont atomiques. Toutefois, par défaut, un système basé sur des microservices qui possède une base de données distincte pour chaque service ne dispose pas d'un coordinateur de transactions global couvrant les différentes bases de données. Comme les transactions ne sont pas coordonnées de manière centralisée, une défaillance lors du traitement d'un paiement n'entraîne pas l'annulation des modifications validées dans le service de commande. Par conséquent, le système peut présenter des incohérences.
Les modèles suivants sont généralement utilisés pour gérer les transactions distribuées :
- Protocole de validation en deux phases (2PC) : Faisant partie d'une famille de protocoles de consensus, le protocole 2PC coordonne le commit d'une transaction distribuée et garantit atomicité, cohérence, isolation et durabilité (ACID). Le protocole est divisé en phases de préparation et de commit. Une transaction n'est validée que si tous les participants ont voté pour elle. Si les participants ne parviennent pas à un consensus, l'intégralité de la transaction fait l'objet d'un rollback.
- Saga : le modèle Saga consiste à exécuter des transactions locales dans chaque microservice impliqué dans la transaction distribuée. Un événement est déclenché à la fin de chaque opération en fonction du résultat (réussite ou échec). Tous les microservices impliqués dans la transaction distribuée s'abonnent à ces événements. Si les microservices suivants reçoivent un événement de réussite, ils exécutent leur opération. En cas de défaillance, les microservices précédents mettent en œuvre des actions compensatoires pour annuler les modifications. Saga fournit une vue cohérente du système en garantissant que lorsque toutes les étapes sont terminées, soit toutes les opérations aboutissent, soit des actions compensatoires annulent toutes les opérations effectuées.
Nous recommandons Saga pour les transactions de longue durée. Dans une application basée sur des microservices, les appels interservices et la communication avec des systèmes tiers sont fréquents. Par conséquent, une conception favorisant la cohérence à terme est préférable : il faut réessayer pour les erreurs récupérables tout en exposant des événements compensatoires pour annuler les modifications en cas d'erreurs non-récupérables.
Il existe différentes manières de mettre en œuvre le modèle Saga. Par exemple, vous pouvez utiliser des moteurs de tâches et de workflows tels que Apache Airflow ou Apache Camel. Vous pouvez également écrire vos propres gestionnaires d'événements à l'aide de systèmes basés sur Kafka, RabbitMQ ou ActiveMQ.
L'application Online Boutique utilise le service de paiement pour orchestrer les services de paiement, d'expédition et de notification par e-mail. Le service de paiement gère également le workflow de l'entreprise et des commandes. Au lieu de créer votre propre moteur de workflow, vous pouvez utiliser un composant tiers tel que Zeebe. Zeebe propose un modèle basé sur interface utilisateur. Nous vous recommandons d'évaluer soigneusement les options disponibles pour l'orchestration des microservices en fonction des exigences de votre application. Ce choix est très important pour l'exécution et le scaling de vos microservices.
Application refactorisée
Pour activer les transactions distribuées dans l'application refactorisée, le service de paiement gère la communication entre le service de paiement, d'expédition et d'e-mail. Le workflow BPMN (Business Process Model and Notation) général utilise le flux suivant :
Figure 3. Workflow de commande qui garantit des transactions distribuées dans des microservices types.
Le schéma précédent représente le workflow suivant :
- Le service d'interface reçoit une requête de commande, puis effectue les opérations suivantes :
- Envoie les articles de la commande au service de panier d'achat. Le service de panier enregistre ensuite les détails de la commande (Redis).
- Redirige vers la page de paiement. Le service de paiement extrait les commandes du service de panier, définit l'état de la commande sur
Pending
et demande au client le paiement. - Confirme le paiement de l'utilisateur. Une fois la confirmation effectuée, le service de paiement demande au service de messagerie de générer un e-mail de confirmation et de l'envoyer au client.
- Le service de paiement traite ensuite la demande.
- Si la demande de paiement aboutit, le service de paiement met à jour l'état de la commande sur
Complete
. - Si la requête de paiement échoue, le service de paiement lance une transaction compensatoire.
- La demande de paiement est annulée.
- Le service de paiement définit l'état de la commande sur
Failed
.
- Si le service de paiement n'est pas disponible, la requête expire au bout de N secondes et le service de paiement lance une transaction compensatoire.
- Le service de paiement définit l'état de la commande sur
Failed
.
- Si la demande de paiement aboutit, le service de paiement met à jour l'état de la commande sur
Objectifs
- Déployer l'application monolithique Online Boutique sur Google Kubernetes Engine (GKE).
- Validez le processus de paiement monolithique.
- Déployer la version basée sur les microservices de l'application monolithique refactorisée
- Vérifier que le nouveau flux de paiement fonctionne.
- Vérifier que les transactions distribuées et les actions compensatoires fonctionnent correctement en cas d'échec.
Coûts
Dans ce document, vous utilisez les composants facturables suivants de Google Cloud :
Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût.
Une fois que vous aurez terminé de suivre ce document, vous pourrez éviter de continuer à payer des frais en supprimant les ressources créées. Pour en savoir plus, consultez la section Effectuer un nettoyage.
Avant de commencer
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
In the Google Cloud console, activate Cloud Shell.
At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.
Activez les API pour Compute Engine, Google Kubernetes Engine, Cloud SQL, Artifact Analysis et Container Registry :
gcloud services enable \ compute.googleapis.com \ sql-component.googleapis.com \ servicenetworking.googleapis.com\ container.googleapis.com \ containeranalysis.googleapis.com \ containerregistry.googleapis.com \ sqladmin.googleapis.com
Exportez les variables d'environnement suivantes :
export PROJECT=$(gcloud config get-value project) export CLUSTER=$PROJECT-gke export REGION="us-central1"
Déployer le monolithe e-commerce
Dans cette section, vous déployez l'application monolithique Online Boutique dans un cluster GKE. L'application utilise Cloud SQL comme base de données relationnelle. Le schéma suivant illustre l'architecture de l'application monolithique :
Figure 4. Un client se connecte à l'application dans un cluster GKE, et l'application se connecte à une base de données Cloud SQL.
Pour déployer l'application, procédez comme suit :
Clonez le dépôt GitHub.
git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
Remplacez l'espace réservé
PROJECT_ID
dans le fichier de manifeste des variables Terraform :cd monolith-to-microservices-example/setup && \ sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" terraform.tfvars
Exécutez les scripts Terraform pour terminer la configuration de l'infrastructure et la déployer. Pour en savoir plus sur Terraform, consultez la page Premiers pas avec Terraform sur Google Cloud :
terraform init && terraform apply -auto-approve
Le script Terraform crée les éléments suivants :
- Un réseau VPC nommé
PROJECT_ID-vpc
- Un cluster GKE nommé
PROJECT_ID-gke
- Une instance Cloud SQL nommée
PROJECT_ID-mysql
- Une base de données nommée
ecommerce
et utilisée par l'application - Un utilisateur
root
avec le mot de passe défini surpassword
- Une base de données nommée
Vous pouvez modifier le script Terraform pour générer automatiquement un mot de passe. Cette configuration utilise un exemple simplifié à ne pas utiliser en production.
Le provisionnement de l'infrastructure peut prendre jusqu'à 10 minutes. Une fois le script exécuté avec succès, le résultat ressemble à ceci :
... Apply complete! Resources: 8 added, 0 changed, 0 destroyed. Outputs: kubernetes_cluster_name = PROJECT_ID-gke sql_database_name = PROJECT_ID-mysql vpc_name = PROJECT_ID-vpc
- Un réseau VPC nommé
Connectez-vous au cluster et créez un espace de noms nommé
monolith
. Vous déployez l'application dans son propre espace de noms dans le cluster GKE :gcloud container clusters get-credentials $CLUSTER \ --region $REGION \ --project $PROJECT && \ kubectl create ns monolith
L'application exécutée sur GKE utilise des secrets Kubernetes pour accéder à la base de données Cloud SQL. Créez un secret qui utilise les identifiants utilisateur de la base de données :
kubectl create secret generic dbsecret \ --from-literal=username=root \ --from-literal=password=password -n monolith
Créez l'image "monolith" et importez-la dans Container Registry :
cd ~/monolith gcloud builds submit --tag gcr.io/$PROJECT_ID/ecomm
Mettez à jour la référence dans le fichier
deploy.yaml
vers l'image Docker nouvellement créée :cd ~/monolith sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" deploy.yaml
Remplacez les espaces réservés dans les fichiers de manifeste de déploiement, puis déployez l'application :
cd .. && \ DB_IP=$(gcloud sql instances describe $PROJECT-mysql | grep "ipAddress:" | tail -1 | awk -F ":" '{print $NF}') sed -i -e "s/\[DB_IP\]/$DB_IP/g" monolith/deploy.yaml kubectl apply -f monolith/deploy.yaml
Vérifiez l'état du déploiement :
kubectl rollout status deployment/ecomm -n monolith
Le résultat est semblable à ce qui suit.
Waiting for deployment "ecomm" rollout to finish: 0 of 1 updated replicas are available... deployment "ecomm" successfully rolled out
Obtenez l'adresse IP de l'application déployée :
kubectl get svc ecomm -n monolith \ -o jsonpath="{.status.loadBalancer.ingress[*].ip}" -w
Attendez que l'adresse IP de l'équilibreur de charge soit publiée. Pour quitter la commande, appuyez sur
Ctrl+C
. Notez l'adresse IP de l'équilibreur de charge, puis accédez à l'application à l'URLhttp://IP_ADDRESS
. Il peut s'écouler un certain temps avant que l'équilibreur de charge ne soit opérationnel et commence à transmettre du trafic.
Valider le flux de paiement monolithique
Dans cette section, vous allez créer une commande test afin de valider le flux de paiement.
- Accédez à l'URL que vous avez notée dans la section précédente,
http://IP_ADDRESS
. - Sur la page d'accueil de l'application qui s'affiche, sélectionnez un produit puis cliquez sur Ajouter au panier.
- Pour créer un achat test, cliquez sur Passer votre commande :
- Une fois le paiement effectué, la fenêtre de confirmation de commande s'affiche avec un ID de confirmation de commande.
Pour afficher les détails de la commande, connectez-vous à la base de données :
gcloud sql connect $PROJECT-mysql --user=root
Vous pouvez également utiliser n'importe quelle autre méthode compatible pour vous connecter à la base de données. Lorsque vous y êtes invité, saisissez le mot de passe en tant que
password
.Pour afficher les détails d'une commande enregistrée, exécutez la commande suivante :
select cart_id from ecommerce.cart;
La sortie ressemble à ceci :
+--------------------------------------+ | cart_id | +--------------------------------------+ | 7cb9ab11-d268-477f-bf4d-4913d64c5b27 | +--------------------------------------+
Déployer l'application e-commerce basée sur des microservices
Dans cette section, vous allez déployer l'application refactorisée. Ce document se concentre uniquement sur la dissociation des services d'interface et de paiement. Le document suivant de cette série, Traçage distribué dans une application à microservices, décrit d'autres services que vous pouvez dissocier de l'application monolithique (services de recommandation et de publicité). Le service de paiement gère les transactions distribuées entre les services d'interface et de paiement et est déployé en tant que service Kubernetes dans le cluster GKE, comme illustré dans le schéma suivant :
Figure 5. Le service de paiement orchestre les transactions entre les services de panier, de paiement et de messagerie.
Déployer les microservices
Dans cette section, vous utilisez l'infrastructure que vous avez provisionnée précédemment pour déployer des microservices dans leur propre espace de noms microservice
:
Assurez-vous d'avoir les ressources suivantes :
- Projet Google Cloud
- Environnement de shell avec
gcloud
,git
etkubectl
Dans Cloud Shell, clonez le dépôt de microservices :
git clone https://github.com/GoogleCloudPlatform/microservices-demo cd microservices-demo/
Définissez la région et le projet Google Cloud, et assurez-vous que l'API GKE est activée :
export PROJECT_ID=PROJECT_ID export REGION=us-central1 gcloud services enable container.googleapis.com \ --project=${PROJECT_ID}
Remplacez
PROJECT_ID
par l'ID de votre projet Google Cloud.Créez un cluster GKE et obtenez les identifiants associés :
gcloud container clusters create-auto online-boutique \ --project=${PROJECT_ID} --region=${REGION}
La création du cluster peut prendre quelques minutes.
Déployez des microservices sur le cluster :
kubectl apply -f ./release/kubernetes-manifests.yaml
Attendez que les pods soient prêts :
kubectl get pods
Après quelques minutes, les pods s'affichent avec l'état
Running
.Accédez à l'interface Web dans un navigateur à l'aide de son adresse IP externe :
kubectl get service frontend-external | awk '{print $4}'
Accédez à
http://EXTERNAL_IP
dans un navigateur Web pour accéder à votre instance de Boutique en ligne.
Valider le nouveau flux de paiement
- Pour vérifier le flux de traitement de paiement, sélectionnez un produit et passez une commande comme décrit dans la section précédente Valider le flux de paiement monolithique.
- Une fois le paiement effectué, la fenêtre de confirmation n'affiche pas d'ID de confirmation. Au lieu de cela, la fenêtre de confirmation vous invite à consulter votre messagerie pour obtenir plus de détails sur la confirmation.
Pour vérifier que la commande a bien été reçue, que le service de paiement a bien traité le paiement et que les détails de la commande ont bien été mis à jour, exécutez la commande suivante :
kubectl logs -f deploy/checkoutservice --tail=100
La sortie ressemble à ceci :
[...] {"message":"[PlaceOrder] user_id=\"98828e7a-b2b3-47ce-a663-c2b1019774a3\" user_currency=\"CAD\"","severity":"info","timestamp":"2023-08-10T04:19:20.498893921Z"} {"message":"payment went through (transaction_id: f0b4a592-026f-4b4a-9892-ce86d2711aed)","severity":"info","timestamp":"2023-08-10T04:19:20.528338189Z"} {"message":"order confirmation email sent to \"someone@example.com\"","severity":"info","timestamp":"2023-08-10T04:19:20.540275988Z"}
Pour quitter les journaux, appuyez sur
Ctrl+C
.Vérifiez que le paiement a bien été effectué :
kubectl logs -f deploy/paymentservice -n --tail=100
La sortie ressemble à ceci :
[...] {"severity":"info","time":1691641282208,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-charge","message":"Transaction processed: visa ending 0454 Amount: CAD119.30128260"} {"severity":"info","time":1691641300051,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-server","message":"PaymentService#Charge invoked with request {\"amount\":{\"currency_code\":\"USD\",\"units\":\"137\",\"nanos\":850000000},\"credit_card\":{\"credit_card_number\":\"4432-8015-6152-0454\",\"credit_card_cvv\":672,\"credit_card_expiration_year\":2039,\"credit_card_expiration_month\":1}}"}
Pour quitter les journaux, appuyez sur
Ctrl+C
.Vérifiez que l'e-mail de confirmation de commande est envoyé :
kubectl logs -f deploy/emailservice -n --tail=100
La sortie ressemble à ceci :
[...] {"timestamp": 1691642217.5026057, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email to kalani@examplepetstore.com has been received."}
Les messages de journal de chaque microservice indiquent que la transaction distribuée sur les services de paiement, de messagerie et de paiement a bien abouti.
Valider une action compensatoire dans une transaction distribuée
Cette section simule un scénario dans lequel un client passe une commande et le service de paiement tombe en panne.
Pour simuler l'indisponibilité du service, supprimez le déploiement et le service de paiement :
kubectl delete deploy paymentservice && \ kubectl delete svc paymentservice
Accédez de nouveau à l'application et suivez le flux de paiement. Dans cet exemple, si le service de paiement ne répond pas, la requête expire et une action compensatoire est déclenchée.
Dans l'interface utilisateur, cliquez sur le bouton Commander. Le résultat se présente comme suit :
HTTP Status: 500 Internal Server Error rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host" failed to complete the order main.(*frontendServer).placeOrderHandler /src/handlers.go:360
Consultez les journaux du service d'interface :
kubectl logs -f deploy/frontend --tail=100
Le résultat se présente comme suit :
[...] {"error":"failed to complete the order: rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host\"","http.req.id":"0a4cb058-ee9b-470a-9bb1-3a965636022e","http.req.method":"POST","http.req.path":"/cart/checkout","message":"request error","session":"96c94881-a435-4490-9801-c788dc400cc1","severity":"error","timestamp":"2023-08-11T18:25:47.127294259Z"}
Consultez les journaux du service de paiement :
kubectl logs -f deploy/frontend --tail=100
Le résultat se présente comme suit :
[...] {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T18:25:46.947901041Z"} {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T19:54:21.796343643Z"}
Notez qu'aucun appel ultérieur au service de messagerie n'est effectué pour envoyer une notification. Il n'y a pas de journal de transactions, comme
payment went through (transaction_id: 06f0083f-fa47-4d91-8258-6d61edfab1ca)
.Consultez les journaux du service de messagerie :
kubectl logs -f deploy/emailservice --tail=100
Notez qu'aucune entrée de journal n'est créée pour l'échec de la transaction sur le service de messagerie.
En tant qu'orchestrateur, si un appel de service échoue, le service de paiement renvoie un état d'erreur et ferme le processus de paiement.
Effectuer un nettoyage
Si vous envisagez de suivre les étapes décrites du document suivant de cette série, traçage distribué dans une application à microservices, vous pouvez réutiliser le projet et les ressources plutôt que de les supprimer.
Supprimer le projet
- In the Google Cloud console, go to the Manage resources page.
- In the project list, select the project that you want to delete, and then click Delete.
- In the dialog, type the project ID, and then click Shut down to delete the project.
Supprimer les ressources
Si vous souhaitez conserver le projet Google Cloud que vous avez utilisé dans ce document, supprimez les ressources individuelles.
Dans Cloud Shell, exécutez la commande suivante :
cd setup && terraform destroy -auto-approve
Pour supprimer le cluster de microservices à l'aide de la Google Cloud CLI, exécutez la commande suivante :
gcloud container clusters delete online-boutique \ --location $REGION
Étapes suivantes
- Découvrez l'architecture de microservices.
- Lisez le premier document de cette série pour en savoir plus sur les microservices, leurs avantages, leurs défis et leurs cas d'utilisation.
- Lisez le deuxième document de cette série pour en savoir plus sur les stratégies de refactorisation d'application en microservices.
- Lisez le dernier document de cette série pour en savoir plus sur le traçage distribué des requêtes entre microservices.