Instrument zur Steigerung der Leistung Ihrer App in Go (Teil 1: Nachverfolgen)

1. Einführung

505827108874614d.png

Zuletzt aktualisiert: 15.07.2022

Beobachtbarkeit der Anwendung

Beobachtbarkeit und OpenTelemetry

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

OpenTelemetry ist eine Reihe von Spezifikationen, Bibliotheken und Agents, die die Instrumentierung und den Export von Telemetriedaten (Logs, Messwerte und Traces) beschleunigen, die für die Observability erforderlich sind. OpenTelemetry ist ein offener Standard und ein Community-Projekt unter der CNCF. Durch die Verwendung von Bibliotheken, die das Projekt und sein Ökosystem bereitstellen, können Entwickler ihre Anwendungen anbieterneutral und für mehrere Architekturen instrumentieren.

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 1 der Reihe und behandelt die Instrumentierung verteilter Traces in Mikrodiensten mit OpenTelemetry und Cloud Trace. Im zweiten Teil geht es um die kontinuierliche Profilerstellung mit Cloud Profiler.

Verteilter Trace

Von Logs, Messwerten und Traces ist der Trace die Telemetrie, die die Latenz eines bestimmten Teils des Prozesses im System angibt. Gerade im Zeitalter von Mikrodiensten ist das verteilte Tracing ein wichtiger Faktor, um Latenzengpässe im gesamten verteilten System zu ermitteln.

Bei der Analyse verteilter Traces ist die Visualisierung der Trace-Daten der Schlüssel, um die Latenzen des Gesamtsystems auf einen Blick zu erfassen. Beim verteilten Tracing werden eine Reihe von Aufrufen verarbeitet, um eine einzelne Anfrage an den Systemeinstiegspunkt in Form eines Trace mit mehreren Spans zu verarbeiten.

Ein Span stellt eine einzelne Arbeitseinheit dar, die in einem verteilten System ausgeführt wird, und zeichnet Start- und Endzeiten auf. Spans haben oft hierarchische Beziehungen zueinander. Im Bild unten sind alle kleineren Spans untergeordnete Spans eines großen /messages-Spans und werden zu einem Trace zusammengefasst, der den Pfad der Arbeit durch ein System zeigt.

Ein Trace

Google Cloud Trace ist eine der Optionen für das Backend für verteiltes Tracing und ist gut in andere Produkte in Google Cloud eingebunden.

Umfang

In diesem Codelab instrumentieren Sie Trace-Informationen in den Diensten, die als „Shakespeare-Anwendung“ (auch Shakesapp) bezeichnet werden und in einem Google Kubernetes Engine-Cluster ausgeführt werden. 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 nimmt die Anfrage vom Client entgegen, 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.

Sie instrumentieren die Trace-Informationen für die gesamte Anfrage. Danach betten Sie einen Profiler-Agent in den Server ein und untersuchen den Engpass.

Lerninhalte

  • Erste Schritte mit den OpenTelemetry Trace-Bibliotheken in einem Go-Projekt
  • Span mit der Bibliothek erstellen
  • Spannenkontexte über das Netzwerk zwischen App-Komponenten weitergeben
  • Trace-Daten an Cloud Trace senden
  • Trace in Cloud Trace analysieren

In diesem Codelab wird beschrieben, wie Sie Ihre Mikrodienste instrumentieren. Dieses Beispiel enthält nur drei Komponenten (Load Generator, Client und Server), damit es leicht nachzuvollziehen ist. Sie können den in diesem Codelab beschriebenen Prozess jedoch auch auf komplexere und größere Systeme anwenden.

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 drücken Sie 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 einer kleinen Anzahl von Befehlen wird der Workflow zum Erstellen, Übertragen und Bereitstellen von Anwendungscontainern abgewickelt. 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 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 erstellen Sie Ihre Microservices, übertragen sie per Push und stellen sie im Cluster bereit.

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 step0. Sie können sich auch den Quellcode in den step[1-6]-Ordnern ansehen, um die Antworten in den folgenden Schritten zu finden. 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 step0
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 die Registry ü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 für HTTP

Konzept der Trace-Instrumentierung und -Weitergabe

