Traçage distribué dans une application à microservices

Date d'actualisation : 24-10-2023

Ce document est le quatriè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.

  1. Présentation des microservices
  2. Refactoriser un monolithe en microservices
  3. Communication interservices dans une configuration à microservices
  4. Traçage distribué dans une application à microservices (le présent document)

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.

Dans un système distribué, il est important de savoir comment une requête circule d'un service à un autre et combien de temps il faut pour exécuter une tâche dans chaque service. Prenons l'exemple d'application basée sur des microservices Online Boutique que vous avez déployée dans le document précédent, Refactoriser un monolithe en microservices. L'application est composée de plusieurs services. Par exemple, la capture d'écran suivante montre la page des informations détaillées sur le produit, qui extrait des informations des services d'interface ("frontend"), de recommandation ("recommendation") et de publicité ("ads").

Page des informations détaillées sur le produit.

Pour afficher la page des informations détaillées sur le produit, le service d'interface communique avec le service de recommandation ainsi qu'avec le service de publicité, comme illustré dans le schéma suivant :

Le service d'interface communique avec le service de recommandation, le catalogue de produits et le service de publicité.

Figure 1 : Services écrits dans des langages différents.

Dans la figure 1, le service d'interface est écrit en Go. Le service de recommandation, écrit en Python, utilise gRPC pour communiquer avec le service d'interface. Le service de publicité, écrit en Java, utilise également gRPC pour communiquer avec le service d'interface. Outre gRPC, la méthode de communication inter-service peut également utiliser REST HTTP.

Lorsque vous créez un tel système distribué, vos outils d'observabilité doivent pouvoir vous fournir les informations suivantes :

  • Les services par lesquels une requête est passée.
  • Les emplacements spécifiques où des retards se sont produits si une requête était lente.
  • Les emplacements spécifiques où une erreur s'est produite si la requête a échoué.
  • En quoi l'exécution de la requête était-elle différente du comportement normal du système ?
  • Les différences d'exécution de la requête sont-elles liées aux performances (des appels de service ont-ils mis plus longtemps que d'habitude à aboutir) ?

Objectifs

  • Utiliser les fichiers manifestes Kustomize pour configurer l'infrastructure.
  • Déployer l'exemple d'application Online Boutique sur Google Kubernetes Engine (GKE).
  • Utiliser Cloud Trace pour examiner le parcours d'un utilisateur dans l'exemple d'application.

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. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Une fois que vous aurez terminé de suivre ce document, vous pourrez éviter de continuer à payer des frais en supprimant les ressources créées. Consultez la section Effectuer un nettoyage pour en savoir plus.

Avant de commencer

Si vous avez déjà configuré un projet en suivant le document précédent de cette série (Communication entre services dans une configuration à microservices), vous pouvez le réutiliser. Appliquez les étapes ci-dessous pour activer des API supplémentaires et définir des variables d'environnement.

  1. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder au sélecteur de projet

  2. Vérifiez que la facturation est activée pour votre projet Google Cloud.

  3. Dans la console Google Cloud, activez Cloud Shell.

    Activer Cloud Shell

  4. Activez les API pour Compute Engine, GKE, Cloud SQL, Artifact Analysis, Trace 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
    

Traçage distribué

Le traçage distribué associe des métadonnées contextuelles à chaque requête et garantit le partage des métadonnées entre les requêtes. Vous pouvez utiliser des points de trace pour instrumenter le traçage distribué. Par exemple, vous pouvez instrumenter vos services (interface, recommandation et publicité) avec deux points de trace pour gérer une requête client visant à afficher les détails d'un produit : un point de trace pour envoyer la requête et un autre point de trace pour recevoir la réponse. Le schéma suivant illustre le fonctionnement de cette instrumentation de point de trace :

Instrumentation de point de trace comportant deux points de trace.

Figure 2. Chaque appel interservice comprend deux points de trace constitués d'une paire requête-réponse.

Pour que les points de trace comprennent la requête à exécuter lors de l'appel du service, le service d'origine transmet un ID de trace tout au long du flux d'exécution. Le processus qui transmet l'ID de trace est appelé propagation de métadonnées ou propagation de contexte distribué. La propagation de contexte transfère les métadonnées via des appels réseau lorsque les services d'une application distribuée communiquent entre eux lors de l'exécution d'une requête donnée. Le schéma suivant illustre la propagation des métadonnées :

