Améliorez les performances de votre application en Go (partie 2: profileur)

1. Introduction

e0509e8a07ad5537.png

Dernière mise à jour:14/07/2022

Observabilité de l'application

Observabilité et Profileur continu

L'observabilité est le terme utilisé pour décrire un attribut d'un système. Un système avec observabilité permet aux équipes de déboguer activement leur système. Dans ce contexte, les trois piliers de l'observabilité : Les journaux, les métriques et les traces constituent l'instrumentation fondamentale pour que le système acquière l'observabilité.

Outre les trois piliers de l'observabilité, le profilage continu est un autre composant clé de l'observabilité et élargit la base d'utilisateurs du secteur. Cloud Profiler est l'un des créateurs de cette plate-forme. Il offre une interface simple pour afficher le détail des métriques de performances dans les piles d'appels de l'application.

Cet atelier de programmation constitue la deuxième partie de la série et aborde l'instrumentation d'un agent de profilage continu. La première partie traite du traçage distribué avec OpenTelemetry et Cloud Trace. Dans la première partie, vous découvrirez comment mieux identifier le goulot d'étranglement des microservices.

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez instrumenter un agent de profilage continu dans le service de serveur de "Shakespeare application". (également appelé "Shakesapp") qui s'exécute sur un cluster Google Kubernetes Engine. L'architecture de Shakesapp est décrite ci-dessous:

44e243182ced442f.png

  • Loadgen envoie une chaîne de requête au client en HTTP
  • Les clients transmettent la requête de loadgen au serveur dans gRPC.
  • Le serveur accepte la requête du client, extrait tous les travaux de Shakespare au format texte à partir de Google Cloud Storage, recherche les lignes contenant la requête et renvoie le numéro de la ligne qui correspond au client

Dans la première partie, vous avez constaté que le goulot d'étranglement existe quelque part dans le service de serveur, mais vous n'avez pas pu en identifier la cause exacte.

Points abordés

  • Intégrer l'agent Profiler
  • Examiner le goulot d'étranglement sur Cloud Profiler

Cet atelier de programmation explique comment instrumenter un agent de profilage continu dans votre application.

Prérequis

  • Des connaissances de base sur Go
  • Des connaissances de base sur Kubernetes

2. Préparation

Configuration de l'environnement au rythme de chacun

Si vous ne possédez pas encore de compte Google (Gmail ou Google Apps), vous devez en créer un. Connectez-vous à la console Google Cloud Platform (console.cloud.google.com) et créez un projet.

Si vous avez déjà un projet, cliquez sur le menu déroulant de sélection du projet dans l'angle supérieur gauche de la console :

7a32e5469db69e9.png

Cliquez ensuite sur le bouton "NEW PROJECT" (NOUVEAU PROJET) dans la boîte de dialogue qui s'affiche pour créer un projet :

7136b3ee36ebaf89.png

Si vous n'avez pas encore de projet, une boîte de dialogue semblable à celle-ci apparaîtra pour vous permettre d'en créer un :

870a3cbd6541ee86.png

La boîte de dialogue de création de projet suivante vous permet de saisir les détails de votre nouveau projet :

affdc444517ba805.png

Notez l'ID du projet. Il s'agit d'un nom unique pour tous les projets Google Cloud, ce qui implique que le nom ci-dessus n'est plus disponible pour vous… Désolé ! Il sera désigné par le nom "PROJECT_ID" dans la suite de cet atelier de programmation.

Ensuite, si vous ne l'avez pas déjà fait, vous devez activer la facturation dans la Play Console pour pouvoir utiliser les ressources Google Cloud et activer l'API Cloud Trace.

15d0ef27a8fbab27.png

Suivre cet atelier de programmation ne devrait pas vous coûter plus d'un euro. Cependant, cela peut s'avérer plus coûteux si vous décidez d'utiliser davantage de ressources ou si vous n'interrompez pas les ressources (voir la section "Effectuer un nettoyage" à la fin du présent document). Les tarifs de Google Cloud Trace, de Google Kubernetes Engine et de Google Artifact Registry sont indiqués dans la documentation officielle.

Les nouveaux utilisateurs de Google Cloud Platform peuvent bénéficier d'un Essai gratuit avec 300 $ de crédits afin de suivre gratuitement le présent atelier.

Configuration de Google Cloud Shell