Bevor wir den Quellcode bearbeiten, möchte ich kurz anhand eines einfachen Diagramms erläutern, wie verteilte Traces funktionieren.

6be42e353b9bfd1d.png

In diesem Beispiel instrumentieren wir den Code, um Trace- und Span-Informationen nach Cloud Trace zu exportieren und den Trace-Kontext über die Anfrage vom Loadgen-Dienst zum Serverdienst weiterzugeben.

Anwendungen müssen Trace-Metadaten wie Trace-ID und Span-ID senden, damit Cloud Trace alle Spans mit derselben Trace-ID zu einem Trace zusammenfassen kann. Außerdem muss die Anwendung Trace-Kontexte (die Kombination aus Trace-ID und Span-ID des übergeordneten Spans) beim Anfordern von Downstream-Diensten weitergeben, damit diese wissen, welchen Trace-Kontext sie verarbeiten.

OpenTelemetry bietet folgende Vorteile:

  • eindeutige Trace-ID und Span-ID zu generieren.
  • Trace-ID und Span-ID ins Backend exportieren
  • Trace-Kontexte an andere Dienste weiterzugeben
  • um zusätzliche Metadaten einzubetten, die bei der Analyse von Traces helfen.

Komponenten in OpenTelemetry Trace

b01f7bb90188db0d.png

So instrumentieren Sie Anwendungstraces mit OpenTelemetry:

  1. Exporter erstellen
  2. Erstellen Sie eine TracerProvider-Bindung für den Exporter in Schritt 1 und legen Sie sie global fest.
  3. TextMapPropagaror festlegen, um die Weiterleitungsmethode festzulegen
  4. Tracer über TracerProvider abrufen
  5. Zeitraum aus dem Tracer generieren

Sie müssen die detaillierten Eigenschaften in den einzelnen Komponenten noch nicht verstehen. Wichtig ist Folgendes:

  • Der Exporter kann hier in TracerProvider eingebunden werden.
  • TracerProvider enthält alle Konfigurationen für das Trace-Sampling und den Export.
  • Alle Traces sind im Tracer-Objekt gebündelt.

Nachdem wir das geklärt haben, können wir uns dem eigentlichen Programmieren zuwenden.

Ersten Span instrumentieren

Load-Generator-Dienst instrumentieren

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

step0/src/loadgen/main.go

func main() {
        ...
        for range t.C {
                log.Printf("simulating client requests, round %d", i)
                if err := run(numWorkers, numConcurrency); err != nil {
                        log.Printf("aborted round with error: %v", err)
                }
                log.Printf("simulated %d requests", numWorkers)
                if numRounds != 0 && i > numRounds {
                        break
                }
                i++
        }
}

In der Hauptfunktion sehen Sie die Schleife, in der die Funktion run aufgerufen wird. In der aktuellen Implementierung enthält der Abschnitt zwei Logzeilen, in denen der Beginn und das Ende des Funktionsaufrufs aufgezeichnet werden. Jetzt instrumentieren wir die Spanneninformationen, um die Latenz des Funktionsaufrufs zu erfassen.

Richten wir zuerst, wie im vorherigen Abschnitt beschrieben, die gesamte Konfiguration für OpenTelemetry ein. Fügen Sie OpenTelemetry-Pakete so hinzu:

step0/src/loadgen/main.go

import (
        "context" // step1. add packages
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
        // step1. end add packages
)

Zur besseren Lesbarkeit erstellen wir eine Einrichtungsfunktion namens initTracer und rufen sie in der Funktion main auf.

step0/src/loadgen/main.go

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

Sie werden feststellen, dass die Einrichtung von OpenTelemetry wie im vorherigen Abschnitt beschrieben erfolgt. Bei dieser Implementierung verwenden wir einen stdout-Exporter, der alle Trace-Informationen in einem strukturierten Format in die Standardausgabe exportiert.

Anschließend rufen Sie sie über die Hauptfunktion auf. Rufen Sie initTracer() auf und achten Sie darauf, dass Sie TracerProvider.Shutdown() aufrufen, wenn Sie die Anwendung schließen.

step0/src/loadgen/main.go

