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

1. Wprowadzenie

505827108874614d.png

Ostatnia aktualizacja: 15.07.2022

Dostrzegalność aplikacji

Obserwowalność i OpenTelemetry

Termin „obserwowalność” odnosi się do atrybutu systemu. System z możliwością monitorowania umożliwia zespołom aktywne debugowanie systemu. W tym kontekście 3 filary obserwowalności: logi, dane i ślady to podstawowe elementy, które umożliwiają systemowi uzyskiwanie możliwości obserwowalności.

OpenTelemetry to zestaw specyfikacji, bibliotek i agentów, które przyspieszają instrumentację i eksport danych telemetrycznych (logów, wskaźników i śladów), których wymaga obserwowalność. OpenTelemetry to projekt oparty na otwartym standardzie i społeczności, który jest wspierany przez CNCF. Korzystając z bibliotek udostępnianych przez projekt i jego ekosystem, deweloperzy mogą instrumentować swoje aplikacje w sposób neutralny dla dostawcy i w różnych architekturach.

Oprócz 3 filarów obserwowalności kolejną kluczową funkcją jest ciągłe profilowanie, które poszerza bazę użytkowników w branży. Narzędzie Cloud Profiler jest jednym z pierwszych i zapewnia łatwy interfejs do analizowania danych o wydajności w zbiorach wywołań aplikacji.

To pierwsze z serii laboratoriów programistycznych, które obejmuje rejestrowanie rozproszonych dzienników w mikroserwisach za pomocą OpenTelemetry i Cloud Trace. Część 2 będzie dotyczyć profilowania ciągłego za pomocą narzędzia Cloud Profiler.

Rozproszony śledzenie

Wśród logów, danych i śladów ślad to telemetria, która informuje o opóźnieniu w konkretnej części procesu w systemie. Zwłaszcza w erze mikroserwisów rozproszone śledzenie jest skutecznym sposobem na znajdowanie wąskich gardeł powodujących opóźnienia w całym rozproszonych systemie.

Podczas analizowania rozproszonych ścieżek wizualizacja danych ścieżki jest kluczowa, aby w skrócie poznać ogólne opóźnienia systemu. W rozproszonej funkcji śledzenia obsługujemy zestaw wywołań, aby przetworzyć pojedyncze żądanie do punktu wejścia systemu w postaci ścieżki zawierającej wiele elementów Span.

Przedział reprezentuje pojedynczą jednostkę pracy wykonaną w rozproszonych systemach, rejestrując czas rozpoczęcia i zakończenia. Elementy te często mają hierarchiczną relację między sobą – na obrazku poniżej wszystkie mniejsze elementy są elementami podrzędnymi dużego elementu /messages i są połączone w jeden ślad, który pokazuje ścieżkę pracy przez system.

log czasu,

Google Cloud Trace to jedna z opcji backendu rozproszonego śledzenia, która jest dobrze zintegrowana z innymi usługami w Google Cloud.

Co utworzysz

W tym laboratorium programistycznym spróbujesz zinstrumentować informacje o wyświetleniu w usługach o nazwie „Shakespeare application” (czyli Shakesapp), które działają w klastrze Google Kubernetes Engine. Architektura Shakesapp wygląda tak:

44e243182ced442f.png

  • Loadgen wysyła ciąg znaków zapytania do klienta w formacie HTTP
  • Klienci przekazują zapytanie od generatora obciążenia do serwera w formacie 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 numer wiersza, który pasuje do klienta.

Musisz zaimplementować informacje o prześledowaniu w ramach żądania. Następnie umieść na serwerze agenta profilującego i zbadaj wąskie gardło.

Czego się nauczysz

  • Jak zacząć korzystać z bibliotek OpenTelemetry Trace w projekcie Go
  • Jak utworzyć przedział za pomocą biblioteki
  • Jak propagować konteksty za pomocą protokołu między komponentami aplikacji
  • Jak wysyłać dane monitorowania do Cloud Trace
  • Jak analizować ślad w Cloud Trace

