1. Wprowadzenie

Ostatnia aktualizacja: 15.07.2022
Dostrzegalność aplikacji
Dostrzegalność i OpenTelemetry
Obserwowalność to termin używany do opisania atrybutu systemu. System z obserwowalnością umożliwia zespołom aktywne debugowanie systemu. W tym kontekście 3 filary dostrzegalności – logi, dane i logi czasu – są podstawowymi narzędziami do instrumentacji systemu w celu uzyskania dostrzegalności.
OpenTelemetry to zestaw specyfikacji, bibliotek i agentów, które przyspieszają instrumentację i eksportowanie danych telemetrycznych (logów, wskaźników i logów czasu) wymaganych do dostrzegalności. OpenTelemetry to otwarty standard i projekt społecznościowy w ramach CNCF. Dzięki korzystaniu z bibliotek udostępnianych przez projekt i jego ekosystem deweloperzy mogą instrumentować aplikacje w sposób niezależny od dostawcy i w przypadku wielu architektur.
Oprócz 3 filarów obserwacji kolejnym kluczowym komponentem jest ciągłe profilowanie, które zwiększa bazę użytkowników w branży. Cloud Profiler to jedna z pierwszych usług tego typu. Zapewnia ona prosty interfejs do szczegółowego analizowania danych o wydajności w stosach wywołań aplikacji.
To ćwiczenie jest pierwszą częścią serii i obejmuje instrumentację rozproszonych śladów w mikroserwisach za pomocą OpenTelemetry i Cloud Trace. W części 2 omówimy profilowanie ciągłe za pomocą Cloud Profiler.
Distributed Trace
Ślad to dane telemetryczne, które informują o opóźnieniu określonej części procesu w systemie. Szczególnie w erze mikroserwisów śledzenie rozproszone jest silnym motorem do wykrywania wąskich gardeł w zakresie opóźnień w całym systemie rozproszonym.
Podczas analizowania rozproszonych śladów wizualizacja danych śledzenia jest kluczem do szybkiego zrozumienia ogólnych opóźnień systemu. W śledzeniu rozproszonym obsługujemy zestaw wywołań w celu przetworzenia pojedynczego żądania do punktu wejścia systemu w postaci śladu zawierającego wiele zakresów.
Span reprezentuje pojedynczą jednostkę pracy wykonaną w systemie rozproszonym, rejestrującą czas rozpoczęcia i zakończenia. Zakresy często mają między sobą relacje hierarchiczne – na ilustracji poniżej wszystkie mniejsze zakresy są zakresami podrzędnymi dużego zakresu /messages i są łączone w jeden ślad, który pokazuje ścieżkę pracy w systemie.

Google Cloud Trace to jedna z opcji backendu śledzenia rozproszonego, która jest dobrze zintegrowana z innymi usługami Google Cloud.
Co utworzysz
W tym ćwiczeniu w Codelabs zinstrumentujesz informacje o logach czasu w usługach o nazwie „Shakespeare application” (czyli Shakesapp), które działają w klastrze Google Kubernetes Engine. Architektura Shakesapp jest opisana poniżej:

- Narzędzie Loadgen wysyła do klienta ciąg zapytania w protokole HTTP.
- Klienci przekazują zapytanie z generatora obciążenia do serwera w gRPC.
- Serwer akceptuje zapytanie od klienta, pobiera wszystkie dzieła Szekspira w formacie tekstowym z Google Cloud Storage, wyszukuje wiersze zawierające zapytanie i zwraca klientowi numer wiersza, który pasuje do zapytania.
Musisz instrumentować informacje o śledzeniu w całym żądaniu. Następnie osadzisz w serwerze agenta profilującego i zbadaj wąskie gardło.
Czego się nauczysz
- Pierwsze kroki z bibliotekami śledzenia OpenTelemetry w projekcie Go
- Jak utworzyć zakres za pomocą biblioteki
- Jak propagować konteksty zakresów w sieci między komponentami aplikacji
- Jak wysyłać dane śledzenia do Cloud Trace
- Jak analizować ślad w Cloud Trace
To ćwiczenie pokazuje, jak instrumentować mikroserwisy. Aby ułatwić zrozumienie, ten przykład zawiera tylko 3 komponenty (generator obciążenia, klient i serwer), ale ten sam proces opisany w tym ćwiczeniu możesz zastosować w przypadku bardziej złożonych i większych systemów.
Czego potrzebujesz
- Podstawowa znajomość języka Go
- Podstawowa znajomość Kubernetes
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
Jeśli nie masz jeszcze konta Google (Gmail lub Google Apps), musisz je utworzyć. Zaloguj się w konsoli Google Cloud Platform ( console.cloud.google.com) i utwórz nowy projekt.
Jeśli masz już projekt, kliknij menu wyboru projektu w lewym górnym rogu konsoli:

i w wyświetlonym oknie kliknij przycisk „NOWY PROJEKT”, aby utworzyć nowy projekt:

Jeśli nie masz jeszcze projektu, powinien wyświetlić się taki dialog, w którym możesz utworzyć pierwszy projekt:

W kolejnym oknie dialogowym tworzenia projektu możesz wpisać szczegóły nowego projektu:

Zapamiętaj identyfikator projektu, który jest unikalną nazwą we wszystkich projektach Google Cloud (podana powyżej nazwa jest już zajęta i nie będzie działać w Twoim przypadku). W dalszej części tych ćwiczeń z programowania będzie on nazywany PROJECT_ID.
Następnie, jeśli jeszcze tego nie zrobisz, musisz włączyć płatności w Konsoli deweloperów, aby korzystać z zasobów Google Cloud, i włączyć Cloud Trace API.

Wykonanie tego samouczka nie powinno kosztować więcej niż kilka dolarów, ale może okazać się droższe, jeśli zdecydujesz się wykorzystać więcej zasobów lub pozostawisz je uruchomione (patrz sekcja „Czyszczenie” na końcu tego dokumentu). Ceny Google Cloud Trace, Google Kubernetes Engine i Google Artifact Registry są podane w oficjalnej dokumentacji.
- Cennik pakietu operacyjnego Google Cloud | Operations Suite
- Cennik | Dokumentacja Kubernetes Engine
- Cennik Artifact Registry | Dokumentacja Artifact Registry
Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD, co powinno sprawić, że to ćwiczenie w Codelabs będzie całkowicie bezpłatne.
Konfiguracja Google Cloud Shell
Z Google Cloud i Google Cloud Trace można korzystać zdalnie na laptopie, ale w tym ćwiczeniu użyjemy Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.
Ta maszyna wirtualna oparta na Debianie zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Oznacza to, że do ukończenia tego ćwiczenia potrzebujesz tylko przeglądarki (działa ona na Chromebooku).
Aby aktywować Cloud Shell w konsoli Cloud, kliknij Aktywuj Cloud Shell
(udostępnienie środowiska i połączenie się z nim powinno zająć tylko kilka chwil).


Po połączeniu z Cloud Shell zobaczysz, że uwierzytelnianie zostało już przeprowadzone, a projekt jest już ustawiony na Twój identyfikator projektu PROJECT_ID.
gcloud auth list
Wynik polecenia
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Wynik polecenia
[core] project = <PROJECT_ID>
Jeśli z jakiegoś powodu projekt nie jest ustawiony, po prostu wydaj to polecenie:
gcloud config set project <PROJECT_ID>
Szukasz urządzenia PROJECT_ID? Sprawdź, jakiego identyfikatora użyto w krokach konfiguracji, lub wyszukaj go w panelu konsoli Cloud:

Cloud Shell domyślnie ustawia też niektóre zmienne środowiskowe, które mogą być przydatne podczas wykonywania kolejnych poleceń.
echo $GOOGLE_CLOUD_PROJECT
Wynik polecenia
<PROJECT_ID>
Na koniec ustaw domyślną strefę i konfigurację projektu.
gcloud config set compute/zone us-central1-f
Możesz wybrać różne strefy. Więcej informacji znajdziesz w artykule Regiony i strefy.
Przejdź do konfiguracji języka
W tym ćwiczeniu używamy języka Go we wszystkich kodach źródłowych. Uruchom to polecenie w Cloud Shell i sprawdź, czy wersja Go to 1.17 lub nowsza.
go version
Wynik polecenia
go version go1.18.3 linux/amd64
Konfigurowanie klastra Google Kubernetes
W tym ćwiczeniu uruchomisz klaster mikroserwisów w Google Kubernetes Engine (GKE). W tym ćwiczeniu wykonasz te czynności:
- Pobieranie projektu podstawowego do Cloud Shell
- Tworzenie mikroserwisów w kontenerach
- Przesyłanie kontenerów do Google Artifact Registry (GAR)
- Wdrażanie kontenerów w GKE
- Modyfikowanie kodu źródłowego usług na potrzeby instrumentacji śledzenia
- Przejdź do kroku 2
Włącz Kubernetes Engine
Najpierw skonfigurujemy klaster Kubernetes, w którym Shakesapp będzie działać w GKE, więc musimy włączyć GKE. Otwórz menu „Kubernetes Engine” i kliknij przycisk WŁĄCZ.

