Instrument zur Steigerung der Leistung Ihrer App in Go (Teil 2: Profiler)

1. Einführung

e0509e8a07ad5537.png

Zuletzt aktualisiert: 14.07.2022

Beobachtbarkeit der Anwendung

Beobachtbarkeit und Continuous Profiler

„Beobachtbarkeit“ ist ein Begriff, der ein Attribut eines Systems beschreibt. Ein System mit Beobachtbarkeit ermöglicht es Teams, ihr System aktiv zu debuggen. In diesem Zusammenhang sind die drei Säulen der Beobachtbarkeit (Logs, Messwerte und Traces) die grundlegende Instrumentierung für das System, um Beobachtbarkeit zu erlangen.

Neben den drei Säulen der Observability ist das kontinuierliche Profiling eine weitere wichtige Komponente für die Observability und erweitert die Nutzerbasis in der Branche. Cloud Profiler ist eines der ursprünglichen Tools und bietet eine einfache Oberfläche, um die Leistungsmesswerte in den Anwendungsaufrufstacks zu analysieren.

Dieses Codelab ist Teil 2 der Reihe und behandelt die Instrumentierung eines Continuous Profiler-Agents. Teil 1 behandelt das verteilte Tracing mit OpenTelemetry und Cloud Trace. Sie erfahren, wie Sie den Engpass der Mikrodienste besser identifizieren können.

Umfang

In diesem Codelab instrumentieren Sie den Continuous Profiler-Agent im Serverservice der „Shakespeare-Anwendung“ (auch bekannt als Shakesapp), die in einem Google Kubernetes Engine-Cluster ausgeführt wird. Die Architektur von Shakesapp ist wie unten beschrieben:

44e243182ced442f.png

  • Loadgen sendet einen Abfragestring in HTTP an den Client.
  • Clients leiten die Anfrage von Loadgen über gRPC an den Server weiter.
  • Der Server akzeptiert die Anfrage vom Client, ruft alle Werke von Shakespeare im Textformat aus Google Cloud Storage ab, sucht nach den Zeilen, die die Anfrage enthalten, und gibt die Nummer der Zeile zurück, die mit dem Client übereinstimmt.

In Teil 1 haben Sie festgestellt, dass der Engpass irgendwo im Serverservice liegt, konnten die genaue Ursache aber nicht ermitteln.

Lerninhalte

  • Profiler-Agent einbetten
  • Engpässe mit Cloud Profiler untersuchen

In diesem Codelab wird beschrieben, wie Sie einen Continuous Profiler-Agent in Ihrer Anwendung instrumentieren.

Voraussetzungen

  • Grundlegende Go-Kenntnisse
  • Grundkenntnisse in Kubernetes

2. Einrichtung und Anforderungen

Umgebung zum selbstbestimmten Lernen einrichten

Wenn Sie noch kein Google-Konto (Gmail oder Google Apps) haben, müssen Sie eines erstellen. Melden Sie sich in der Google Cloud Console ( console.cloud.google.com) an und erstellen Sie ein neues Projekt.

Wenn Sie bereits ein Projekt haben, klicken Sie oben links in der Console auf das Drop-down-Menü zur Projektauswahl:

7a32e5469db69e9.png

Klicken Sie im angezeigten Dialogfeld auf die Schaltfläche „NEUES PROJEKT“, um ein neues Projekt zu erstellen:

7136b3ee36ebaf89.png

Wenn Sie noch kein Projekt haben, wird ein Dialogfeld wie das folgende angezeigt, in dem Sie Ihr erstes Projekt erstellen können:

870a3cbd6541ee86.png

Im nachfolgenden Dialogfeld zum Erstellen von Projekten können Sie die Details Ihres neuen Projekts eingeben:

affdc444517ba805.png

Merken Sie sich die Projekt-ID. Sie ist für alle Google Cloud-Projekte ein eindeutiger Name. Der Name oben ist bereits vergeben und kann nicht verwendet werden. Sie wird später in diesem Codelab als PROJECT_ID bezeichnet.

Als Nächstes müssen Sie, falls noch nicht geschehen, die Abrechnung in der Entwicklerkonsole aktivieren, um Google Cloud-Ressourcen verwenden zu können, und die Cloud Trace API aktivieren.

15d0ef27a8fbab27.png