W tych ćwiczeniach z programowania znajdziesz informacje o tym, jak instrumentować mikrousługi. Aby ułatwić zrozumienie tego przykładu, zawiera on tylko 3 komponenty (generator obciążenia, klient i serwer), ale możesz zastosować opisane w tym laboratorium kodu instrukcje do bardziej złożonych i większych systemów.

Czego potrzebujesz

  • podstawowa znajomość języka Go;
  • podstawową wiedzę o Kubernetes;

2. Konfiguracja i wymagania

Konfiguracja środowiska w samodzielnym tempie

Jeśli nie masz jeszcze konta Google (Gmail lub Google Apps), utwórz je. Zaloguj się w konsoli Google Cloud Platform ( console.cloud.google.com) i utwórz nowy projekt.

Jeśli masz już projekt, w lewym górnym rogu konsoli kliknij menu wyboru projektu:

7a32e5469db69e9.png

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

7136b3ee36ebaf89.png

Jeśli nie masz jeszcze projektu, wyświetli się okno podobne do tego:

870a3cbd6541ee86.png

W kolejnych oknach tworzenia projektu możesz podać szczegóły nowego projektu:

affdc444517ba805.png

Zapamiętaj identyfikator projektu, który jest unikalną nazwą we wszystkich projektach Google Cloud (podane powyżej imię i nazwisko jest już zajęte i nie będzie działać). W dalszej części tego ćwiczenia będzie on oznaczany jako IDENTYFIKATOR_PROJEKTU.

Następnie, jeśli jeszcze tego nie zrobiono, musisz włączyć rozliczenia w Konsoli programistów, aby korzystać z zasobów Google Cloud, i włączyć interfejs Cloud Trace API.

15d0ef27a8fbab27.png

Przeprowadzenie tego ćwiczenia 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 „Oczyszczanie” 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, dzięki czemu to laboratorium kodu jest całkowicie bezpłatne.

Konfiguracja Google Cloud Shell