Teraz możesz utworzyć klaster Kubernetes.
Utwórz klaster Kubernetes
Aby utworzyć klaster Kubernetes, uruchom w Cloud Shell to polecenie: Sprawdź, czy wartość strefy znajduje się w regionie, którego użyjesz do utworzenia repozytorium Artifact Registry. Zmień wartość strefy us-central1-f, jeśli region repozytorium nie obejmuje tej strefy.
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
Wynik polecenia
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
Konfigurowanie Artifact Registry i Skaffold
Mamy już klaster Kubernetes gotowy do wdrożenia. Następnie przygotowujemy rejestr kontenerów do przesyłania i wdrażania kontenerów. W tych krokach musimy skonfigurować Artifact Registry (GAR) i skaffold, aby z nich korzystać.
Konfiguracja Artifact Registry
Przejdź do menu „Artifact Registry” i kliknij przycisk WŁĄCZ.

Po chwili zobaczysz przeglądarkę repozytorium GAR. Kliknij przycisk „UTWÓRZ REPOZYTORIUM” i wpisz nazwę repozytorium.

W tym ćwiczeniu nadam nowemu repozytorium nazwę trace-codelab. Format artefaktu to „Docker”, a typ lokalizacji to „Region”. Wybierz region zbliżony do tego, który został ustawiony jako domyślna strefa Google Compute Engine. W tym przykładzie powyżej wybrano „us-central1-f”, więc tutaj wybieramy „us-central1 (Iowa)”. Następnie kliknij przycisk „UTWÓRZ”.

W przeglądarce repozytorium zobaczysz teraz „trace-codelab”.

Później wrócimy tutaj, aby sprawdzić ścieżkę rejestru.
Konfiguracja Skaffold
Skaffold to przydatne narzędzie podczas tworzenia mikroserwisów działających w Kubernetes. Za pomocą kilku poleceń obsługuje przepływ pracy związany z tworzeniem, przesyłaniem i wdrażaniem kontenerów aplikacji. Skaffold domyślnie używa Docker Registry jako rejestru kontenerów, więc musisz skonfigurować Skaffold, aby rozpoznawał GAR podczas przesyłania kontenerów.
Ponownie otwórz Cloud Shell i sprawdź, czy narzędzie Skaffold jest zainstalowane. (Cloud Shell domyślnie instaluje Skaffold w środowisku). Uruchom to polecenie i sprawdź wersję Skaffold.
skaffold version
Wynik polecenia
v1.38.0
Teraz możesz zarejestrować domyślne repozytorium, z którego będzie korzystać narzędzie Skaffold. Aby uzyskać ścieżkę rejestru, przejdź do panelu Artifact Registry i kliknij nazwę repozytorium, które zostało skonfigurowane w poprzednim kroku.

U góry strony zobaczysz wtedy elementy menu nawigacyjnego. Kliknij ikonę
, aby skopiować ścieżkę rejestru do schowka.