Dieses Codelab sollte Sie nicht mehr als ein paar Dollar kosten, aber es könnte mehr sein, wenn Sie sich für mehr Ressourcen entscheiden oder wenn Sie sie laufen lassen (siehe Abschnitt „Bereinigen“ am Ende dieses Dokuments). Die Preise für Google Cloud Trace, Google Kubernetes Engine und Google Artifact Registry sind in der offiziellen Dokumentation aufgeführt.

Neuen Nutzern der Google Cloud Platform steht eine kostenlose Testversion mit einem Guthaben von 300$ zur Verfügung. Dieses Codelab sollte damit vollständig kostenlos sein.

Google Cloud Shell einrichten

Während Sie Google Cloud und Google Cloud Trace von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Google Cloud Shell verwendet, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.

Diese Debian-basierte virtuelle Maschine verfügt über alle Entwicklungstools, die Sie benötigen. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Für dieses Codelab benötigen Sie also nur einen Browser (es funktioniert auch auf einem Chromebook).

Klicken Sie zum Aktivieren von Cloud Shell in der Cloud Console einfach auf „Cloud Shell aktivieren“ gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A. Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Augenblicke dauern.

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

Screen Shot 2017-06-14 at 10.13.43 PM.png

Sobald die Verbindung mit der Cloud Shell hergestellt ist, sehen Sie, dass Sie bereits authentifiziert sind und für das Projekt schon Ihre PROJECT_ID eingestellt ist.

gcloud auth list

Befehlsausgabe

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

Befehlsausgabe

[core]
project = <PROJECT_ID>

Wenn das Projekt aus irgendeinem Grund nicht festgelegt ist, führen Sie einfach den folgenden Befehl aus:

gcloud config set project <PROJECT_ID>

Suchst du nach deinem PROJECT_ID? Sehen Sie nach, welche ID Sie in den Einrichtungsschritten verwendet haben, oder suchen Sie sie im Cloud Console-Dashboard:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

In Cloud Shell werden auch einige Umgebungsvariablen standardmäßig festgelegt, die für zukünftige Befehle nützlich sein können.

echo $GOOGLE_CLOUD_PROJECT

Befehlsausgabe

<PROJECT_ID>

Legen Sie zum Schluss die Standardzone und die Projektkonfiguration fest.

gcloud config set compute/zone us-central1-f

Sie können verschiedene Zonen auswählen. Weitere Informationen finden Sie unter Regionen und Zonen.

Spracheinrichtung

In diesem Codelab verwenden wir Go für den gesamten Quellcode. Führen Sie den folgenden Befehl in Cloud Shell aus und prüfen Sie, ob die Go-Version 1.17 oder höher ist.

go version

Befehlsausgabe

go version go1.18.3 linux/amd64

Google Kubernetes-Cluster einrichten

In diesem Codelab führen Sie einen Cluster von Mikrodiensten in Google Kubernetes Engine (GKE) aus. In diesem Codelab gehen wir so vor:

  1. Baseline-Projekt in Cloud Shell herunterladen
  2. Mikrodienste in Containern erstellen
  3. Container in Google Artifact Registry (GAR) hochladen
  4. Container in GKE bereitstellen
  5. Quellcode von Diensten für die Trace-Instrumentierung ändern
  6. Zu Schritt 2

Kubernetes Engine aktivieren

Zuerst richten wir einen Kubernetes-Cluster ein, in dem Shakesapp in GKE ausgeführt wird. Dazu müssen wir GKE aktivieren. Rufen Sie das Menü „Kubernetes Engine“ auf und klicken Sie auf die Schaltfläche „AKTIVIEREN“.

548cfd95bc6d344d.png

Jetzt können Sie einen Kubernetes-Cluster erstellen.

Kubernetes-Cluster erstellen

Führen Sie in Cloud Shell den folgenden Befehl aus, um einen Kubernetes-Cluster zu erstellen. Bestätigen Sie, dass der Zonenwert unter der Region liegt, die Sie zum Erstellen des Artifact Registry-Repositorys verwenden. Ändern Sie den Zonenwert us-central1-f, wenn Ihre Repository-Region die Zone nicht abdeckt.

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

Befehlsausgabe

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

Artifact Registry und Skaffold einrichten

Jetzt haben wir einen Kubernetes-Cluster, der für die Bereitstellung bereit ist. Als Nächstes bereiten wir eine Container Registry für das Pushen und Bereitstellen von Containern vor. Für diese Schritte müssen wir eine Artifact Registry (GAR) einrichten und Skaffold verwenden.