Bien que vous puissiez utiliser Google Cloud et Google Cloud Trace à distance depuis votre ordinateur portable, dans cet atelier de programmation, nous allons utiliser Google Cloud Shell, un environnement de ligne de commande exécuté dans le cloud.

Cette machine virtuelle basée sur Debian contient tous les outils de développement dont vous aurez besoin. Elle intègre un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Cela signifie que tout ce dont vous avez besoin pour cet atelier de programmation est un navigateur (oui, tout fonctionne sur un Chromebook).

Pour activer Cloud Shell depuis la console Cloud, il vous suffit de cliquer sur Activer Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A. Le provisionnement et la connexion à l'environnement ne devraient pas prendre plus de quelques minutes.

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Capture d'écran du 2017-06-14 à 10.13.43 PM.png

Une fois connecté à Cloud Shell, vous êtes normalement déjà authentifié et le projet PROJECT_ID est sélectionné :

gcloud auth list

Résultat de la commande

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Résultat de la commande

[core]
project = <PROJECT_ID>

Si, pour une raison quelconque, le projet n'est pas défini, exécutez simplement la commande suivante :

gcloud config set project <PROJECT_ID>

Vous recherchez votre PROJECT_ID ? Vérifiez l'ID que vous avez utilisé pendant les étapes de configuration ou recherchez-le dans le tableau de bord Cloud Console :

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Par défaut, Cloud Shell définit certaines variables d'environnement qui pourront s'avérer utiles pour exécuter certaines commandes dans le futur.

echo $GOOGLE_CLOUD_PROJECT

Résultat de la commande

<PROJECT_ID>

Pour finir, définissez la configuration du projet et de la zone par défaut :

gcloud config set compute/zone us-central1-f

Vous pouvez choisir parmi différentes zones. Pour en savoir plus, consultez la page Régions et zones.

Configurer la langue

Dans cet atelier de programmation, nous utilisons Go pour l'ensemble du code source. Exécutez la commande suivante dans Cloud Shell et vérifiez que la version de Go est 1.17+

go version

Résultat de la commande

go version go1.18.3 linux/amd64

Configurer un cluster Google Kubernetes

Dans cet atelier de programmation, vous allez exécuter un cluster de microservices sur Google Kubernetes Engine (GKE). Le processus de cet atelier de programmation est le suivant:

  1. Télécharger le projet de référence dans Cloud Shell
  2. Construire des microservices en conteneurs
  3. Importer des conteneurs sur Google Artifact Registry (GAR)
  4. Déployer des conteneurs sur GKE
  5. Modifier le code source des services pour l'instrumentation de traces
  6. Accéder à l'étape 2

Activer Kubernetes Engine

Tout d'abord, nous avons configuré un cluster Kubernetes dans lequel Shakesapp s'exécute sur GKE. Nous devons donc activer GKE. Accédez au menu "Kubernetes Engine". et appuyez sur le bouton ACTIVER.

548cfd95bc6d344d.png

Vous êtes maintenant prêt à créer un cluster Kubernetes.

Créer un cluster Kubernetes

Dans Cloud Shell, exécutez la commande suivante pour créer un cluster Kubernetes. Veuillez confirmer que la valeur de la zone se trouve sous la région que vous utiliserez pour créer le dépôt Artifact Registry. Modifiez la valeur de zone us-central1-f si la région de votre dépôt ne couvre pas la zone.

gcloud container clusters create otel-trace-codelab2 \
--zone us-central1-f \
--release-channel rapid \
--preemptible \
--enable-autoscaling \
--max-nodes 8 \
--no-enable-ip-alias \
--scopes cloud-platform

Résultat de la commande

Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done.     
Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403
kubeconfig entry generated for otel-trace-codelab2.
NAME: otel-trace-codelab2
LOCATION: us-central1-f
MASTER_VERSION: 1.23.6-gke.1501
MASTER_IP: 104.154.76.89
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.23.6-gke.1501
NUM_NODES: 3
STATUS: RUNNING

Configurer Artifact Registry et Skaffold

Nous disposons maintenant d'un cluster Kubernetes prêt pour le déploiement. Nous allons ensuite préparer un registre de conteneurs pour l'envoi et le déploiement de conteneurs. Pour ces étapes, nous devons configurer Artifact Registry (GAR) et Skaffold pour l'utiliser.

Configurer Artifact Registry

Accédez au menu "Artifact Registry" et appuyez sur le bouton ACTIVER.

45e384b87f7cf0db.png