Google Cloud i Google Cloud Trace można obsługiwać zdalnie z laptopa, ale w tym laboratorium kodu będziemy używać 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 stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie poprawia wydajność sieci i uwierzytelnianie. Oznacza to, że do tego ćwiczenia będziesz potrzebować tylko przeglądarki (tak, działa to 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 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 powinieneś zobaczyć, że jesteś już uwierzytelniony i że projekt jest już ustawiony na 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 uruchom to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź, jakiego identyfikatora użyto w procesie konfiguracji, lub odszukaj go w panelu Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell domyślnie ustawia też niektóre zmienne środowiskowe, co może 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.

Konfigurowanie języka

W tym laboratorium kodu używamy Go do całego kodu źródłowego. 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 laboratorium programistycznym uruchomisz klaster mikroserwisów w Google Kubernetes Engine (GKE). Proces w tym ćwiczeniu obejmuje te czynności:

  1. Pobieranie projektu referencyjnego 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. zmodyfikować kod źródłowy usług w celu przeprowadzenia instrumentacji śledzonej,
  6. Przejdź do kroku 2

Włącz Kubernetes Engine

Najpierw skonfigurujemy klaster Kubernetes, w którym Shakesapp 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.

Tworzenie klastra 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 rejestru artefaktów. Zmień wartość strefy us-central1-f, jeśli region repozytorium nie obejmuje danej 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

Konfiguracja Artifact Registry i skaffolda

Klaster Kubernetes jest już gotowy do wdrożenia. Następnie przygotowujemy rejestr kontenerów do przesyłania i wdrażania kontenerów. Aby wykonać te czynności, musimy skonfigurować rejestr artefaktów (GAR) i użyć skaffolda.

Konfiguracja Artifact Registry

Otwórz menu „Rejestr artefaktów” i kliknij przycisk WŁĄCZ.

45e384b87f7cf0db.png

Po chwili zobaczysz przeglądarkę repozytorium usługi Google Analytics Reporting API. Kliknij przycisk „UTWÓRZ REPOSITORYUM” i wpisz nazwę repozytorium.

d6a70f4cb4ebcbe3.png

W tym laboratorium kodu nazwałem nowe repozytorium 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. Na przykład w tym przykładzie wybrano „us-central1-f”, więc tutaj wybierzemy „us-central1 (Iowa)”. Następnie kliknij przycisk „Utwórz”.

9c2d1ce65258ef70.png

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

7a3c1f47346bea15.png

Wrócimy tutaj później, aby sprawdzić ścieżkę rejestru.

Konfiguracja Skaffold

Skaffold to przydatne narzędzie do tworzenia mikroserwisów działających w Kubernetes. Za pomocą niewielkiego zestawu poleceń obsługuje przepływ pracy obejmujący tworzenie, przesyłanie i wdrażanie 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.

Otwórz ponownie Cloud Shell i sprawdź, czy skaffold jest zainstalowany. (Cloud Shell domyślnie instaluje skaffold w środowisku). Uruchom to polecenie, aby sprawdzić wersję skaffold.

skaffold version

Wynik polecenia

v1.38.0

Teraz możesz zarejestrować domyślne repozytorium, z którego będzie korzystać skaffold. Aby uzyskać ścieżkę rejestru, otwórz panel Artifact Registry i kliknij nazwę repozytorium skonfigurowanego w poprzednim kroku.

7a3c1f47346bea15.png

Następnie u góry strony zobaczysz 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:

„us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab” został skopiowany

Wróć do Cloud Shell. Uruchom polecenie skaffold config set default-repo z wartością, którą właśnie skopiowałeś/skopiowałaś 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 zgodnie z konfiguracją 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

Teraz możesz przejść do następnego kroku, czyli skonfigurować kontener Kubernetes w GKE.

Podsumowanie

W tym kroku skonfigurujesz środowisko Codelab:

  • Konfigurowanie Cloud Shell
  • Utworzono repozytorium Artifact Registry dla rejestru kontenerów.
  • Konfigurowanie skaffolda do używania repozytorium kontenerów
  • Utworzono klaster Kubernetes, w którym działają mikroserwisy codelab.

Następny krok

W następnym kroku skompilujesz, prześlesz i wdrożysz mikroserwisy w klastrze.

3. Kompilowanie, wypychanie i wdrażanie mikroserwisów

Pobierz materiały z ćwiczeń z programowania

W poprzednim kroku skonfigurowaliśmy wszystkie wymagania wstępne dla tego ćwiczenia. Teraz możesz uruchamiać na nich całe mikroserwisy. Materiały z Codelab są hostowane na GitHubie, więc pobierz je do środowiska 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 protokołu dla komunikacji między klientem a serwerem
  • src: katalogi z kodem źródłowym poszczególnych usług
  • skaffold.yaml: plik konfiguracji skaffold

W tym ćwiczeniu z programowania zaktualizujesz kod źródłowy znajdujący się w folderze step0. Aby uzyskać odpowiedzi na te pytania, możesz też przejrzeć kod źródłowy w folderach step[1-6]. (część 1 obejmuje kroki od 0 do 4, a część 2 – kroki 5 i 6).

Uruchom polecenie skaffold

Teraz możesz skompilować, przesłać i wdrożyć wszystkie treści do utworzonego właśnie klastra Kubernetes. Brzmi to jak kilka kroków, ale skaffold zrobi wszystko za Ciebie. Spróbujmy tego za pomocą tego polecenia:

cd step0
skaffold dev

Po uruchomieniu polecenia zobaczysz dane wyjściowe docker build i możesz potwierdzić, ż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 Kubernetesa 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 zobaczysz w każdym kontenerze rzeczywiste dzienniki aplikacji emitowane do stdout w taki 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. Właśnie możesz zacząć instrumentować aplikację za pomocą OpenTelemetry w celu rozproszonego śledzenia usług.

Zanim zaczniesz instrumentować usługę, wyłącz klaster za pomocą 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 przygotowałeś materiały z codelab w swoim środowisku i sprawdziłeś, czy skaffold działa zgodnie z oczekiwaniami.

Następny krok

W następnym kroku zmodyfikujesz kod źródłowy usługi loadgen, aby uwzględnić w nim informacje o śledzeniu.

4. Instrumentacja dla HTTP

Koncepcja pomiaru i rozprzestrzeniania śladów

Zanim zaczniesz edytować kod źródłowy, wyjaśnię Ci na prostym diagramie, jak działają rozproszone ścieżki.

6be42e353b9bfd1d.png

W tym przykładzie kod jest instrumentowany w celu eksportowania informacji o śledzeniu i przekazaniu ich do Cloud Trace oraz propagowania kontekstu śledzenia w ramach żądania od usługi loadgen do usługi serwera.

Aplikacje muszą wysyłać metadane śledzenia, takie jak identyfikator śledzenia i identyfikator zakresu, aby Cloud Trace mógł połączyć wszystkie zakresy, które mają ten sam identyfikator śledzenia, w jedno śledzenie. Aplikacja musi też rozpowszechniać konteksty śledzenia (kombinację identyfikatora ścieżki i identyfikatora zakresu nadrzędnego) w usługach, które wysyłają żądania, aby te usługi wiedziały, który kontekst śledzenia obsługują.

OpenTelemetry umożliwia:

  • do generowania unikalnego identyfikatora logu czasu i identyfikatora zakresu;
  • eksportować identyfikator ścieżki i identyfikator zakresu do backendu;
  • rozpowszechniać konteksty śledzone do innych usług;
  • dołączać dodatkowe metadane, które pomagają analizować ścieżki;

Komponenty w śladzie OpenTelemetry

b01f7bb90188db0d.png

Proces dodawania do aplikacji ścieżki śladu za pomocą OpenTelemetry:

  1. Tworzenie eksportera
  2. Utwórz obiekt TracerProvider, który wiąże eksporter w 1 i ustaw go globalnie.
  3. Ustaw TextMapPropagaror, aby określić metodę propagacji.
  4. Pobieranie Tracera z TracerProvider
  5. Generowanie zakresu z Tracera

Obecnie nie musisz rozumieć szczegółowych właściwości poszczególnych komponentów, ale pamiętaj o tych najważniejszych:

  • Eksporter jest tutaj podłączany do TracerProvider
  • TracerProvider przechowuje całą konfigurację dotyczącą próbkowania i eksportowania śladów
  • wszystkie ślady są pogrupowane w obiekt Tracer

Po zapoznaniu się z tymi informacjami przejdź do właściwego kodowania.

Pierwsza część instrumentu

Usługa generatora obciążenia instrumentu

Otwórz Edytor Cloud Shell, klikając przycisk 776a11bfb2122549.png w prawym górnym rogu Cloud Shell. W eksploratorze w lewym panelu otwórz step0/src/loadgen/main.go i znajdź główną funkcję.

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 widzisz pętlę wywołującą funkcję run. W obecnej implementacji sekcja zawiera 2 wiersze logowania, które rejestrują początek i koniec wywołania funkcji. Teraz użyjemy informacji z usługi Span do śledzenia opóźnienia wywołania funkcji.

Najpierw, zgodnie z informacjami podanymi w poprzedniej sekcji, skonfigurujmy wszystkie ustawienia 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
)