Artifact Registry einrichten

Rufen Sie das Menü von „Artifact Registry“ auf und drücken Sie die Schaltfläche „AKTIVIEREN“.

45e384b87f7cf0db.png

Nach kurzer Zeit wird der Repository-Browser von GAR angezeigt. Klicken Sie auf die Schaltfläche „REPOSITORY ERSTELLEN“ und geben Sie den Namen des Repositorys ein.

d6a70f4cb4ebcbe3.png

In diesem Codelab nenne ich das neue Repository trace-codelab. Das Format des Artefakts ist „Docker“ und der Standorttyp ist „Region“. Wählen Sie die Region aus, die der Region entspricht, die Sie für die Google Compute Engine-Standardzone festgelegt haben. Im obigen Beispiel wurde „us-central1-f“ ausgewählt. Hier wählen wir also „us-central1 (Iowa)“ aus. Klicken Sie dann auf die Schaltfläche „ERSTELLEN“.

9c2d1ce65258ef70.png

Im Repository-Browser wird jetzt „trace-codelab“ angezeigt.

7a3c1f47346bea15.png

Wir kehren später hierher zurück, um den Registrierungspfad zu prüfen.

Skaffold einrichten

Skaffold ist ein praktisches Tool, wenn Sie Mikrodienste entwickeln, die in Kubernetes ausgeführt werden. Mit nur wenigen Befehlen können Sie Container von Anwendungen erstellen, übertragen und bereitstellen. Skaffold verwendet standardmäßig Docker Registry als Container-Registry. Sie müssen Skaffold also so konfigurieren, dass GAR beim Übertragen von Containern erkannt wird.

Öffnen Sie Cloud Shell noch einmal und prüfen Sie, ob Skaffold installiert ist. (In Cloud Shell wird Skaffold standardmäßig in der Umgebung installiert.) Führen Sie den folgenden Befehl aus, um die Skaffold-Version zu sehen.

skaffold version

Befehlsausgabe

v1.38.0

Sie können jetzt das Standard-Repository für Skaffold registrieren. Um den Registrierungspfad zu erhalten, rufen Sie das Artifact Registry-Dashboard auf und klicken Sie auf den Namen des Repositorys, das Sie gerade im vorherigen Schritt eingerichtet haben.

7a3c1f47346bea15.png

Oben auf der Seite sehen Sie dann Navigationspfade. Klicken Sie auf das Symbol e157b1359c3edc06.png, um den Registrierungspfad in die Zwischenablage zu kopieren.

e0f2ae2144880b8b.png

Wenn Sie auf die Schaltfläche „Kopieren“ klicken, wird unten im Browser ein Dialogfeld mit einer Meldung wie der folgenden angezeigt:

„us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab“ wurde kopiert

Kehren Sie zur Cloud Shell zurück. Führen Sie den Befehl skaffold config set default-repo mit dem Wert aus, den Sie gerade aus dem Dashboard kopiert haben.

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

Befehlsausgabe

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

Außerdem müssen Sie die Registry für die Docker-Konfiguration konfigurieren. Führen Sie dazu diesen Befehl aus:

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

Befehlsausgabe