Po kliknięciu przycisku kopiowania u dołu przeglądarki pojawi się okno z komunikatem podobnym do tego:
Skopiowano „us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab”
Wróć do Cloud Shell. Uruchom polecenie skaffold config set default-repo z wartością skopiowaną z panelu.
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
Wynik polecenia
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
Musisz też skonfigurować rejestr w konfiguracji Dockera. Uruchom to polecenie:
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
Wynik polecenia
{
"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
Możesz teraz przejść do następnego kroku, czyli skonfigurowania kontenera Kubernetes w GKE.
Podsumowanie
W tym kroku skonfigurujesz środowisko do ćwiczeń z programowania:
- Konfigurowanie Cloud Shell
- Utworzono repozytorium Artifact Registry dla rejestru kontenerów.
- Konfigurowanie narzędzia Skaffold do korzystania z repozytorium kontenerów
- utworzyć klaster Kubernetes, w którym będą działać mikroserwisy z ćwiczenia;
Dalsze czynności
W następnym kroku skompilujesz, wypchniesz i wdrożysz mikrousługi w klastrze.
3. Tworzenie, wypychanie i wdrażanie mikroserwisów
Pobieranie materiałów do ćwiczeń z programowania
W poprzednim kroku skonfigurowaliśmy wszystkie wymagania wstępne dotyczące tego ćwiczenia. Teraz możesz uruchomić na nich całe mikroserwisy. Materiały do ćwiczeń w Codelabs są przechowywane na serwerze w GitHubie, więc pobierz je do środowiska powłoki Cloud Shell za pomocą tego polecenia git:
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
Struktura katalogów projektu jest następująca:
.
├── 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: pliki manifestu Kubernetes
- proto: definicja proto komunikacji między klientem a serwerem
- src: katalogi z kodem źródłowym poszczególnych usług;
- skaffold.yaml: plik konfiguracyjny Skaffold
W tym ćwiczeniu zaktualizujesz kod źródłowy znajdujący się w folderze step0. Odpowiedzi znajdziesz też w kodzie źródłowym w folderach step[1-6]. (Część 1 obejmuje kroki 0–4, a część 2 – kroki 5 i 6).
Uruchom polecenie Skaffold
Teraz możesz utworzyć, przesłać i wdrożyć całą zawartość w utworzonym właśnie klastrze Kubernetes. Może się wydawać, że proces ten składa się z wielu kroków, ale w rzeczywistości skaffold wykonuje wszystko za Ciebie. Wypróbujmy to za pomocą tego polecenia:
cd step0 skaffold dev
Po uruchomieniu polecenia zobaczysz dane wyjściowe dziennika docker build i będziesz mieć pewność, że zostały one przesłane do rejestru.
Wynik polecenia
... ---> 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
Po przesłaniu wszystkich kontenerów usług wdrożenia Kubernetes uruchamiają się automatycznie.
Wynik polecenia
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
Po wdrożeniu w każdym kontenerze zobaczysz rzeczywiste dzienniki aplikacji emitowane do stdout, np. w ten sposób:
Wynik polecenia
[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
Pamiętaj, że w tym momencie chcesz zobaczyć wszystkie wiadomości z serwera. OK, teraz możesz zacząć instrumentować aplikację za pomocą OpenTelemetry, aby śledzić rozproszone usługi.
Zanim zaczniesz instrumentować usługę, wyłącz klaster, naciskając Ctrl+C.
Wynik polecenia
...
[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
Podsumowanie
Na tym etapie przygotowujesz materiały do ćwiczeń w swoim środowisku i sprawdzasz, czy skaffold działa zgodnie z oczekiwaniami.
Dalsze czynności
W następnym kroku zmodyfikujesz kod źródłowy usługi loadgen, aby instrumentować informacje o śledzeniu.
4. Instrumentacja HTTP
Koncepcja instrumentacji i propagacji śladów
Zanim zaczniesz edytować kod źródłowy, krótko wyjaśnię, jak działają ślady rozproszone, na prostym diagramie.

W tym przykładzie instrumentujemy kod, aby eksportować informacje o śladach i zakresach do Cloud Trace oraz propagować kontekst śledzenia w żądaniu z usługi loadgen do usługi serwera.
Aplikacje muszą wysyłać metadane logu czasu, takie jak identyfikator logu czasu i identyfikator spanu, aby Cloud Trace mógł zebrać wszystkie spany o tym samym identyfikatorze logu czasu w jeden log czasu. Aplikacja musi też propagować konteksty śledzenia (połączenie identyfikatora śledzenia i identyfikatora spanu spanu nadrzędnego) podczas wysyłania żądań do usług podrzędnych, aby mogły one wiedzieć, który kontekst śledzenia obsługują.
OpenTelemetry pomaga:
- generować unikalne identyfikatory logu czasu i zakresu,
- eksportować identyfikator logu czasu i identyfikator zakresu do backendu,
- przekazywać konteksty śledzenia do innych usług,
- do osadzania dodatkowych metadanych, które pomagają analizować ślady.
Komponenty w śladzie OpenTelemetry

Proces instrumentacji śledzenia aplikacji za pomocą OpenTelemetry wygląda tak:
- Tworzenie eksportera
- Utwórz obiekt TracerProvider, który wiąże eksportera w 1 i ustawia go jako globalny.
- Ustaw TextMapPropagaror, aby określić metodę propagacji.
- Pobieranie obiektu Tracer z obiektu TracerProvider
- Generowanie zakresu z obiektu Tracer
Na razie nie musisz rozumieć szczegółowych właściwości każdego komponentu, ale najważniejsze rzeczy, o których musisz pamiętać, to:
- Eksportujący może być podłączony do TracerProvider.
- TracerProvider zawiera całą konfigurację dotyczącą próbkowania i eksportowania logów czasu.
- wszystkie logi czasu są zgrupowane w obiekcie Tracer.
Mając to na uwadze, przejdźmy do właściwego kodowania.
Instrument first span
Usługa generatora obciążenia instrumentu
Otwórz edytor Cloud Shell, klikając przycisk
w prawym górnym rogu Cloud Shell. Otwórz step0/src/loadgen/main.go w eksploratorze w panelu po lewej stronie i znajdź funkcję główną.
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++
}
}
W funkcji głównej zobaczysz pętlę, w której wywoływana jest funkcja run. W obecnej implementacji sekcja zawiera 2 wiersze dziennika, które rejestrują początek i koniec wywołania funkcji. Teraz zinstrumentujmy informacje o spanie, aby śledzić opóźnienie wywołania funkcji.
Najpierw, jak wspomnieliśmy w poprzedniej sekcji, skonfigurujmy całą usługę OpenTelemetry. Dodaj pakiety OpenTelemetry w ten sposób:
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
)
Aby zwiększyć czytelność, tworzymy funkcję konfiguracji o nazwie initTracer i wywołujemy ją w funkcji main.
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
}
Możesz zauważyć, że procedura konfigurowania OpenTelemetry jest taka sama jak w poprzedniej sekcji. W tej implementacji używamy stdouteksportera, który eksportuje wszystkie informacje o śledzeniu do stdout w uporządkowanym formacie.
Następnie wywołujesz ją z funkcji głównej. Wywołaj funkcję initTracer() i pamiętaj, aby wywołać funkcję TracerProvider.Shutdown() po zamknięciu aplikacji.
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)
...
Po zakończeniu konfiguracji musisz utworzyć zakres z unikalnym identyfikatorem śledzenia i identyfikatorem zakresu. OpenTelemetry udostępnia do tego celu przydatną bibliotekę. Dodaj dodatkowe nowe pakiety do klienta HTTP instrumentu.
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"
)
Generator obciążenia wywołuje usługę klienta w HTTP za pomocą funkcji net/http w runQuery, dlatego używamy pakietu contrib dla net/http i włączamy instrumentację za pomocą rozszerzenia pakietu httptrace i otelhttp.
Najpierw dodaj globalną zmienną pakietu httpClient, aby wywoływać żądania HTTP za pomocą instrumentowanego klienta.
step0/src/loadgen/main.go
var httpClient = http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport)
}
Następnie dodaj instrumentację w funkcji runQuery, aby utworzyć niestandardowy zakres za pomocą OpenTelemetry i automatycznie wygenerowany zakres z niestandardowego klienta HTTP. Co musisz zrobić:
- Uzyskaj Tracer z globalnego
TracerProviderza pomocąotel.Tracer() - Tworzenie głównego zakresu za pomocą metody
Tracer.Start() - Zakończ główny span w dowolnym momencie (w tym przypadku na końcu funkcji
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)
}
Instrumentacja w narzędziu do generowania obciążenia (aplikacji klienta HTTP) została zakończona. Pamiętaj, aby zaktualizować go.mod i go.sum za pomocą polecenia go mod.
go mod tidy
Obsługa klienta dotycząca instrumentów
W poprzedniej sekcji zaimplementowaliśmy część zaznaczoną na czerwono na rysunku poniżej. W usłudze generatora obciążenia zaimplementowaliśmy informacje o zakresie. Podobnie jak w przypadku usługi generatora obciążenia musimy teraz instrumentować usługę klienta. Różnica w porównaniu z usługą generatora obciążenia polega na tym, że usługa klienta musi wyodrębnić informacje o identyfikatorze śledzenia propagowane z usługi generatora obciążenia w nagłówku HTTP i użyć tego identyfikatora do wygenerowania zakresów.