Après quelques instants, le navigateur de dépôt de GAR s'affiche. Cliquez sur "CRÉER UN DÉPÔT" et saisissez le nom du dépôt.

d6a70f4cb4ebcbe3.png

Dans cet atelier de programmation, je nomme le nouveau dépôt trace-codelab. Le format de l'artefact est "Docker" et le type d'emplacement est "Région". Choisissez une région proche de celle que vous avez définie pour la zone Google Compute Engine par défaut. Dans cet exemple, nous choisissons "us-central1-f". ci-dessus. C'est pourquoi nous choisissons ici "us-central1 (Iowa)". Cliquez ensuite sur le bouton CRÉER .

9c2d1ce65258ef70.png

"trace-codelab" s'affiche dans le navigateur de dépôt.

7a3c1f47346bea15.png

Nous reviendrons ici plus tard pour vérifier le chemin d'accès au registre.

Configuration de Skaffold

Skaffold est un outil pratique lorsque vous créez des microservices exécutés sur Kubernetes. Il gère le workflow de création, de transfert et de déploiement de conteneurs d'applications avec un petit ensemble de commandes. Skaffold utilise par défaut le registre Docker comme registre de conteneurs. Vous devez donc configurer Skaffold pour qu'il reconnaisse GAR lors du transfert de conteneurs.

Ouvrez à nouveau Cloud Shell et vérifiez si Skaffold est installé. (Cloud Shell installe Skaffold dans l'environnement par défaut.) Exécutez la commande suivante et affichez la version Skaffold.

skaffold version

Résultat de la commande

v1.38.0

Vous pouvez maintenant enregistrer le dépôt par défaut que Skaffold utilisera. Pour obtenir le chemin d'accès au registre, accédez au tableau de bord Artifact Registry, puis cliquez sur le nom du dépôt que vous venez de configurer.

7a3c1f47346bea15.png

Des fils d'Ariane s'affichent alors en haut de la page. Cliquez sur l'icône e157b1359c3edc06.png pour copier le chemin d'accès au registre dans le presse-papiers.

e0f2ae2144880b8b.png

Lorsque vous cliquez sur le bouton "Copier", la boîte de dialogue qui s'affiche en bas du navigateur affiche le message suivant:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; a été copié(e)

Revenez à Cloud Shell. Exécutez la commande skaffold config set default-repo avec la valeur que vous venez de copier depuis le tableau de bord.

skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab

Résultat de la commande

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

Vous devez également appliquer la configuration Docker au registre. Exécutez la commande suivante :

gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

Résultat de la commande

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

Vous pouvez maintenant passer à l'étape suivante, qui consiste à configurer un conteneur Kubernetes sur GKE.

Résumé

Au cours de cette étape, vous allez configurer l'environnement de votre atelier de programmation:

  • Configurer Cloud Shell
  • Créer un dépôt Artifact Registry pour le registre de conteneurs
  • Configurer Skaffold pour utiliser Container Registry
  • Création d'un cluster Kubernetes dans lequel les microservices de l'atelier de programmation s'exécutent

Étape suivante

À l'étape suivante, vous allez instrumenter l'agent de profileur continu dans le service du serveur.

3. Créer, transférer et déployer les microservices

Télécharger les supports de l'atelier de programmation

À l'étape précédente, nous avons rempli toutes les conditions préalables pour cet atelier de programmation. Vous êtes maintenant prêt à exécuter des microservices entiers sur ces microservices. Les supports de l'atelier de programmation étant hébergés sur GitHub, téléchargez-les dans l'environnement Cloud Shell à l'aide de la commande Git suivante.

cd ~
git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git
cd opentelemetry-trace-codelab-go

La structure de répertoires du projet est la suivante:

.
├── README.md
├── step0
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step1
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step2
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step3
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step4
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step5
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
└── step6
    ├── manifests
    ├── proto
    ├── skaffold.yaml
    └── src
  • manifestes: fichiers manifestes Kubernetes
  • proto: définition du proto pour la communication entre le client et le serveur
  • src: répertoires du code source de chaque service
  • skaffold.yaml: fichier de configuration de Skaffold

Dans cet atelier de programmation, vous allez mettre à jour le code source situé dans le dossier step4. Vous pouvez également vous reporter au code source dans les dossiers step[1-6] pour voir les modifications depuis le début. (La partie 1 couvre l'étape 0 à l'étape 4, et la partie 2 couvre les étapes 5 et 6.)

Exécuter la commande Skaffold

Enfin, vous êtes prêt à créer, transférer et déployer tout le contenu sur le cluster Kubernetes que vous venez de créer. On dirait qu'il comporte plusieurs étapes, mais Skaffold fait tout pour vous. Essayons avec la commande suivante:

cd step4
skaffold dev

Dès l'exécution de la commande, la sortie de journal de docker build s'affiche, et vous pouvez confirmer qu'ils ont bien été transférés vers le registre.

Résultat de la commande

...
---> Running in c39b3ea8692b
 ---> 90932a583ab6
Successfully built 90932a583ab6
Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1
The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice]
cc8f5a05df4a: Preparing
5bf719419ee2: Preparing
2901929ad341: Preparing
88d9943798ba: Preparing
b0fdf826a39a: Preparing
3c9c1e0b1647: Preparing
f3427ce9393d: Preparing
14a1ca976738: Preparing
f3427ce9393d: Waiting
14a1ca976738: Waiting
3c9c1e0b1647: Waiting
b0fdf826a39a: Layer already exists
88d9943798ba: Layer already exists
f3427ce9393d: Layer already exists
3c9c1e0b1647: Layer already exists
14a1ca976738: Layer already exists
2901929ad341: Pushed
5bf719419ee2: Pushed
cc8f5a05df4a: Pushed
step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001