func main() {
        // step1. 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)
                }
        }()
        // step1. end setup

        log.Printf("starting worder with %d workers in %d concurrency", numWorkers, numConcurrency)
        log.Printf("number of rounds: %d (0 is inifinite)", numRounds)
        ...

Nachdem Sie die Einrichtung abgeschlossen haben, müssen Sie einen Span mit einer eindeutigen Trace-ID und Span-ID erstellen. OpenTelemetry bietet dafür eine praktische Bibliothek. Fügen Sie dem HTTP-Client des Instruments zusätzliche neue Pakete hinzu.

step0/src/loadgen/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/http/httptrace" // step1. add packages
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        // step1. end add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
)

Da der Lastgenerator den Clientdienst in HTTP mit net/http in der Funktion runQuery aufruft, verwenden wir das contrib-Paket für net/http und aktivieren die Instrumentierung mit der Erweiterung des Pakets httptrace und otelhttp.

Zuerst fügen wir eine globale Paketvariable „httpClient“ hinzu, um HTTP-Anfragen über den instrumentierten Client aufzurufen.

step0/src/loadgen/main.go

var httpClient = http.Client{
        Transport: otelhttp.NewTransport(http.DefaultTransport)
}

Fügen Sie als Nächstes Instrumentierung in der Funktion runQuery hinzu, um den benutzerdefinierten Span mit OpenTelemetry und dem automatisch generierten Span des benutzerdefinierten HTTP-Clients zu erstellen. Das werden Sie tun:

  1. Tracer von globalen TracerProvider mit otel.Tracer() abrufen
  2. Stamm-Spanne mit der Methode Tracer.Start() erstellen
  3. Beenden Sie den Stamm-Span zu einem beliebigen Zeitpunkt (in diesem Fall am Ende der Funktion runQuery).

step0/src/loadgen/main.go

        reqURL.RawQuery = v.Encode()
        // step1. replace http.Get() with custom client call
        // resp, err := http.Get(reqURL.String())

        // step1. instrument trace
        ctx := context.Background()
        tr := otel.Tracer("loadgen")
        ctx, span := tr.Start(ctx, "query.request", trace.WithAttributes(
                semconv.TelemetrySDKLanguageGo,
                semconv.ServiceNameKey.String("loadgen.runQuery"),
                attribute.Key("query").String(s),
        ))
        defer span.End()
        ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx))
        req, err := http.NewRequestWithContext(ctx, "GET", reqURL.String(), nil)
        if err != nil {
                return -1, fmt.Errorf("error creating HTTP request object: %v", err)
        }
        resp, err := httpClient.Do(req)
        // step1. end instrumentation
        if err != nil {
                return -1, fmt.Errorf("error sending request to %v: %v", reqURL.String(), err)
        }

Die Instrumentierung in loadgen (HTTP-Clientanwendung) ist jetzt abgeschlossen. Aktualisieren Sie go.mod und go.sum mit dem Befehl go mod.

go mod tidy

Kundenservice für Instrumente

Im vorherigen Abschnitt haben wir den Teil instrumentiert, der im folgenden Bild rot umrandet ist. Wir haben die Spanneninformationen im Load Generator-Dienst instrumentiert. Ähnlich wie beim Load Generator-Dienst müssen wir jetzt den Clientdienst instrumentieren. Der Unterschied zum Load-Generator-Dienst besteht darin, dass der Clientdienst die vom Load-Generator-Dienst im HTTP-Header weitergegebenen Trace-ID-Informationen extrahieren und die ID zum Generieren von Spans verwenden muss.

bcaccd06691269f8.png

Öffnen Sie den Cloud Shell-Editor und fügen Sie die erforderlichen Pakete hinzu, wie wir es für den Load Generator-Dienst getan haben.

step0/src/client/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "net/url"
        "os"
        "time"

        "opentelemetry-trace-codelab-go/client/shakesapp"
        // step1. add new import
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "go.opentelemetry.io/otel/trace"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
        // step1. end new import
)

Auch hier müssen wir OpenTelemetry einrichten. Kopieren Sie einfach die initTracer-Funktion aus loadgen und rufen Sie sie auch in der main-Funktion des Clientdienstes auf.