Otwórz edytor Cloud Shell i dodaj wymagane pakiety, tak jak w przypadku usługi generatora obciążenia.
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
)
Ponownie musimy skonfigurować OpenTelemetry. Skopiuj i wklej funkcję initTracer z narzędzia loadgen i wywołaj ją w funkcji main usługi klienta.
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
}
Teraz czas na instrumentację zakresów. Usługa klienta musi akceptować żądania HTTP z usługi loadgen, dlatego musi instrumentować moduł obsługi. Serwer HTTP w usłudze klienta jest zaimplementowany za pomocą pakietu net/http. Możesz użyć pakietu otelhttp, tak jak w przypadku generatora obciążenia.
Najpierw zastąpimy rejestrację modułu obsługi modułem otelhttp. W funkcji main znajdź wiersze, w których procedura obsługi HTTP jest rejestrowana za pomocą http.HandleFunc().
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)
Następnie instrumentujemy rzeczywisty zakres w obsłudze. Znajdź funkcję func (*clientService) handler() i dodaj instrumentację zakresu za pomocą trace.SpanFromContext().
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
...
Dzięki temu instrumentowi uzyskasz zakresy od początku do końca metody handler. Aby ułatwić analizowanie zakresów, dodaj do zapytania dodatkowy atrybut, który będzie przechowywać liczbę dopasowań. Tuż przed wierszem dziennika dodaj ten kod.
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))
...
Po wykonaniu wszystkich powyższych czynności masz już gotowe śledzenie między narzędziem do generowania obciążenia a klientem. Zobaczmy, jak to działa. Ponownie uruchom kod za pomocą narzędzia Skaffold.
skaffold dev
Po pewnym czasie, gdy usługi będą działać w klastrze GKE, zobaczysz dużą liczbę komunikatów logu podobnych do tego:
Wynik polecenia
[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"
...
stdout eksporter wysyła te wiadomości. Zauważysz, że elementy nadrzędne wszystkich spanów wygenerowanych przez loadgen mają wartość TraceID: 00000000000000000000000000000000, ponieważ jest to span główny, czyli pierwszy span w logu czasu. Zauważysz też, że atrybut osadzania "query" zawiera ciąg zapytania przekazywany do usługi klienta.
Podsumowanie
W tym kroku skonfigurowano usługę generatora obciążenia i usługę klienta, które komunikują się za pomocą protokołu HTTP, oraz potwierdzono, że można skutecznie propagować kontekst śledzenia między usługami i eksportować informacje o zakresie z obu usług do standardowego wyjścia.
Dalsze czynności
W następnym kroku zinstrumentujesz usługę klienta i usługę serwera, aby sprawdzić, jak propagować kontekst śledzenia za pomocą gRPC.
5. Instrumentacja gRPC
W poprzednim kroku zaimplementowaliśmy pierwszą połowę żądania w tych mikroserwisach. W tym kroku spróbujemy instrumentować komunikację gRPC między usługą klienta a usługą serwera. (Zielony i fioletowy prostokąt na obrazie poniżej)

Wstępne tworzenie instrumentacji klienta gRPC
Ekosystem OpenTelemetry oferuje wiele przydatnych bibliotek, które pomagają deweloperom w instrumentacji aplikacji. W poprzednim kroku użyliśmy wstępnie utworzonych instrumentów dla pakietu net/http. W tym kroku, ponieważ próbujemy propagować kontekst śledzenia za pomocą gRPC, używamy do tego biblioteki.
Najpierw zaimportuj gotowy pakiet gRPC o nazwie 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"
)
Tym razem usługa klienta jest klientem gRPC w stosunku do usługi serwera, więc musisz wyposażyć klienta gRPC w instrumentację. Znajdź funkcję mustConnGRPC i dodaj przechwytujące gRPC, które instrumentują nowe zakresy za każdym razem, gdy klient wysyła żądania do serwera.
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))
}
}
Nie musisz tego robić, ponieważ usługa OpenTelemetry została już skonfigurowana w poprzedniej sekcji.
Gotowa instrumentacja dla serwera gRPC
Podobnie jak w przypadku klienta gRPC, wywołujemy gotową instrumentację serwera gRPC. Dodaj nowy pakiet do sekcji importu, np.:
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"
)
Ponieważ jest to pierwsze instrumentowanie serwera, musisz najpierw skonfigurować OpenTelemetry, podobnie jak w przypadku usług loadgen i klienta.
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
...
Następnie musisz dodać przechwytujące serwery. W funkcji main znajdź miejsce, w którym wywoływana jest funkcja grpc.NewServer(), i dodaj do niej przechwytujące.
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)
...
Uruchom mikroserwis i potwierdź log czasu
Następnie uruchom zmodyfikowany kod za pomocą polecenia skaffold.
skaffold dev
Ponownie zobaczysz w stdout wiele informacji o spanie.
Wynik polecenia
...
[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] }
...
Zauważysz, że nie masz osadzonych nazw zakresów i ręcznie utworzonych zakresów za pomocą trace.Start() lub span.SpanFromContext(). Nadal jednak otrzymujesz dużą liczbę zakresów, ponieważ zostały one wygenerowane przez przechwytujące gRPC.
Podsumowanie
W tym kroku zintegrowaliśmy komunikację opartą na gRPC z bibliotekami ekosystemu OpenTelemetry.
Dalsze czynności
W następnym kroku wreszcie wizualizujesz ślad za pomocą Cloud Trace i dowiesz się, jak analizować zebrane zakresy.
6. Wizualizacja śladu za pomocą Cloud Trace
W całym systemie masz instrumentację logów czasu za pomocą OpenTelemetry. Wiesz już, jak instrumentować usługi HTTP i gRPC. Wiesz już, jak je instrumentować, ale nie wiesz jeszcze, jak je analizować. W tej sekcji zastąpisz eksportery stdout eksporterami Cloud Trace i dowiesz się, jak analizować ślady.
Używanie eksportera Cloud Trace
Jedną z najważniejszych cech OpenTelemetry jest możliwość podłączania różnych komponentów. Aby wizualizować wszystkie zakresy zebrane przez instrumentację, wystarczy zastąpić eksporter stdout eksporterem Cloud Trace.
Otwórz pliki main.go każdej usługi i znajdź funkcję initTracer(). Usuń wiersz, aby wygenerować eksporter stdout, i zamiast niego utwórz eksporter Cloud Trace.
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
}
Musisz też edytować tę samą funkcję w usłudze klienta i serwera.
Uruchom mikroserwis i potwierdź log czasu
Po wprowadzeniu zmian uruchom klaster jak zwykle za pomocą polecenia skaffold.
skaffold dev
W formacie dzienników strukturalnych nie widzisz już wielu informacji o zakresie w stdout, ponieważ eksporter został zastąpiony eksporterem Cloud Trace.
Wynik polecenia
[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
...
Teraz sprawdźmy, czy wszystkie zakresy zostały prawidłowo wysłane do Cloud Trace. Otwórz konsolę Cloud i przejdź do „Listy logów czasu”. Jest łatwo dostępna z poziomu pola wyszukiwania. Możesz też kliknąć menu w panelu po lewej stronie. 
Następnie zobaczysz wiele niebieskich punktów rozmieszczonych na wykresie opóźnień. Każdy punkt reprezentuje jeden log czasu.

Kliknij jeden z nich, aby wyświetlić szczegóły w śladzie. 
Już po tym krótkim przeglądzie możesz wyciągnąć wiele wniosków. Na przykład na wykresie kaskadowym widać, że przyczyną opóźnienia jest głównie zakres o nazwie shakesapp.ShakespeareService/GetMatchCount. (Patrz punkt 1 na obrazie powyżej). Możesz to sprawdzić w tabeli z podsumowaniem. (W kolumnie po prawej widać czas trwania każdego zakresu). Poza tym ten ślad dotyczył zapytania „friend”. (Patrz punkt 2 na obrazku powyżej).
Na podstawie tych krótkich analiz możesz dojść do wniosku, że potrzebujesz bardziej szczegółowych zakresów w metodzie GetMatchCount. W porównaniu z informacjami stdout wizualizacja jest bardzo przydatna. Więcej informacji o szczegółach logu czasu Cloud Trace znajdziesz w naszej oficjalnej dokumentacji.
Podsumowanie
W tym kroku zastąpiliśmy eksporter stdout eksporterem Cloud Trace i wyświetliliśmy ślady w Cloud Trace. Dowiedzieliśmy się też, jak rozpocząć analizę śladów.
Dalsze czynności
W następnym kroku zmodyfikujesz kod źródłowy usługi serwera, aby dodać podrzędny zakres w funkcji GetMatchCount.
7. Dodawanie podzakresu w celu lepszej analizy
W poprzednim kroku udało Ci się ustalić, że przyczyną czasu oczekiwania w przypadku narzędzia do generowania obciążenia jest głównie proces w metodzie GetMatchCount, czyli w obsłudze gRPC w usłudze serwera. Nie mamy jednak żadnych innych danych poza danymi modułu obsługi, więc nie możemy wyciągnąć z wykresu kaskadowego żadnych dodatkowych wniosków. Jest to częsty przypadek, gdy zaczynamy instrumentować mikroserwisy.

W tej sekcji będziemy instrumentować podzakres, w którym serwer wywołuje Google Cloud Storage, ponieważ często zdarza się, że niektóre zewnętrzne operacje wejścia-wyjścia sieciowego trwają długo, a ważne jest, aby sprawdzić, czy to wywołanie jest przyczyną.
Instrumentowanie podzakresu na serwerze
Otwórz main.go na serwerze i znajdź funkcję readFiles. Ta funkcja wysyła żądanie do Google Cloud Storage, aby pobrać wszystkie pliki tekstowe z dzieł Szekspira. W tej funkcji możesz utworzyć podzakres, tak jak w przypadku instrumentacji serwera HTTP w usłudze klienta.
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
...
To wszystko, jeśli chodzi o dodawanie nowego zakresu. Zobaczmy, jak to działa, uruchamiając aplikację.
Uruchom mikroserwis i potwierdź log czasu
Po wprowadzeniu zmian uruchom klaster jak zwykle za pomocą polecenia skaffold.
skaffold dev
Następnie wybierz z listy ślad o nazwie query.request. Zobaczysz podobny wykres kaskadowy śladu, ale z nowym zakresem w sekcji shakesapp.ShakespeareService/GetMatchCount. (Zakres zaznaczony poniżej czerwonym prostokątem)

Z tego wykresu możesz teraz odczytać, że zewnętrzne wywołanie Google Cloud Storage zajmuje dużo czasu, ale większość opóźnienia powodują inne czynniki.
Już po kilku spojrzeniach na wykres kaskadowy śladu możesz uzyskać wiele informacji. Jak uzyskać w aplikacji bardziej szczegółowe informacje o skuteczności? W tym momencie przydaje się program profilujący, ale na razie zakończymy to ćwiczenie w Codelabs i przeniesiemy wszystkie samouczki dotyczące programu profilującego do części 2.
Podsumowanie
W tym kroku utworzyliśmy kolejny zakres w usłudze serwera i uzyskaliśmy dodatkowe informacje o opóźnieniu systemu.
8. Gratulacje
Udało Ci się utworzyć rozproszone logi czasu za pomocą OpenTelemetry i potwierdzić opóźnienia żądań w mikrousłudze w Cloud Trace.
W przypadku rozbudowanych ćwiczeń możesz samodzielnie wypróbować te tematy.
- Obecna implementacja wysyła wszystkie zakresy wygenerowane przez kontrolę stanu. (
grpc.health.v1.Health/Check) Jak odfiltrować te zakresy ze śladów Cloud Trace? Wskazówka jest tutaj. - Powiąż dzienniki zdarzeń z zakresami i zobacz, jak to działa w Google Cloud Trace i Google Cloud Logging. Wskazówka jest tutaj.
- Zastąp niektóre usługi usługami w innym języku i spróbuj je instrumentować za pomocą OpenTelemetry w tym języku.
Jeśli po tym chcesz dowiedzieć się więcej o profilerze, przejdź do części 2. W takim przypadku możesz pominąć sekcję zwalniania miejsca poniżej.
Czyszczenie danych
Po ukończeniu tego ćwiczenia zatrzymaj klaster Kubernetes i usuń projekt, aby uniknąć nieoczekiwanych opłat za Google Kubernetes Engine, Google Cloud Trace i Google Artifact Registry.
Najpierw usuń klaster. Jeśli klaster jest uruchomiony za pomocą polecenia skaffold dev, wystarczy nacisnąć Ctrl-C. Jeśli klaster jest uruchomiony za pomocą skaffold run, uruchom to polecenie:
skaffold delete
Wynik polecenia
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
Po usunięciu klastra w panelu menu wybierz „IAM i administracja” > „Ustawienia”, a następnie kliknij przycisk „WYŁĄCZ”.

Następnie w formularzu w oknie wpisz identyfikator projektu (nie nazwę projektu) i potwierdź wyłączenie.