Ze względu na 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 opisana w poprzedniej sekcji. W tej implementacji używamy eksportera stdout, który eksportuje wszystkie informacje o wyświetleniu do stdout w uporządkowanym formacie.

Następnie wywołujesz ją z funkcji głównej. Po zamknięciu aplikacji zadzwoń do initTracer() i upewnij się, że zadzwoniłeś/zadzwoniłaś do TracerProvider.Shutdown().

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 do klienta HTTP dodatkowe nowe pakiety.

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 z net/http w funkcji runQuery, dlatego używamy pakietu contrib dla net/http i włączamy pomiar z rozszerzeniem httptrace i pakietu otelhttp.

Najpierw dodaj zmienną globalną pakietu httpClient, aby wywoływać żądania HTTP za pomocą zinstrumentowanego 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 przedział za pomocą OpenTelemetry i automatycznie wygenerowanego przedziału z niestandardowego klienta HTTP. Co musisz zrobić:

  1. Uzyskiwanie ścieżki z globalnego TracerProvider z użyciem otel.Tracer()
  2. Utwórz wierzchołk skoku za pomocą metody Tracer.Start()
  3. Zakończ przedział korzenia 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)
        }

Zakończyłeś już pomiary w loadgen (aplikacji klienta HTTP). Zaktualizuj go.modgo.sum za pomocą polecenia go mod.