Propagation des métadonnées transmettant l'ID de trace.

Figure 3. Les métadonnées de trace sont transmises entre les services. Les métadonnées incluent des informations telles que le service appelant et l'horodatage.

Dans l'exemple Online Boutique, une trace commence lorsqu'un utilisateur envoie une requête initiale visant à récupérer les informations détaillées sur un produit. Un nouvel ID de trace est généré, et chaque requête successive est décorée d'en-têtes contenant des métadonnées contextuelles remontant jusqu'à la requête d'origine.

Chaque opération individuelle appelée dans le cadre du traitement de la requête de l'utilisateur final est appelée un délai ("span"). Le service d'origine marque chaque délai avec son propre ID unique ainsi qu'avec l'ID de trace du délai parent. Le schéma suivant représente une visualisation de trace sous la forme d'un diagramme de Gantt :

Opérations individuelles marquées en tant que délais.

Figure 4. Un délai parent inclut le temps de réponse des délais enfants.

La figure 4 montre une arborescence de traces dans laquelle le service d'interface appelle le service de recommandations et le service de publicité. Le service d'interface est le délai parent, qui décrit le temps de réponse observé par l'utilisateur final. Les délais enfants indiquent comment les services de recommandation et de publicité sont appelés et répondent, y compris les informations de temps de réponse.

Un maillage de services tel que Istio permet un traçage distribué du trafic de service à service sans nécessiter d'instrumentation dédiée. Toutefois, vous aurez peut-être besoin de mieux contrôler les traces dans certains cas, ou de tracer du code qui ne s'exécute pas dans un maillage de services.

Le présent document utilise OpenTelemetry pour permettre l'instrumentation des applications distribuées à microservices afin de collecter des traces et des métriques. OpenTelemetry vous permet de collecter des métriques et des traces, puis de les exporter vers des backends tels que Prometheus, Cloud Monitoring, Datadog, Graphite, Zipkin et Jaeger.

Instrumentation à l'aide d'OpenTelemetry

Les sections suivantes expliquent comment utiliser la propagation de contexte afin d'ajouter les délais de plusieurs requêtes à une même trace parente.

Cet exemple utilise les bibliothèques OpenTelemetry JavaScript, Python et Go pour instrumenter l'implémentation de trace pour les services de paiement, de recommandation et d'interface. Selon le niveau de verbosité de l'instrumentation, les données de traçage peuvent avoir une incidence sur le coût du projet (facturation Cloud Trace). Pour limiter cette répercussion sur le coût, la plupart des systèmes de traçage utilisent différentes formes d'échantillonnage afin de ne capturer qu'un certain pourcentage des traces observées. Dans vos environnements de production, votre organisation peut avoir certaines raisons qui justifient à la fois ce qu'elle veut échantillonner et pourquoi. Vous pouvez souhaiter personnaliser votre stratégie d'échantillonnage en vous basant sur la gestion des coûts, en vous concentrant sur les traces intéressantes ou en filtrant le bruit. Pour en savoir plus sur l'échantillonnage, consultez la page Échantillonnage d'OpenTelemetry.

Ce document utilise Trace pour visualiser les traces distribuées. Vous utilisez un exportateur OpenTelemetry pour envoyer les traces vers Trace.

Enregistrer des exportateurs de traces

Cette section explique comment enregistrer l'exportateur de traces dans chaque service en ajoutant des lignes au code du microservice.

Pour le service d'interface (écrit en Go), l'exemple de code suivant permet d'enregistrer l'exportateur :

[...]
exporter, err := otlptracegrpc.New(
        ctx,
        otlptracegrpc.WithGRPCConn(svc.collectorConn))
    if err != nil {
        log.Warnf("warn: Failed to create trace exporter: %v", err)
    }
tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithSampler(sdktrace.AlwaysSample()))
    otel.SetTracerProvider(tp)

Pour le service de recommandation (écrit en Python), l'exemple de code suivant permet d'enregistrer l'exportateur :

if os.environ["ENABLE_TRACING"] == "1":
    trace.set_tracer_provider(TracerProvider())
    otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317")
    trace.get_tracer_provider().add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(
            endpoint = otel_endpoint,
            insecure = True
            )
        )
    )

