Zwiększanie wydajności aplikacji w Go (część 1: śledzenie)

1. Wprowadzenie

505827108874614d.png

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.

Log czasu

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:

44e243182ced442f.png

  • 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:

7a32e5469db69e9.png

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

7136b3ee36ebaf89.png

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

870a3cbd6541ee86.png

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

affdc444517ba805.png

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.

15d0ef27a8fbab27.png

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.

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 gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (udostępnienie środowiska i połączenie się z nim powinno zająć tylko kilka chwil).

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

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

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:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

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:

  1. Pobieranie projektu podstawowego do Cloud Shell
  2. Tworzenie mikroserwisów w kontenerach
  3. Przesyłanie kontenerów do Google Artifact Registry (GAR)
  4. Wdrażanie kontenerów w GKE
  5. Modyfikowanie kodu źródłowego usług na potrzeby instrumentacji śledzenia
  6. 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.

548cfd95bc6d344d.png

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.

45e384b87f7cf0db.png

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

d6a70f4cb4ebcbe3.png

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

9c2d1ce65258ef70.png

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

7a3c1f47346bea15.png

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.

7a3c1f47346bea15.png

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

e0f2ae2144880b8b.png

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.

6be42e353b9bfd1d.png

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

b01f7bb90188db0d.png

Proces instrumentacji śledzenia aplikacji za pomocą OpenTelemetry wygląda tak:

  1. Tworzenie eksportera
  2. Utwórz obiekt TracerProvider, który wiąże eksportera w 1 i ustawia go jako globalny.
  3. Ustaw TextMapPropagaror, aby określić metodę propagacji.
  4. Pobieranie obiektu Tracer z obiektu TracerProvider
  5. 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 776a11bfb2122549.pngw 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/httprunQuery, dlatego używamy pakietu contrib dla net/http i włączamy instrumentację za pomocą rozszerzenia pakietu httptraceotelhttp.

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ć:

  1. Uzyskaj Tracer z globalnego TracerProvider za pomocą otel.Tracer()
  2. Tworzenie głównego zakresu za pomocą metody Tracer.Start()
  3. 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.modgo.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.

bcaccd06691269f8.png

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.

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

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)

75310d8e0e3b1a30.png

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. 8b3f8411bd737e06.png

Następnie zobaczysz wiele niebieskich punktów rozmieszczonych na wykresie opóźnień. Każdy punkt reprezentuje jeden log czasu.

3ecf131423fc4c40.png

Kliknij jeden z nich, aby wyświetlić szczegóły w śladzie. 4fd10960c6648a03.png

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.

3b63a1e471dddb8c.png

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)

3d4a891aa30d7a32.png

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

45aa37b7d5e1ddd1.png

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