Une fois tous les conteneurs de service transférés, les déploiements Kubernetes démarrent automatiquement.

Résultat de la commande

sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997
Tags used in deployment:
 - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe
 - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8
 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a
Starting deploy...
 - deployment.apps/clientservice created
 - service/clientservice created
 - deployment.apps/loadgen created
 - deployment.apps/serverservice created
 - service/serverservice created

Après le déploiement, vous verrez les journaux de l'application émis vers stdout dans chaque conteneur, comme suit:

Résultat de la commande

[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:16 {"match_count":3040}
[loadgen] 2022/07/14 06:33:16 query 'love': matched 3040
[client] 2022/07/14 06:33:19 {"match_count":463}
[loadgen] 2022/07/14 06:33:19 query 'tear': matched 463
[loadgen] 2022/07/14 06:33:20 query 'world': matched 728
[client] 2022/07/14 06:33:20 {"match_count":728}
[client] 2022/07/14 06:33:22 {"match_count":463}
[loadgen] 2022/07/14 06:33:22 query 'tear': matched 463

Notez qu'à ce stade, vous souhaitez consulter tous les messages provenant du serveur. Vous êtes maintenant prêt à instrumenter votre application avec OpenTelemetry pour le traçage distribué des services.

Avant de commencer à instrumenter le service, veuillez arrêter votre cluster avec Ctrl-C.

Résultat de la commande

...
[client] 2022/07/14 06:34:57 {"match_count":1}
[loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1
^CCleaning up...
 - W0714 06:34:58.464305   28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
 - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Résumé

Au cours de cette étape, vous avez préparé le matériel de l'atelier de programmation dans votre environnement et confirmé que Skaffold s'exécute comme prévu.

Étape suivante

À l'étape suivante, vous allez modifier le code source du service loadgen pour instrumenter les informations de trace.

4. Instrumentation de l'agent Cloud Profiler

Concept du profilage continu

Avant d'expliquer le concept de profilage continu, nous devons comprendre celui-ci. Le profilage est l'un des moyens d'analyser l'application de manière dynamique (analyse de programme dynamique). Il s'effectue généralement au cours du développement de l'application, par le biais de tests de charge, etc. Il s'agit d'une activité unique pour mesurer les métriques système, telles que l'utilisation du processeur et de la mémoire, pendant une période donnée. Après avoir collecté les données de profil, les développeurs les analysent à partir du code.

Le profilage continu est l'approche étendue du profilage normal: il exécute régulièrement des profils de fenêtre courts sur l'application de longue durée et collecte de nombreuses données de profil. Ensuite, il génère automatiquement l'analyse statistique en fonction d'un certain attribut de l'application, tel que le numéro de version, la zone de déploiement, la durée de mesure, etc. Pour en savoir plus sur ce concept, consultez notre documentation.

Étant donné que la cible est une application en cours d'exécution, il existe un moyen de collecter régulièrement les données de profil et de les envoyer à un backend qui post-traite les données statistiques. Il s'agit de l'agent Cloud Profiler. Vous allez bientôt l'intégrer au service du serveur.

Intégrer l'agent Cloud Profiler

Ouvrez l'éditeur Cloud Shell en appuyant sur le bouton 776a11bfb2122549.png en haut à droite de la fenêtre Cloud Shell. Ouvrez step4/src/server/main.go à partir de l'explorateur dans le volet de gauche et recherchez la fonction principale.

step4/src/server/main.go

func main() {
        ...
        // step2. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        svc := NewServerService()
        // step2: add interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        srv := grpc.NewServer(
                grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
                grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
        )
        // step2: end adding interceptor
        shakesapp.RegisterShakespeareServiceServer(srv, svc)
        healthpb.RegisterHealthServer(srv, svc)
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("error serving server: %v", err)
        }
}