{
  "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

Jetzt können Sie mit dem nächsten Schritt fortfahren und einen Kubernetes-Container in GKE einrichten.

Zusammenfassung

In diesem Schritt richten Sie Ihre Codelab-Umgebung ein:

  • Cloud Shell einrichten
  • Sie haben ein Artifact Registry-Repository für die Containerregistrierung erstellt.
  • Skaffold für die Verwendung der Container Registry einrichten
  • Sie haben einen Kubernetes-Cluster erstellt, in dem die Mikrodienste des Codelabs ausgeführt werden.

Als Nächstes

Im nächsten Schritt instrumentieren Sie den Continuous Profiler-Agent im Serverservice.

3. Mikrodienste erstellen, übertragen und bereitstellen

Codelab-Material herunterladen

Im vorherigen Schritt haben wir alle Voraussetzungen für dieses Codelab eingerichtet. Jetzt können Sie ganze Microservices darauf ausführen. Die Codelab-Materialien werden auf GitHub gehostet. Laden Sie sie mit dem folgenden Git-Befehl in die Cloud Shell-Umgebung herunter.

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

Die Verzeichnisstruktur des Projekts sieht so aus:

.
├── 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
  • manifests: Kubernetes-Manifestdateien
  • proto: Proto-Definition für die Kommunikation zwischen Client und Server
  • src: Verzeichnisse für den Quellcode der einzelnen Dienste
  • skaffold.yaml: Konfigurationsdatei für Skaffold

In diesem Codelab aktualisieren Sie den Quellcode im Ordner step4. Sie können sich auch den Quellcode in den step[1-6]-Ordnern ansehen, um die Änderungen von Anfang an zu verfolgen. (Teil 1 umfasst die Schritte 0 bis 4 und Teil 2 die Schritte 5 und 6.)

Skaffold-Befehl ausführen

Jetzt können Sie die Inhalte erstellen, per Push übertragen und im Kubernetes-Cluster bereitstellen, den Sie gerade erstellt haben. Das klingt nach mehreren Schritten, aber tatsächlich erledigt Skaffold alles für Sie. Versuchen Sie es mit dem folgenden Befehl:

cd step4
skaffold dev

Sobald Sie den Befehl ausführen, sehen Sie die Logausgabe von docker build und können bestätigen, dass die Dateien erfolgreich in das Repository übertragen wurden.

Befehlsausgabe

...
---> 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

Nachdem alle Dienstcontainer übertragen wurden, werden Kubernetes-Bereitstellungen automatisch gestartet.

Befehlsausgabe

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

Nach dem Deployment werden die tatsächlichen Anwendungslogs, die in stdout ausgegeben werden, in jedem Container so angezeigt:

Befehlsausgabe

[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

An diesem Punkt sollten alle Nachrichten vom Server angezeigt werden. Jetzt können Sie endlich mit der Instrumentierung Ihrer Anwendung mit OpenTelemetry für das verteilte Tracing der Dienste beginnen.

Bevor Sie mit der Instrumentierung des Dienstes beginnen, fahren Sie den Cluster mit Strg+C herunter.

Befehlsausgabe

...
[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

Zusammenfassung

In diesem Schritt haben Sie das Codelab-Material in Ihrer Umgebung vorbereitet und bestätigt, dass Skaffold wie erwartet ausgeführt wird.

Als Nächstes

Im nächsten Schritt ändern Sie den Quellcode des Loadgen-Dienstes, um die Trace-Informationen zu instrumentieren.

4. Instrumentierung des Cloud Profiler-Agents

Konzept der kontinuierlichen Profilerstellung

Bevor wir das Konzept des kontinuierlichen Profilings erläutern, müssen wir das Konzept des Profilings selbst verstehen. Die Profilerstellung ist eine der Möglichkeiten, die Anwendung dynamisch zu analysieren (dynamische Programmanalyse). Sie wird in der Regel während der Anwendungsentwicklung im Rahmen von Lasttests usw. durchgeführt. Das ist eine einmalige Aktivität, um die Systemmesswerte wie CPU- und Speicherauslastung während des angegebenen Zeitraums zu messen. Nachdem die Profildaten erfasst wurden, analysieren Entwickler sie außerhalb des Codes.

Das kontinuierliche Profiling ist die erweiterte Version des normalen Profilings: Es werden regelmäßig Profile mit kurzem Zeitfenster für die lang laufende Anwendung ausgeführt und eine Vielzahl von Profildaten erfasst. Anschließend wird die statistische Analyse automatisch anhand eines bestimmten Attributs der Anwendung generiert, z. B. Versionsnummer, Bereitstellungszone oder Messzeit. Weitere Informationen zu diesem Konzept finden Sie in unserer Dokumentation.

Da das Ziel eine laufende Anwendung ist, können Profildaten regelmäßig erfasst und an ein Backend gesendet werden, das die statistischen Daten nachverarbeitet. Das ist der Cloud Profiler-Agent, den Sie bald in den Serverdienst einbetten werden.

Cloud Profiler-Agent einbetten

Öffnen Sie Cloud Shell Editor, indem Sie oben rechts in Cloud Shell auf die Schaltfläche 776a11bfb2122549.png klicken. Öffnen Sie step4/src/server/main.go über den Explorer im linken Bereich und suchen Sie nach der Hauptfunktion.

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)
        }
}