go mod tidy

Obsługa klienta instrumentu

W poprzedniej sekcji instrumentowaliśmy część zaznaczoną na rysunku poniżej czerwonym prostokątem. W usłudze generatora obciążenia dodaliśmy informacje o przedziałach. Podobnie jak w przypadku usługi generatora obciążenia, teraz musimy zaimplementować usługę klienta. Różnica w stosunku do usługi generatora obciążenia polega na tym, że usługa klienta musi wyodrębnić informacje o identyfikatorze śladu rozpowszechniane przez usługę generatora obciążenia w nagłówku HTTP i użyć tego identyfikatora do generowania elementów śladu.

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. Wystarczy skopiować funkcję initTracer z loadgen i wywoływać ją w funkcji main obsł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
}

Czas na instrumenty klawiszowe. Ponieważ usługa klienta musi akceptować żądania HTTP z usługi loadgen, musi obsłużyć moduł obsługi. Serwer HTTP w usłudze klienta jest implementowany za pomocą pakietu net/http. Możesz użyć pakietu otelhttp, tak jak w przypadku loadgen.

Najpierw zastępujemy rejestrację modułu obsługi modułem obsługi otelhttp. W funkcji main znajdź wiersze, w których moduł obsługi HTTP jest zarejestrowany w funkcji 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 rejestrujemy rzeczywisty zakres wewnątrz modułu obsługi. Znajdź funkcję (*clientService) handler() i dodaj pomiar za pomocą funkcji 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 tej instrumentacji uzyskujesz zakresy od początku do końca metody handler. Aby ułatwić analizowanie zakresów, dodaj do zapytania dodatkowy atrybut, który przechowuje 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 tych czynności masz już zakończone przechwytywanie informacji o wykonywaniu kodu między loadgen a klientami. Zobaczmy, jak to działa. Ponownie uruchom kod za pomocą skaffold.

skaffold dev

Po pewnym czasie uruchamiania usług na klastrze GKE zobaczysz ogromną ilość komunikatów logowania, takich jak ten:

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

Te komunikaty emituje eksporter stdout. Zauważ, że rodzice wszystkich elementów typu „loadgen” mają wartość TraceID: 00000000000000000000000000000000, ponieważ jest to element główny, czyli pierwszy element w pościegu. Widzisz też, że atrybut embed "query" zawiera ciąg zapytania przekazywany do obsługi klienta.

Podsumowanie

Na tym etapie masz zinstrumentowaną usługę generatora obciążenia i usługę klienta, które komunikują się za pomocą HTTP. Potwierdziłeś/Potwierdziłaś, że udało Ci się rozpowszechnić kontekst śladu w usługach i wyeksportować informacje o spanach z obu usług do stdout.

Następny krok

W następnym kroku zaimplementujesz usługę klienta i usługę serwera, aby sprawdzić, jak propagować kontekst śladu za pomocą gRPC.

5. Instrumentacja dla gRPC

W poprzednim kroku zinstrumentowaliśmy pierwszą połowę żądania w tej usłudze mikrousługi. Na tym etapie próbujemy skonfigurować komunikację gRPC między usługą klienta a serwerem. (zielono-fioletowy prostokąt na obrazku poniżej)

75310d8e0e3b1a30.png

Przed budowaniem aplikacji: instrumentacja klienta gRPC

Ekosystem OpenTelemetry oferuje wiele przydatnych bibliotek, które pomagają deweloperom tworzyć aplikacje. W poprzednim kroku użyliśmy instrumentacji przed kompilacją w przypadku pakietu net/http. W tym kroku, ponieważ próbujemy rozpowszechniać kontekst śladu 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 dla usługi serwera, więc musisz zaimplementować klienta gRPC. Znajdź funkcję mustConnGRPC i dodaj przechwytujące gRPC, które będą inicjować nowe przedziały 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.