Pour le service de paiement (écrit en JavaScript), l'exemple de code suivant permet d'enregistrer l'exportateur :

provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({url: collectorUrl})));
provider.register();

Configurer la propagation de contexte

Le système de traçage doit respecter une spécification de contexte de trace qui définit le format permettant de propager le contexte de traçage entre les services. Les exemples de format de propagation incluent le format B3 de Zipkin et le format X-Google-Cloud-Trace.

OpenTelemetry propage le contexte à l'aide de l'outil de propagation TextMapPropagator global. Cet exemple utilise l'outil de propagation de contexte de Trace, qui utilise le format traceparent W3C. Les bibliothèques d'instrumentation, telles que les bibliothèques HTTP et gRPC d'OpenTelemetry, utilisent l'outil de propagation global pour ajouter le contexte de trace en tant que métadonnées aux requêtes HTTP ou gRPC. Pour que la propagation de contexte réussisse, le client et le serveur doivent utiliser le même format de propagation.

Propagation de contexte via HTTP

Le service d'interface injecte un contexte de trace dans les en-têtes de requête HTTP. Les services de backend extraient le contexte de trace. L'exemple de code suivant montre comment le service d'interface est instrumenté pour configurer le contexte de trace :

otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))

if os.Getenv("ENABLE_TRACING") == "1" {
    log.Info("Tracing enabled.")
    initTracing(log, ctx, svc)
} else {
    log.Info("Tracing disabled.")
}

...

var handler http.Handler = r
handler = &logHandler{log: log, next: handler}     // add logging
handler = ensureSessionID(handler)                 // add session ID
handler = otelhttp.NewHandler(handler, "frontend") // add OpenTelemetry tracing

Propagation du contexte sur gRPC

Considérez le flux dans lequel le service de paiement passe la commande en fonction du produit sélectionné par un utilisateur. Ces services communiquent via gRPC.

L'exemple de code suivant utilise un intercepteur d'appels gRPC qui intercepte les appels sortants et injecte le contexte de trace :

var srv *grpc.Server

// Propagate trace context always
otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))
srv = grpc.NewServer(
    grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
    grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)

Après avoir reçu la requête, le service de paiement ou de catalogue de produits (ListProducts) extrait le contexte des en-têtes de requête et utilise les métadonnées de la trace parente pour générer un segment enfant.

Les sections suivantes expliquent comment configurer et vérifier le traçage distribué pour l'exemple d'application Online Boutique.

Déployer l'application