Dans la fonction main, vous pouvez voir du code de configuration pour OpenTelemetry et gRPC, réalisé dans la première partie de l'atelier de programmation. Vous allez maintenant ajouter une instrumentation pour l'agent Cloud Profiler ici. Comme nous l'avons fait pour initTracer(), vous pouvez écrire une fonction appelée initProfiler() pour une meilleure lisibilité.

step4/src/server/main.go

import (
        ...
        "cloud.google.com/go/profiler" // step5. add profiler package
        "cloud.google.com/go/storage"
        ...
)

// step5: add Profiler initializer
func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.0.0",
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Examinons de plus près les options spécifiées dans l'objet profiler.Config{}.

  • Service: nom du service que vous pouvez sélectionner et activer dans le tableau de bord du profileur
  • ServiceVersion: nom de la version du service. Vous pouvez comparer les ensembles de données de profil en fonction de cette valeur.
  • NoHeapProfiling: désactive le profilage de l'utilisation de la mémoire
  • NoAllocProfiling: désactive le profilage de l'allocation de mémoire
  • NoGoroutineProfiling: désactiver le profilage de goroutine
  • NoCPUProfiling: désactive le profilage du processeur

Dans cet atelier de programmation, nous n'activerons que le profilage du processeur.

Il ne vous reste plus qu'à appeler cette fonction dans la fonction main. Veillez à importer le package Cloud Profiler dans le bloc d'importation.

step4/src/server/main.go

func main() {
        ...
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        // step5. start profiler
        go initProfiler()
        // step5. end

        svc := NewServerService()
        // step2: add interceptor
        ...
}

Notez que vous appelez la fonction initProfiler() avec le mot clé go. Parce que profiler.Start() se bloque. Vous devez donc l'exécuter dans une autre goroutine. Il est maintenant prêt à être compilé. Veillez à exécuter go mod tidy avant le déploiement.

go mod tidy

Déployez maintenant votre cluster avec votre nouveau service de serveur.

skaffold dev

L'affichage du graphique de type "flamme" sur Cloud Profiler prend généralement quelques minutes. Saisissez "profiler". dans le champ de recherche situé en haut, puis cliquez sur l'icône de Profiler.

3d8ca8a64b267a40.png

Le graphique de type "flamme" suivant s'affiche alors.

7f80797dddc0128d.png

Résumé

Au cours de cette étape, vous avez intégré l'agent Cloud Profiler au service du serveur et confirmé qu'il génère un graphique de type "flamme".

Étape suivante

À l'étape suivante, vous allez rechercher la cause du goulot d'étranglement dans l'application à l'aide du graphique de type "flamme".

5. Analyser le graphique de type "flamme" de Cloud Profiler

Qu'est-ce qu'un graphique de type "flamme" ?

Le graphique de type "flamme" est l'un des moyens de visualiser les données de profil. Pour obtenir des explications détaillées, veuillez consulter notre document. En voici un résumé:

  • Chaque barre exprime l'appel de méthode/fonction dans l'application
  • La direction verticale correspond à la pile d'appel. la pile d'appel s'étend de haut en bas
  • L'orientation horizontale correspond à l'utilisation des ressources. plus longtemps, le pire est.

Par conséquent, examinons le graphique de type "flamme" obtenu.

7f80797dddc0128d.png

Analyser le graphique de type "flamme"

Dans la section précédente, vous avez appris que chaque barre du graphique de type "flamme" exprime l'appel de fonction/méthode, et que sa durée correspond à l'utilisation des ressources dans la fonction/méthode. Le graphique de type "flamme" de Cloud Profiler trie la barre dans l'ordre décroissant ou sur sa longueur de gauche à droite. Vous pouvez commencer par regarder en haut à gauche du graphique.