step0/src/client/main.go

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

Jetzt ist es an der Zeit, Spans zu instrumentieren. Da der Clientdienst HTTP-Anfragen vom Loadgen-Dienst akzeptieren muss, muss der Handler instrumentiert werden. Der HTTP-Server im Clientdienst wird mit net/http implementiert. Sie können das otelhttp-Paket wie im Loadgen verwenden.

Zuerst ersetzen wir die Handler-Registrierung durch otelhttp Handler. Suchen Sie in der Funktion main nach den Zeilen, in denen der HTTP-Handler mit http.HandleFunc() registriert ist.

step0/src/client/main.go

        // step1. change handler to intercept OpenTelemetry related headers
        // http.HandleFunc("/", svc.handler)
        otelHandler := otelhttp.NewHandler(http.HandlerFunc(svc.handler), "client.handler")
        http.Handle("/", otelHandler)
        // step1. end intercepter setting
        http.HandleFunc("/_genki", svc.health)

Anschließend instrumentieren wir den tatsächlichen Bereich innerhalb des Handlers. Suchen Sie nach func (*clientService) handler() und fügen Sie die Spanneninstrumentierung mit trace.SpanFromContext() hinzu.

step0/src/client/main.go

func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) {
        ...
        ctx := r.Context()
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()
        // step1. instrument trace
        span := trace.SpanFromContext(ctx)
        defer span.End()
        // step1. end instrument
        ...

Mit dieser Instrumentierung erhalten Sie die Spannen vom Beginn bis zum Ende der handler-Methode. Damit sich die Spannen leichter analysieren lassen, fügen Sie der Abfrage ein zusätzliches Attribut hinzu, in dem die Anzahl der Übereinstimmungen gespeichert wird. Fügen Sie direkt vor der Protokollzeile den folgenden Code ein.

step0/src/client/main.go

func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) {
        ...
        // step1. add span specific attribute
        span.SetAttributes(attribute.Key("matched").Int64(resp.MatchCount))
        // step1. end adding attribute
        log.Println(string(ret))
        ...

Mit der oben beschriebenen Instrumentierung haben Sie die Trace-Instrumentierung zwischen Loadgen und Client abgeschlossen. In den folgenden Abschnitten erfahren Sie, welche Vorteile Android für Ihr BYOD-Programm bietet. Führen Sie den Code noch einmal mit Skaffold aus.

skaffold dev

Nach einiger Zeit, in der die Dienste im GKE-Cluster ausgeführt werden, sehen Sie eine große Anzahl von Log-Nachrichten wie diese:

Befehlsausgabe

[loadgen] {
[loadgen]       "Name": "query.request",
[loadgen]       "SpanContext": {
[loadgen]               "TraceID": "cfa22247a542beeb55a3434392d46b89",
[loadgen]               "SpanID": "18b06404b10c418b",
[loadgen]               "TraceFlags": "01",
[loadgen]               "TraceState": "",
[loadgen]               "Remote": false
[loadgen]       },
[loadgen]       "Parent": {
[loadgen]               "TraceID": "00000000000000000000000000000000",
[loadgen]               "SpanID": "0000000000000000",
[loadgen]               "TraceFlags": "00",
[loadgen]               "TraceState": "",
[loadgen]               "Remote": false
[loadgen]       },
[loadgen]       "SpanKind": 1,
[loadgen]       "StartTime": "2022-07-14T13:13:36.686751087Z",
[loadgen]       "EndTime": "2022-07-14T13:14:31.849601964Z",
[loadgen]       "Attributes": [
[loadgen]               {
[loadgen]                       "Key": "telemetry.sdk.language",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "go"
[loadgen]                       }
[loadgen]               },
[loadgen]               {
[loadgen]                       "Key": "service.name",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "loadgen.runQuery"
[loadgen]                       }
[loadgen]               },
[loadgen]               {
[loadgen]                       "Key": "query",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "faith"
[loadgen]                       }
[loadgen]               }
[loadgen]       ],
[loadgen]       "Events": null,
[loadgen]       "Links": null,
[loadgen]       "Status": {
[loadgen]               "Code": "Unset",
[loadgen]               "Description": ""
[loadgen]       },
[loadgen]       "DroppedAttributes": 0,
[loadgen]       "DroppedEvents": 0,
[loadgen]       "DroppedLinks": 0,
[loadgen]       "ChildSpanCount": 5,
[loadgen]       "Resource": [
[loadgen]               {
[loadgen]                       "Key": "service.name",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "unknown_service:loadgen"
...

Der stdout-Exporter gibt diese Meldungen aus. Die übergeordneten Spans aller Spans von loadgen haben TraceID: 00000000000000000000000000000000, da dies der Stamm-Span ist, also der erste Span im Trace. Außerdem sehen Sie, dass das Attribut "query" den Abfragestring enthält, der an den Clientdienst übergeben wird.

Zusammenfassung

In diesem Schritt haben Sie den Load-Generator-Dienst und den Clientdienst, die über HTTP kommunizieren, instrumentiert und bestätigt, dass Sie den Trace-Kontext erfolgreich über Dienste hinweg weitergeben und Informationen zu Spans aus beiden Diensten in die Standardausgabe exportieren können.

Als Nächstes

Im nächsten Schritt instrumentieren Sie den Client- und den Serverdienst, um zu bestätigen, wie der Trace-Kontext über gRPC weitergegeben wird.

5. Instrumentierung für gRPC

Im vorherigen Schritt haben wir die erste Hälfte der Anfrage in diesen Mikrodiensten instrumentiert. In diesem Schritt versuchen wir, die gRPC-Kommunikation zwischen dem Client- und dem Serverdienst zu instrumentieren. (Grünes und violettes Rechteck im Bild unten)

75310d8e0e3b1a30.png

Instrumentierung für gRPC-Client vor dem Build

Das Ökosystem von OpenTelemetry bietet viele praktische Bibliotheken, die Entwicklern bei der Instrumentierung von Anwendungen helfen. Im vorherigen Schritt haben wir die vorab erstellte Instrumentierung für das Paket net/http verwendet. In diesem Schritt verwenden wir die Bibliothek, um den Trace-Kontext über gRPC weiterzugeben.

Zuerst importieren Sie das vorgefertigte gRPC-Paket namens otelgrpc.

step0/src/client/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "net/url"
        "os"
        "time"

        "opentelemetry-trace-codelab-go/client/shakesapp"
        // step2. add prebuilt gRPC package (otelgrpc) 
        "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "go.opentelemetry.io/otel/trace"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
)

Diesmal ist der Clientdienst ein gRPC-Client für den Serverdienst. Sie müssen also den gRPC-Client instrumentieren. Suchen Sie die Funktion mustConnGRPC und fügen Sie gRPC-Interceptors hinzu, die jedes Mal neue Spans instrumentieren, wenn der Client Anfragen an den Server sendet.

step0/src/client/main.go

// Helper function for gRPC connections: Dial and create client once, reuse.
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
        var err error
        // step2. add gRPC interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        *conn, err = grpc.DialContext(ctx, addr,
                grpc.WithTransportCredentials(insecure.NewCredentials()),
                grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)),
                grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)),
                grpc.WithTimeout(time.Second*3),
        )
        // step2: end adding interceptor
        if err != nil {
                panic(fmt.Sprintf("Error %s grpc: failed to connect %s", err, addr))
        }
}