Wstępnie skompilowane narzędzie pomiarowe dla serwera gRPC

Podobnie jak w przypadku klienta gRPC, w przypadku serwera gRPC użyliśmy gotowego narzędzia do pomiarów. 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ż serwer jest instrumentowany po raz pierwszy, musisz najpierw skonfigurować OpenTelemetry w sposób podobny do tego, który zastosowaliśmy 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 przechwytywacze.

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 mikrousługę i potwierdź ślad

Następnie uruchom zmodyfikowany kod za pomocą polecenia skaffold.

skaffold dev

Ponownie widzisz na wyjściu do standardowego urządzenia wyjściowego wiele informacji o przedziałach.

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 żadnych nazw zakresów ani zakresów utworzonych ręcznie za pomocą elementów trace.Start() lub span.SpanFromContext(). Nadal otrzymujesz dużą liczbę zakresów, ponieważ zostały one wygenerowane przez przechwytniki gRPC.

Podsumowanie

W tym kroku zintegrowałeś komunikację opartą na gRPC z obsługą bibliotek z ekosystemu OpenTelemetry.

Następny krok

W następnym kroku spróbujesz zwizualizować ślad za pomocą Cloud Trace i dowiesz się, jak analizować zebrane zakresy.

6. Wizualizacja ścieżki za pomocą Cloud Trace

masz ścieżki z instrumentacją w całym systemie za pomocą OpenTelemetry; Do tej pory dowiesz się, jak skonfigurować pomiary w przypadku usług HTTP i gRPC. Chociaż wiesz, jak je skonfigurować, nie wiesz, jak je analizować. W tej sekcji zastąpisz eksportery stdout eksporterami Cloud Trace i dowiesz się, jak analizować ścieżki.

Używanie eksportera Cloud Trace

Jedną z potężnych cech OpenTelemetry jest możliwość dołączania. Aby zwizualizować wszystkie przedziały 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
}

Tę samą funkcję musisz też edytować w usłudze klienta i serwera.

Uruchom mikrousługę i potwierdź ślad

Po wprowadzeniu zmian uruchom klaster jak zwykle za pomocą polecenia skaffold.

skaffold dev

Teraz nie widzisz zbyt wielu informacji o przedziałach w formacie logów uporządkowanych na wyjściu standardowym, ponieważ zastąpiono eksporter 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
...

Sprawdźmy teraz, czy wszystkie zakresy są prawidłowo wysyłane do Cloud Trace. Otwórz konsolę Google Cloud i przejdź do „Lista logów czasu”. Dostęp do niego jest bardzo łatwy – wystarczy użyć pola wyszukiwania. Możesz też kliknąć menu w panelu po lewej stronie. 8b3f8411bd737e06.png

Następnie zobaczysz wiele niebieskich punktów rozproszonych na wykresie opóźnień. Każde miejsce odpowiada jednemu śledzeniu.

3ecf131423fc4c40.png

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

Nawet po tym krótkim spojrzeniu zyskasz wiele cennych informacji. Na przykład na wykresie kaskadowym widać, że opóźnienie jest spowodowane głównie przez zakres o nazwie shakesapp.ShakespeareService/GetMatchCount. (patrz 1 na powyższym obrazku). Możesz to sprawdzić w tabeli podsumowania. (Kolumna z prawej strony zawiera czas trwania każdego przedziału). Ponadto to prześledowanie dotyczyło zapytania „friend” (znajomy). (patrz punkt 2 na obrazku powyżej)

Po tej krótkiej analizie możesz stwierdzić, że potrzebujesz bardziej szczegółowych zakresów w ramach metody GetMatchCount. W porównaniu z informacjami na wyjściu standardowym wizualizacja jest bardzo przydatna. Więcej informacji o Cloud Trace znajdziesz w naszej oficjalnej dokumentacji.

Podsumowanie

W tym kroku zastąpiliśmy eksporter stdout eksporterem Cloud Trace i zwizualizowaliśmy ścieżki w Cloud Trace. Dowiedz się też, jak rozpocząć analizowanie śladów.

Następny krok