Si vous avez déjà une application en cours d'exécution car vous avez suivi le document précédent de cette série, Communication entre services dans une configuration à microservices, vous pouvez passer à la section suivante, Examiner les traces. Sinon, procédez comme suit pour déployer l'exemple Online Boutique :

  1. Pour configurer l'infrastructure, dans Cloud Shell, clonez le dépôt GitHub :

    git clone https://github.com/GoogleCloudPlatform/microservices-demo.git
    
  2. Pour le nouveau déploiement, réinitialisez les variables d'environnement :

    PROJECT_ID=PROJECT_ID
    REGION=us-central1
    GSA_NAME=microservices-sa
    GSA_EMAIL=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com
    

    Remplacez les éléments suivants :

    • PROJECT_ID : identifiant pour votre ID de projet.
  3. Facultatif : créez un nouveau cluster ou réutilisez un cluster existant s'il existe :

    gcloud container clusters create-auto online-boutique --project=${PROJECT_ID}
      --region=${REGION}
    
  4. Créez un compte de service Google :

    gcloud iam service-accounts create $GSA_NAME \
      --project=$PROJECT_ID
    
  5. Activer les API :

    gcloud services enable \
    monitoring.googleapis.com \
    cloudtrace.googleapis.com \
    cloudprofiler.googleapis.com \
      --project ${PROJECT_ID}
    
  6. Attribuez les rôles requis pour le traçage cloud au GSA :

    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudtrace.agent
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/monitoring.metricWriter
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudprofiler.agent
    
    gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]"
    
  7. Annotez votre compte de service Kubernetes (default/default pour l'espace de noms par défaut) pour utiliser le compte de service Google IAM :

    kubectl annotate serviceaccount default \
        iam.gke.io/gcp-service-account=${GSA_EMAIL}
    
  8. Activez la configuration Cloud Operations pour GKE, qui permet le traçage :

    cd ~/microservices-demo/kustomize && \
    kustomize edit add component components/google-cloud-operations
    
  9. Le fichier kustomize/kustomization.yaml sera mis à jour, ce qui pourrait ressembler à ceci :

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    resources:
    - base
    components:
    - components/google-cloud-operations
    [...]
    
  10. Déployez les microservices :

    kubectl apply -k .
    
  11. Vérifiez l'état du déploiement :

    kubectl rollout status deployment/frontend
    kubectl rollout status deployment/paymentservice
    kubectl rollout status deployment/recommendationservice
    kubectl rollout status deployment/adservice
    

    Le résultat de chaque commande ressemble à ceci :

    Waiting for deployment "" rollout to finish: 0 of 1 updated replicas are available...
    deployment "" successfully rolled out
    
  12. Obtenez l'adresse IP de l'application déployée :

    kubectl get service frontend-external | awk '{print $4}'
    

    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'URL http://IP_ADDRESS. Il peut s'écouler un certain temps avant que l'équilibreur de charge ne soit opérationnel et commence à transmettre du trafic.

Examiner des traces à l'aide de Cloud Trace

Le parcours d'achat d'un utilisateur dans l'application Online Boutique suit le flux suivant :

  • L'utilisateur voit un catalogue de produits sur la page de destination.
  • Pour effectuer un achat, il clique sur Acheter.
  • L'utilisateur est redirigé vers une page contenant des informations détaillées sur le produit où il ajoute l'article à son panier.
  • L'utilisateur est redirigé vers une page de paiement sur laquelle il peut effectuer le paiement afin de finaliser la commande.

Imaginons que vous deviez résoudre des problèmes de temps de réponse élevés lors du chargement de la page des informations détaillées sur le produit. Comme décrit précédemment, la page des informations détaillées sur le produit est composée de plusieurs microservices. Pour déterminer où et pourquoi la latence augmente, vous pouvez afficher des graphiques de traçage distribués afin d'examiner les performances de l'ensemble de la requête sur les différents services.

Pour examiner les graphiques de traçage distribués, procédez comme suit :

  1. Accédez à l'application, puis cliquez sur un produit. La page des informations détaillées sur le produit s'affiche.
  2. Dans la console Google Cloud, accédez à la page Liste de traces et examinez la chronologie.
  3. Pour afficher les résultats de trace distribuée, cliquez sur Interface dans la colonne "URI".
  4. L'affichage en cascade de traces affiche les délais associés à l'URI :

    Affichage en cascade des traces incluant les délais.

    Dans la capture d'écran précédente, la trace d'un produit contient les segments suivants :

    • Le segment Frontend (Interface) capture la latence de bout en bout (150,349 ms) que le client observe lors du chargement de la page des informations détaillées sur le produit.
    • Le segment Recommendation Service (Service de recommandation) capture la latence des appels backend pour la récupération des recommandations (4,246 ms) liées au produit.
    • Le segment Ad Service (Service de publicité) capture la latence des appels backend pour la récupération des publicités (4,511 ms) pertinentes pour la page produit.

Pour résoudre les problèmes de temps de réponse élevés, vous pouvez examiner des insights incluant des graphiques de répartition de la latence pour les requêtes aberrantes lorsque les dépendances du service ne respectent pas leurs objectifs de niveau de service (SLO). Vous pouvez également utiliser Cloud Trace pour obtenir des informations sur les performances et créer des rapports d'analyse à partir des données échantillonnées.

Dépannage

Si les traces dans la gestion des performances des applications n'apparaissent pas, vérifiez s'il existe une erreur d'autorisation refusée dans l'explorateur de journaux. L'autorisation refusée se produit lorsque le compte de service n'a pas accès à l'exportation des traces. Examinez les étapes qui concernent l'attribution des rôles requis pour Cloud Trace et veillez à annoter le compte de service avec l'espace de noms approprié. Ensuite, redémarrez opentelemetrycollector :

```
kubectl rollout restart deployment opentelemetrycollector
```

Effectuer un nettoyage

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez les ressources individuelles.

Supprimer le projet

  1. Dans la console Google Cloud, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

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, supprimez les ressources :

    gcloud container clusters delete online-boutique --project=${PROJECT_ID} --region=${REGION}
    

Étape suivante