6d90760c6c1183cd.png

Dans notre cas, il est explicite que grpc.(*Server).serveStreams.func1.2 consomme le plus de temps CPU. En parcourant la pile d'appel de haut en bas, il est utilisé la plupart du temps dans main.(*serverService).GetMatchCount, qui est le gestionnaire de serveur gRPC dans le service du serveur.

Sous GetMatchCount, une série de fonctions d'expression régulière s'affiche: regexp.MatchString et regexp.Compile. Ils sont issus du package standard, c'est-à-dire qu'ils doivent être testés sur de nombreux points de vue, y compris en ce qui concerne les performances. Toutefois, le résultat montre que l'utilisation des ressources de temps CPU est élevée dans regexp.MatchString et regexp.Compile. Compte tenu de ces faits, nous supposons ici que l'utilisation de regexp.MatchString est liée à des problèmes de performances. Lisons donc le code source où la fonction est utilisée.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line, query := strings.ToLower(line), strings.ToLower(req.Query)
                        isMatch, err := regexp.MatchString(query, line)
                        if err != nil {
                                return resp, err
                        }
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Il s'agit de l'emplacement où regexp.MatchString est appelé. En lisant le code source, vous remarquerez que la fonction est appelée dans la boucle "for" imbriquée. L'utilisation de cette fonction peut donc être incorrecte. Regardons le GoDoc de regexp.

80b8a4ba1931ff7b.png

Selon le document, regexp.MatchString compile le modèle d'expression régulière à chaque appel. Voici à quoi ressemble la cause de la consommation importante de ressources.

Résumé

Au cours de cette étape, vous avez effectué l'hypothèse de la cause de la consommation des ressources en analysant le graphique de type "flamme".

Étape suivante

À l'étape suivante, vous allez mettre à jour le code source du service de serveur et confirmer la modification par rapport à la version 1.0.0.

6. Mettre à jour le code source et afficher les différences entre les graphiques de type "flamme"

Mettre à jour le code source

À l'étape précédente, vous avez supposé que l'utilisation de regexp.MatchString était liée à la consommation importante de ressources. Résolvons donc ce problème. Ouvrez le code et modifiez légèrement cette partie.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }

        // step6. considered the process carefully and naively tuned up by extracting
        // regexp pattern compile process out of for loop.
        query := strings.ToLower(req.Query)
        re := regexp.MustCompile(query)
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line = strings.ToLower(line)
                        isMatch := re.MatchString(line)
                        // step6. done replacing regexp with strings
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Comme vous pouvez le voir, le processus de compilation du modèle d'expression régulière est désormais extrait de regexp.MatchString et retiré de la boucle For imbriquée.

Avant de déployer ce code, veillez à mettre à jour la chaîne de version dans la fonction initProfiler().

step4/src/server/main.go

func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.1.0", // step6. update version
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Voyons maintenant comment cela fonctionne. Déployez le cluster à l'aide de la commande Skaffold.

skaffold dev

Après quelques instants, actualisez le tableau de bord Cloud Profiler pour voir comment il se présente.

283cfcd4c13716ad.png