Da Sie OpenTelemetry bereits im vorherigen Abschnitt eingerichtet haben, müssen Sie dies nicht tun.

Vorkonfigurierte Instrumentierung für gRPC-Server

Wie beim gRPC-Client rufen wir die vorgefertigte Instrumentierung für den gRPC-Server auf. Fügen Sie dem Importbereich ein neues Paket hinzu, z. B.:

step0/src/server/main.go

import (
        "context"
        "fmt"
        "io/ioutil"
        "log"
        "net"
        "os"
        "regexp"
        "strings"

        "opentelemetry-trace-codelab-go/server/shakesapp"

        "cloud.google.com/go/storage"
        // step2. add OpenTelemetry packages including otelgrpc
        "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
        "go.opentelemetry.io/otel"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "google.golang.org/api/iterator"
        "google.golang.org/api/option"
        "google.golang.org/grpc"
        healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

Da Sie den Server zum ersten Mal instrumentieren, müssen Sie OpenTelemetry zuerst einrichten. Das funktioniert ähnlich wie bei den Loadgen- und Clientdiensten.

step0/src/server/main.go

// step2. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }
        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

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

Als Nächstes müssen Sie Server-Interceptors hinzufügen. Suchen Sie in der Funktion main nach der Stelle, an der grpc.NewServer() aufgerufen wird, und fügen Sie der Funktion Interceptors hinzu.

step0/src/server/main.go

func main() {
        ...
        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)
        ...

Mikrodienst ausführen und Trace bestätigen

Führen Sie dann den geänderten Code mit dem Skaffold-Befehl aus.

skaffold dev

Auf stdout werden wieder eine Reihe von Informationen zu Spans angezeigt.

Befehlsausgabe

...
[server] {
[server]        "Name": "shakesapp.ShakespeareService/GetMatchCount",
[server]        "SpanContext": {
[server]                "TraceID": "89b472f213a400cf975e0a0041649667",
[server]                "SpanID": "96030dbad0061b3f",
[server]                "TraceFlags": "01",
[server]                "TraceState": "",
[server]                "Remote": false
[server]        },
[server]        "Parent": {
[server]                "TraceID": "89b472f213a400cf975e0a0041649667",
[server]                "SpanID": "cd90cc3859b73890",
[server]                "TraceFlags": "01",
[server]                "TraceState": "",
[server]                "Remote": true
[server]        },
[server]        "SpanKind": 2,
[server]        "StartTime": "2022-07-14T14:05:55.74822525Z",
[server]        "EndTime": "2022-07-14T14:06:03.449258891Z",
[server]        "Attributes": [
...
[server]        ],
[server]        "Events": [
[server]                {
[server]                        "Name": "message",
[server]                        "Attributes": [
...
[server]                        ],
[server]                        "DroppedAttributeCount": 0,
[server]                        "Time": "2022-07-14T14:05:55.748235489Z"
[server]                },
[server]                {
[server]                        "Name": "message",
[server]                        "Attributes": [
...
[server]                        ],
[server]                        "DroppedAttributeCount": 0,
[server]                        "Time": "2022-07-14T14:06:03.449255889Z"
[server]                }
[server]        ],
[server]        "Links": null,
[server]        "Status": {
[server]                "Code": "Unset",
[server]                "Description": ""
[server]        },
[server]        "DroppedAttributes": 0,
[server]        "DroppedEvents": 0,
[server]        "DroppedLinks": 0,
[server]        "ChildSpanCount": 0,
[server]        "Resource": [
[server]                {
...
[server]        ],
[server]        "InstrumentationLibrary": {
[server]                "Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
[server]                "Version": "semver:0.33.0",
[server]                "SchemaURL": ""
[server]        }
[server] }
...

Sie stellen fest, dass Sie keine Spannenamen eingebettet und Spannen manuell mit trace.Start() oder span.SpanFromContext() erstellt haben. Sie erhalten trotzdem eine große Anzahl von Spans, da sie von den generierten gRPC-Interceptors erstellt wurden.

Zusammenfassung

In diesem Schritt haben Sie die gRPC-basierte Kommunikation mit Unterstützung der OpenTelemetry-Ökosystembibliotheken instrumentiert.

Als Nächstes

Im nächsten Schritt visualisieren Sie den Trace mit Cloud Trace und erfahren, wie Sie die erfassten Spans analysieren.

6. Trace mit Cloud Trace visualisieren

Sie haben Traces im gesamten System mit OpenTelemetry instrumentiert. Bisher haben Sie gelernt, wie Sie HTTP- und gRPC-Dienste instrumentieren. Sie haben zwar gelernt, wie Sie sie einrichten, aber noch nicht, wie Sie sie analysieren. In diesem Abschnitt ersetzen Sie stdout-Exporter durch Cloud Trace-Exporter und erfahren, wie Sie Ihre Traces analysieren.

Cloud Trace-Exporter verwenden

Eine der leistungsstarken Eigenschaften von OpenTelemetry ist die Möglichkeit, Plug-ins zu verwenden. Wenn Sie alle von Ihrer Instrumentierung erfassten Spans visualisieren möchten, müssen Sie nur den stdout-Exporter durch den Cloud Trace-Exporter ersetzen.

Öffnen Sie die main.go-Dateien der einzelnen Dienste und suchen Sie nach der Funktion initTracer(). Löschen Sie die Zeile, um einen stdout-Exporter zu generieren, und erstellen Sie stattdessen einen Cloud Trace-Exporter.

step0/src/loadgen/main.go

import (
        ...
        // step3. add OpenTelemetry for Cloud Trace package
        cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
)

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // step3. replace stdout exporter with Cloud Trace exporter
        // cloudtrace.New() finds the credentials to Cloud Trace automatically following the
        // rules defined by golang.org/x/oauth2/google.findDefaultCredentailsWithParams.
        // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams
        exporter, err := cloudtrace.New()
        // step3. end replacing exporter
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

Sie müssen dieselbe Funktion auch im Client- und Serverservice bearbeiten.

Mikrodienst ausführen und Trace bestätigen

Führen Sie den Cluster nach der Bearbeitung einfach wie gewohnt mit dem Skaffold-Befehl aus.

skaffold dev

Da Sie den Exporter durch einen Cloud Trace-Exporter ersetzt haben, sehen Sie jetzt nicht mehr viele Informationen zu Spans im strukturierten Logformat in stdout.

Befehlsausgabe

[loadgen] 2022/07/14 15:01:07 simulated 20 requests
[loadgen] 2022/07/14 15:01:07 simulating client requests, round 37
[loadgen] 2022/07/14 15:01:14 query 'sweet': matched 958
[client] 2022/07/14 15:01:14 {"match_count":958}
[client] 2022/07/14 15:01:14 {"match_count":3040}
[loadgen] 2022/07/14 15:01:14 query 'love': matched 3040
[client] 2022/07/14 15:01:15 {"match_count":349}
[loadgen] 2022/07/14 15:01:15 query 'hello': matched 349
[client] 2022/07/14 15:01:15 {"match_count":484}
[loadgen] 2022/07/14 15:01:15 query 'faith': matched 484
[loadgen] 2022/07/14 15:01:15 query 'insolence': matched 14
[client] 2022/07/14 15:01:15 {"match_count":14}
[client] 2022/07/14 15:01:21 {"match_count":484}
[loadgen] 2022/07/14 15:01:21 query 'faith': matched 484
[client] 2022/07/14 15:01:21 {"match_count":728}
[loadgen] 2022/07/14 15:01:21 query 'world': matched 728
[client] 2022/07/14 15:01:22 {"match_count":484}
[loadgen] 2022/07/14 15:01:22 query 'faith': matched 484
[loadgen] 2022/07/14 15:01:22 query 'hello': matched 349
[client] 2022/07/14 15:01:22 {"match_count":349}
[client] 2022/07/14 15:01:23 {"match_count":1036}
[loadgen] 2022/07/14 15:01:23 query 'friend': matched 1036
[loadgen] 2022/07/14 15:01:28 query 'tear': matched 463
...

Prüfen wir nun, ob alle Spans korrekt an Cloud Trace gesendet werden. Rufen Sie die Cloud Console auf und gehen Sie zur „Trace-Liste“. Sie ist über das Suchfeld leicht zugänglich. Andernfalls können Sie im linken Bereich auf das Dreistrich-Menü 8b3f8411bd737e06.png klicken.

Im Latenzgrafik sind dann viele blaue Punkte zu sehen. Jeder Punkt steht für einen einzelnen Trace.

3ecf131423fc4c40.png

Klicken Sie auf einen der Schritte, um die Details im Trace aufzurufen. 4fd10960c6648a03.png

Schon aus diesem einfachen Überblick lassen sich viele Erkenntnisse gewinnen. Im Wasserfalldiagramm sehen Sie beispielsweise, dass die Latenz hauptsächlich auf den Bereich mit dem Namen shakesapp.ShakespeareService/GetMatchCount zurückzuführen ist. Das können Sie in der Übersichtstabelle sehen (siehe 1 im Bild oben). In der Spalte ganz rechts sehen Sie die Dauer der einzelnen Zeiträume. Außerdem wurde dieser Trace für die Anfrage „Freund“ erstellt. (Siehe 2 im Bild oben)

Anhand dieser kurzen Analysen stellen Sie möglicherweise fest, dass Sie detailliertere Spannen innerhalb der GetMatchCount-Methode benötigen. Im Vergleich zu stdout-Informationen ist die Visualisierung leistungsstark. Weitere Informationen zu Cloud Trace-Details finden Sie in unserer offiziellen Dokumentation.

Zusammenfassung

In diesem Schritt haben Sie den stdout-Exporter durch den Cloud Trace-Exporter ersetzt und Traces in Cloud Trace visualisiert. Außerdem haben Sie gelernt, wie Sie mit der Analyse der Traces beginnen.

Als Nächstes

Im nächsten Schritt ändern Sie den Quellcode des Servers, um in „GetMatchCount“ einen untergeordneten Bereich hinzuzufügen.

7. Unterbereich für eine bessere Analyse hinzufügen

Im vorherigen Schritt haben Sie herausgefunden, dass die Ursache für die von loadgen beobachtete Round-Trip-Zeit hauptsächlich der Prozess in der Methode „GetMatchCount“, dem gRPC-Handler, im Serverservice ist. Da wir jedoch nichts anderes als den Handler instrumentiert haben, können wir keine weiteren Erkenntnisse aus dem Wasserfalldiagramm ziehen. Dies ist ein häufiger Fall, wenn wir mit der Instrumentierung von Microservices beginnen.

3b63a1e471dddb8c.png

In diesem Abschnitt instrumentieren wir einen untergeordneten Bereich, in dem der Server Google Cloud Storage aufruft. Das ist häufig der Fall, wenn einige externe Netzwerk-E/A-Vorgänge im Prozess lange dauern. Es ist wichtig, festzustellen, ob der Aufruf die Ursache ist.

Untergeordneten Bereich auf dem Server instrumentieren

Öffnen Sie main.go auf dem Server und suchen Sie nach der Funktion readFiles. Diese Funktion ruft eine Anfrage an Google Cloud Storage auf, um alle Textdateien mit Werken von Shakespeare abzurufen. In dieser Funktion können Sie einen untergeordneten Bereich erstellen, wie Sie es für die HTTP-Serverinstrumentierung im Clientdienst getan haben.

step0/src/server/main.go

func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) {
        type resp struct {
                s   string
                err error
        }

        // step4: add an extra span
        span := trace.SpanFromContext(ctx)
        span.SetName("server.readFiles")
        span.SetAttributes(attribute.Key("bucketname").String(bucketName))
        defer span.End()
        // step4: end add span
        ...

Das war's schon. Sehen wir uns an, wie es läuft, wenn wir die App ausführen.

Mikrodienst ausführen und Trace bestätigen

Führen Sie den Cluster nach der Bearbeitung einfach wie gewohnt mit dem Skaffold-Befehl aus.

skaffold dev

Wählen Sie in der Liste der Traces einen Trace mit dem Namen query.request aus. Sie sehen ein ähnliches Wasserfalldiagramm, nur mit einem neuen Bereich unter shakesapp.ShakespeareService/GetMatchCount. (Der Bereich im roten Rechteck unten)

3d4a891aa30d7a32.png

Aus diesem Diagramm geht hervor, dass der externe Aufruf von Google Cloud Storage einen großen Teil der Latenz ausmacht, aber auch andere Dinge für einen Großteil der Latenz verantwortlich sind.

Sie haben bereits durch einige Blicke in die Wasserfallgrafik des Traces viele Informationen erhalten. Wie erhalten Sie die weiteren Leistungsdetails in Ihrer Anwendung? Hier kommt der Profiler ins Spiel. Wir beenden dieses Codelab aber erst einmal und verschieben alle Profiler-Tutorials auf Teil 2.

Zusammenfassung

In diesem Schritt haben Sie einen weiteren Bereich im Serverservice instrumentiert und weitere Informationen zur Systemlatenz erhalten.

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.