W następnym kroku zmodyfikujesz kod źródłowy usługi serwera, aby dodać fragment podrzędny w Metodzie GetMatchCount.

7. Dodawanie zakresu podrzędnego w celu ułatwienia analizy

W poprzednim kroku udało Ci się ustalić, że przyczyną czasu podróży w obie strony zarejestrowanego przez loadgen jest głównie proces w metodie GetMatchCount, czyli w obsługującym go programie gRPC w usłudze serwera. Ponieważ nie dodaliśmy żadnych innych niż w obsługującym, nie możemy uzyskać dodatkowych informacji z wykresu kaskadowego. Jest to typowa sytuacja, gdy zaczynamy wdrażać mikrousługi.

3b63a1e471dddb8c.png

W tej sekcji przeanalizujemy podciąg, w którym serwer wywołuje Google Cloud Storage, ponieważ często zdarza się, że niektóre zewnętrzne operacje wejścia/wyjścia sieci zajmują dużo czasu, a my chcemy sprawdzić, czy to właśnie one są tego przyczyną.

Instrumentowanie podciągu na serwerze

Otwórz main.go na serwerze i znajdź funkcję readFiles. Ta funkcja wywołuje żądanie do Google Cloud Storage, aby pobrać wszystkie pliki tekstowe dzieł Szekspira. W tej funkcji możesz utworzyć podsegment, 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 na temat dodawania nowego zakresu. Zobaczmy, jak to działa, gdy uruchomisz aplikację.

Uruchom mikrousługę i potwierdź ślad

Po wprowadzeniu zmian uruchom klaster jak zwykle za pomocą polecenia skaffold.

skaffold dev

Wybierz z listy ścieżek jedną o nazwie query.request. Zobaczysz podobny wykres kaskady śladu, z tym że w sekcji shakesapp.ShakespeareService/GetMatchCount będzie nowy przedział. (przedział zawarty w czerwonym prostokącie poniżej)

3d4a891aa30d7a32.png

Z tego wykresu wynika, że wywołanie zewnętrzne do Google Cloud Storage zajmuje dużo czasu oczekiwania, ale większość czasu zajmują inne operacje.

Już po kilku spojrzeniach na wykres ścieżki udało Ci się uzyskać wiele informacji. Jak uzyskać więcej informacji o skuteczności aplikacji? Tutaj pojawia się profilator, ale na razie zakończmy to ćwiczenie i zostawmy wszystkie samouczki dotyczące profilatora na część 2.

Podsumowanie

W tym kroku zaimplementowałeś/zaimplementowałaś kolejny przedział w usłudze serwera i uzyskałeś/uzyskałaś więcej informacji o opóźnieniach w systemie.

8. Gratulacje

Udało Ci się utworzyć rozproszone ścieżki za pomocą OpenTelemetry i potwierdzić czas oczekiwania na żądania w mikrousłudze w Google Cloud Trace.

Aby poćwiczyć dłuższe ćwiczenia, możesz samodzielnie spróbować się z tych tematów.

  • Obecna implementacja wysyła wszystkie zakresy wygenerowane przez kontrolę stanu. (grpc.health.v1.Health/Check) Jak odfiltrowujesz te przedziały z Cloud Traces? Wskazówka: tutaj.
  • Porównywanie dzienników zdarzeń z zakresami i sprawdzanie, jak to działa w Google Cloud Trace i Google Cloud Logging. Wskazówka: tutaj.
  • Zastąp usługę usługą w innym języku i spróbuj ją skonfigurować za pomocą OpenTelemetry w tym języku.

Jeśli chcesz dowiedzieć się więcej o profilatorze, przejdź do części 2. W takim przypadku możesz pominąć sekcję o czyszczeniu poniżej.

Czyszczenie danych

Po zakończeniu pracy z tym samouczkiem 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 uruchamiasz klaster za pomocą skaffold dev, wystarczy nacisnąć Ctrl+C. Jeśli klaster działa z 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 kliknij „Administracja i zarządzanie” > „Ustawienia”, a następnie przycisk „WYŁĄCZ”.

45aa37b7d5e1ddd1.png

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