In der Funktion main sehen Sie einige Einrichtungscodes für OpenTelemetry und gRPC, die in Teil 1 des Codelabs ausgeführt wurden. Fügen Sie nun die Instrumentierung für den Cloud Profiler-Agent hinzu. Wie bei initTracer() können Sie zur besseren Lesbarkeit eine Funktion namens initProfiler() schreiben.

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)
        }
}

Sehen wir uns die im profiler.Config{}-Objekt angegebenen Optionen genauer an.

  • Dienst: Der Dienstname, den Sie im Profiler-Dashboard auswählen und aktivieren können
  • ServiceVersion: Der Name der Dienstversion. Sie können Profil-Datasets anhand dieses Werts vergleichen.
  • NoHeapProfiling: Profiling des Arbeitsspeicherverbrauchs deaktivieren
  • NoAllocProfiling: Profiling der Arbeitsspeicherzuweisung deaktivieren
  • NoGoroutineProfiling: Deaktiviert die Goroutine-Profilerstellung.
  • NoCPUProfiling: CPU-Profiling deaktivieren

In diesem Codelab aktivieren wir nur das CPU-Profiling.

Jetzt müssen Sie diese Funktion nur noch in der Funktion main aufrufen. Achten Sie darauf, dass Sie das Cloud Profiler-Paket im Importblock importieren.

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
        ...
}

Beachten Sie, dass Sie die Funktion initProfiler() mit dem Keyword go aufrufen. Da profiler.Start() blockiert, müssen Sie es in einer anderen Goroutine ausführen. Jetzt kann es losgehen. Führen Sie go mod tidy vor der Bereitstellung aus.

go mod tidy

Stellen Sie nun Ihren Cluster mit dem neuen Serverservice bereit.

skaffold dev

Es dauert in der Regel einige Minuten, bis das Flammen-Diagramm in Cloud Profiler angezeigt wird. Geben Sie oben in das Suchfeld „Profiler“ ein und klicken Sie auf das Profilersymbol.

3d8ca8a64b267a40.png

Dann wird die folgende Flammengrafik angezeigt.

7f80797dddc0128d.png

Zusammenfassung

In diesem Schritt haben Sie den Cloud Profiler-Agent in den Serverservice eingebettet und bestätigt, dass er ein Flammen-Diagramm generiert.

Als Nächstes

Im nächsten Schritt untersuchen Sie die Ursache des Engpasses in der Anwendung mit dem Flammen-Diagramm.

5. Cloud Profiler-Flame-Diagramm analysieren

Was ist ein Flame-Diagramm?

Das Flame-Diagramm ist eine der Möglichkeiten, Profildaten zu visualisieren. Eine ausführliche Erklärung finden Sie in diesem Dokument. Hier eine kurze Zusammenfassung:

  • Jeder Balken stellt den Methoden-/Funktionsaufruf in der Anwendung dar.
  • Die vertikale Richtung ist der Aufrufstack. Der Aufrufstack wird von oben nach unten erweitert.
  • Die horizontale Richtung gibt die Ressourcennutzung an. Je länger, desto schlechter.

Sehen wir uns das erstellte Flame-Diagramm an.

7f80797dddc0128d.png

Flame-Diagramm analysieren

Im vorherigen Abschnitt haben Sie erfahren, dass jeder Balken in der Flammengrafik den Funktions-/Methodenaufruf darstellt und die Länge des Balkens die Ressourcennutzung in der Funktion/Methode angibt. Im Flammen-Diagramm von Cloud Profiler wird der Balken in absteigender Reihenfolge der Länge von links nach rechts sortiert. Sie können also zuerst oben links im Diagramm nachsehen.

6d90760c6c1183cd.png

In unserem Fall ist explizit zu sehen, dass grpc.(*Server).serveStreams.func1.2 den größten Teil der CPU-Zeit beansprucht. Wenn wir den Aufrufstack von oben nach unten durchgehen, sehen wir, dass die meiste Zeit in main.(*serverService).GetMatchCount verbracht wird, dem gRPC-Server-Handler im Serverservice.

Unter „GetMatchCount“ sehen Sie eine Reihe von regexp-Funktionen: regexp.MatchString und regexp.Compile. Sie stammen aus dem Standardpaket und sollten aus vielen Blickwinkeln, einschließlich der Leistung, gut getestet sein. Das Ergebnis zeigt jedoch, dass die Ressourcennutzung der CPU-Zeit in regexp.MatchString und regexp.Compile hoch ist. Angesichts dieser Fakten wird davon ausgegangen, dass die Verwendung von regexp.MatchString mit Leistungsproblemen zusammenhängt. Sehen wir uns also den Quellcode an, in dem die Funktion verwendet wird.

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
}