Veillez à remplacer la version par "1.1.0" afin de n'afficher que les profils de la version 1.1.0. Comme vous pouvez le constater, la longueur de la barre de GetMatchCount a été réduite et le ratio d'utilisation du temps CPU (c'est-à-dire que la barre a été raccourcie).

e3a1456b4aada9a5.png

Non seulement vous pouvez consulter le graphique de type "flamme" d'une seule version, mais aussi comparer les différences entre deux versions.

841dec77d8ba5595.png

Modifier la valeur de l'option "Comparer à" dans la liste déroulante "Version" et modifiez la valeur de "Version comparée" à "1.0.0", la version d'origine.

5553844292d6a537.png

Vous verrez ce type de graphique de type "flamme". La forme du graphique est identique à celle de la version 1.1.0, mais la couleur est différente. En mode comparaison, voici la signification de la couleur:

  • Bleu: valeur (consommation des ressources) réduite
  • Orange: valeur (consommation des ressources) gagnée.
  • Gris: neutre

Étant donné la légende, examinons de plus près la fonction. Cliquez sur la barre sur laquelle vous souhaitez zoomer pour afficher plus de détails dans la pile. Veuillez cliquer sur la barre main.(*serverService).GetMatchCount. Vous pouvez également pointer sur la barre pour afficher les détails de la comparaison.

ca08d942dc1e2502.png

Il est indiqué que le temps CPU total est passé de 5,26 s à 2,88 s (10 s = fenêtre d'échantillonnage au total). C'est une énorme amélioration !

Vous pouvez maintenant améliorer les performances de votre application en analysant les données de profil.

Résumé

Au cours de cette étape, vous avez apporté une modification au service serveur et confirmé les améliorations apportées au mode de comparaison de Cloud Profiler.

Étape suivante

À l'étape suivante, vous allez mettre à jour le code source du service de serveur et confirmer la modification par rapport à la version 1.0.0.

7. Étape supplémentaire: Confirmer les améliorations apportées à la cascade Trace

Différence entre une trace distribuée et le profilage continu

Dans la première partie de l'atelier de programmation, vous avez confirmé que vous pouviez identifier le service de goulot d'étranglement dans les microservices pour un chemin de requête et que vous ne pouviez pas déterminer la cause exacte du goulot d'étranglement dans le service spécifique. Dans cette deuxième partie de l'atelier de programmation, vous avez appris que le profilage continu permet d'identifier le goulot d'étranglement au sein d'un service unique à partir des piles d'appels.

Au cours de cette étape, nous allons examiner le graphique en cascade de la trace distribuée (Cloud Trace) et découvrir la différence par rapport au profilage continu.

Ce graphique en cascade correspond à l'une des traces avec la requête "love". L'opération prend environ 6,7 s (6 700 ms) au total.

e2b7dec25926ee51.png

Et ceci après l'amélioration de la même requête. Comme vous le savez, la latence totale est désormais de 1,5 s (1 500 ms), ce qui représente une nette amélioration par rapport à l'implémentation précédente.

feeb7207f36c7e5e.png

Le point important ici est que, dans le graphique en cascade de traces distribuée, les informations de la pile d'appel ne sont disponibles que si vous instrumentez des segments n'importe où. De plus, les traces distribuées se concentrent uniquement sur la latence entre les services, tandis que le profilage continu se concentre sur les ressources informatiques (processeur, mémoire, threads d'OS) d'un seul service.

Sous un autre aspect, la trace distribuée est la base d'événements, tandis que le profil continu est statistique. Chaque trace possède un graphique de latence différent, et vous avez besoin d'un format différent (par exemple, distribution) pour obtenir la tendance des variations de latence.

Résumé

Au cours de cette étape, vous avez vérifié la différence entre la trace distribuée et le profilage continu.

8. Félicitations

Vous avez créé des traces distribuées avec OpenTelemery et confirmé les latences des requêtes dans le microservice sur Google Cloud Trace.

Pour des exercices plus longs, vous pouvez essayer les sujets suivants par vous-même.

  • L'implémentation actuelle envoie tous les délais générés par la vérification de l'état. (grpc.health.v1.Health/Check) Comment exclure ces segments de Cloud Trace ? Pour découvrir un indice, cliquez ici.
  • Mettez en corrélation les journaux d'événements avec les segments et observez leur fonctionnement sur Google Cloud Trace et Google Cloud Logging. Pour découvrir un indice, cliquez ici.
  • Remplacez un service par celui dans une autre langue et essayez de l'instrumenter avec OpenTelemetry pour ce langage.

Si vous souhaitez en savoir plus sur le profileur par la suite, passez à la partie 2. Dans ce cas, vous pouvez ignorer la section "Nettoyage" ci-dessous.

Nettoyage

Après cet atelier de programmation, veuillez arrêter le cluster Kubernetes et supprimer le projet afin d'éviter des frais inattendus sur Google Kubernetes Engine, Google Cloud Trace et Google Artifact Registry.

Commencez par supprimer le cluster. Si vous exécutez le cluster avec skaffold dev, il vous suffit d'appuyer sur Ctrl-C. Si vous exécutez le cluster avec skaffold run, exécutez la commande suivante:

skaffold delete

Résultat de la commande

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Après avoir supprimé le cluster, dans le volet de menu, sélectionnez "IAM et Admin" &gt; "Paramètres", puis cliquez sur "ARRÊTER" .

45aa37b7d5e1ddd1.png

Saisissez ensuite l'ID du projet (et non le nom du projet) dans le formulaire de la boîte de dialogue, puis confirmez l'arrêt.