Hier wird regexp.MatchString aufgerufen. Wenn Sie den Quellcode lesen, werden Sie feststellen, dass die Funktion im verschachtelten for-Loop aufgerufen wird. Die Verwendung dieser Funktion ist also möglicherweise nicht korrekt. Sehen wir uns die GoDoc von regexp an.

80b8a4ba1931ff7b.png

Laut dem Dokument wird das Muster des regulären Ausdrucks bei jedem Aufruf von regexp.MatchString kompiliert. Das ist der Grund für den hohen Ressourcenverbrauch.

Zusammenfassung

In diesem Schritt haben Sie die Ursache des Ressourcenverbrauchs durch Analyse des Flame-Diagramms angenommen.

Als Nächstes

Im nächsten Schritt aktualisieren Sie den Quellcode des Serverdienstes und bestätigen die Änderung gegenüber Version 1.0.0.

6. Quellcode aktualisieren und Flame-Graphen vergleichen

Quellcode aktualisieren

Im vorherigen Schritt sind Sie davon ausgegangen, dass die Verwendung von regexp.MatchString etwas mit dem hohen Ressourcenverbrauch zu tun hat. Lass uns das Problem also beheben. Öffnen Sie den Code und ändern Sie diesen Teil.

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
}

Wie Sie sehen, wird die Kompilierung des regulären Ausdrucks jetzt aus der regexp.MatchString extrahiert und aus der verschachtelten for-Schleife verschoben.

Aktualisieren Sie vor der Bereitstellung dieses Codes den Versionsstring in der Funktion 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)
        }
}

Sehen wir uns an, wie das funktioniert. Stellen Sie den Cluster mit dem Skaffold-Befehl bereit.

skaffold dev

Laden Sie das Cloud Profiler-Dashboard nach einer Weile neu und sehen Sie sich das Ergebnis an.

283cfcd4c13716ad.png

Ändern Sie die Version zu "1.1.0", damit nur die Profile aus Version 1.1.0 angezeigt werden. Wie Sie sehen, ist die Länge des Balkens für „GetMatchCount“ kürzer geworden und das Nutzungsverhältnis der CPU-Zeit hat sich verringert.

e3a1456b4aada9a5.png

Sie können nicht nur das Flammen-Diagramm einer einzelnen Version ansehen, sondern auch die Unterschiede zwischen zwei Versionen vergleichen.

841dec77d8ba5595.png

Ändern Sie den Wert der Drop-down-Liste „Vergleichen mit“ in „Version“ und den Wert von „Vergleichsversion“ in „1.0.0“, die Originalversion.

5553844292d6a537.png

Sie sehen eine Flammengrafik wie diese. Die Form des Diagramms ist dieselbe wie in 1.1.0, aber die Farben sind anders. Im Vergleichsmodus haben die Farben folgende Bedeutung:

  • Blau: Der Wert (Ressourcenverbrauch) wurde reduziert.
  • Orange: Der gewonnene Wert (Ressourcenverbrauch)
  • Grau: neutral

Sehen wir uns die Funktion anhand der Legende genauer an. Wenn Sie auf den Balken klicken, den Sie vergrößern möchten, sehen Sie weitere Details im Stapel. Klicken Sie auf die Leiste main.(*serverService).GetMatchCount. Wenn Sie den Mauszeiger auf die Spalte bewegen, werden die Details des Vergleichs angezeigt.

ca08d942dc1e2502.png

Die gesamte CPU-Zeit wird von 5,26 Sekunden auf 2,88 Sekunden reduziert (insgesamt 10 Sekunden = Stichprobenerfassungszeitraum). Das ist eine enorme Verbesserung.

Jetzt können Sie die Leistung Ihrer Anwendung anhand der Analyse von Profildaten verbessern.

Zusammenfassung

In diesem Schritt haben Sie eine Änderung am Serverservice vorgenommen und die Verbesserung im Vergleichsmodus von Cloud Profiler bestätigt.

Als Nächstes

Im nächsten Schritt aktualisieren Sie den Quellcode des Serverdienstes und bestätigen die Änderung gegenüber Version 1.0.0.

7. Zusätzlicher Schritt: Verbesserung im Trace-Wasserfall bestätigen

Unterschied zwischen verteiltem Trace und kontinuierlichem Profiling

Im ersten Teil des Codelabs haben Sie bestätigt, dass Sie den Engpassdienst für einen Anfragepfad über die Mikrodienste hinweg ermitteln können, aber nicht die genaue Ursache des Engpasses im jeweiligen Dienst. In diesem Teil 2 des Codelabs haben Sie gelernt, dass Sie mit kontinuierlichem Profiling den Engpass im einzelnen Dienst anhand von Callstacks ermitteln können.

In diesem Schritt sehen wir uns das Wasserfalldiagramm aus dem verteilten Trace (Cloud Trace) an und vergleichen es mit dem kontinuierlichen Profiling.

Dieses Wasserfalldiagramm gehört zu den Traces mit der Anfrage „love“. Das dauert insgesamt etwa 6,7 Sekunden (6.700 Millisekunden).

e2b7dec25926ee51.png

Das ist das Ergebnis nach der Verbesserung für dieselbe Abfrage. Wie Sie sehen, beträgt die Gesamtlatenz jetzt 1,5 Sekunden (1.500 ms). Das ist eine enorme Verbesserung gegenüber der vorherigen Implementierung.

feeb7207f36c7e5e.png

Wichtig ist hier, dass die Callstack-Informationen im Wasserfalldiagramm für verteilte Traces nur verfügbar sind, wenn Sie Spans überall instrumentieren. Verteilte Traces konzentrieren sich nur auf die Latenz zwischen den Diensten, während sich das kontinuierliche Profiling auf die Computerressourcen (CPU, Arbeitsspeicher, Betriebssystem-Threads) eines einzelnen Dienstes konzentriert.

In einem anderen Aspekt ist der verteilte Trace die Ereignisbasis, das kontinuierliche Profil ist statistisch. Jeder Trace hat ein anderes Latenzdiagramm. Sie benötigen ein anderes Format wie Verteilung, um den Trend der Latenzänderungen zu sehen.

Zusammenfassung

In diesem Schritt haben Sie sich den Unterschied zwischen verteiltem Tracing und kontinuierlichem Profiling angesehen.

8. Glückwunsch

Sie haben mit OpenTelemetry verteilte Traces erstellt und die Anfragelatenzen für den Mikrodienst in Google Cloud Trace bestätigt.

Bei den erweiterten Übungen können Sie die folgenden Themen selbst ausprobieren.

  • Bei der aktuellen Implementierung werden alle Spans gesendet, die von der Systemdiagnose generiert werden. (grpc.health.v1.Health/Check) Wie filtere ich diese Spans aus Cloud Trace heraus? Hier finden Sie einen Hinweis.
  • Ereignislogs mit Spans in Beziehung setzen und sehen, wie das in Google Cloud Trace und Google Cloud Logging funktioniert. Hier finden Sie einen Hinweis.
  • Ersetzen Sie einen Dienst durch den Dienst in einer anderen Sprache und versuchen Sie, ihn mit OpenTelemetry für diese Sprache zu instrumentieren.

Wenn Sie danach mehr über den Profiler erfahren möchten, fahren Sie mit Teil 2 fort. In diesem Fall können Sie den Abschnitt „Bereinigen“ unten überspringen.

Aufräumen

Nach dieser Codelab-Anleitung sollten Sie den Kubernetes-Cluster beenden und das Projekt löschen, damit Ihnen keine unerwarteten Gebühren für Google Kubernetes Engine, Google Cloud Trace und Google Artifact Registry in Rechnung gestellt werden.

Löschen Sie zuerst den Cluster. Wenn Sie den Cluster mit skaffold dev ausführen, müssen Sie nur Strg + C drücken. Wenn Sie den Cluster mit skaffold run ausführen, führen Sie den folgenden Befehl aus:

skaffold delete

Befehlsausgabe

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

Nachdem Sie den Cluster gelöscht haben, wählen Sie im Menübereich „IAM & Admin“ > „Einstellungen“ aus und klicken Sie dann auf die Schaltfläche „HERUNTERFAHREN“.

45aa37b7d5e1ddd1.png

Geben Sie dann die Projekt-ID (nicht den Projektnamen) in das Formular im Dialogfeld ein und bestätigen Sie das Herunterfahren.