Zwiększanie wydajności aplikacji w Go (część 2: narzędzie do profilowania)

1. Wprowadzenie

e0509e8a07ad5537.png

Ostatnia aktualizacja: 14.07.2022 r.

Dostrzegalność aplikacji

Dostrzegalność i ciągłe profilowanie

Dostrzegalność to termin opisujący atrybut układu. System z obserwowalnością umożliwia zespołom aktywne debugowanie systemu. W tym kontekście 3 filary dostrzegalności: logi, wskaźniki i logi czasu to podstawowe narzędzia umożliwiające systemowi uzyskiwanie dostrzegalności.

Profilowanie ciągłe jest również kolejnym kluczowym elementem dostrzegalności, które oprócz 3 filarów dostrzegalności zwiększa bazę użytkowników w branży. Cloud Profiler to jeden z pierwszych aplikacji, który zapewnia łatwy w obsłudze interfejs służący do analizy wskaźników wydajności w stosach wywołań aplikacji.

Ćwiczenie w Codelabs jest częścią 2 tej serii i opisuje konfigurowanie agenta do profilowania ciągłego. Część 1 dotyczy śledzenia rozproszonego za pomocą OpenTelemetry i Cloud Trace, a w części 1 dowiesz się więcej o tym, jak lepiej identyfikować wąskie gardła mikroserwisów.

Co utworzysz

W tym ćwiczeniu w Codelabs dowiesz się, jak wdrożyć agenta ciągłego profilowania w usłudze serwera aplikacji Szekspira. (inaczej Shakesapp), która działa w klastrze Google Kubernetes Engine. Architektura Shakesapp:

44e243182ced442f.png

  • Loadgen wysyła ciąg zapytania do klienta w HTTP
  • Klienty przekazują zapytanie z polecenia loadgen do serwera w gRPC
  • Serwer akceptuje zapytanie od klienta, pobiera z Google Cloud Storage wszystkie dzieła Shakespare w formacie tekstowym, przeszukuje wiersze zawierające zapytanie i zwraca numer wiersza pasującego do klienta.

W części 1 stwierdziliśmy, że wąskie gardło istnieje gdzieś w usłudze serwera, ale nie udało Ci się ustalić dokładnej przyczyny.

Czego się nauczysz

  • Jak umieścić agenta profilującego
  • Jak zbadać szyję butelki w Cloud Profiler

Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak wdrożyć w aplikacji agenta ciągłego profilującego.

Czego potrzebujesz

  • Podstawowa znajomość języka Go
  • Podstawowa znajomość technologii Kubernetes

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

Jeśli nie masz jeszcze konta Google (w Gmailu 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 kliknij „NOWY PROJEKT”. w wyświetlonym oknie, by utworzyć nowy projekt:

7136b3ee36ebaf89.png

Jeśli nie masz jeszcze projektu, zobaczysz takie okno dialogowe umożliwiające utworzenie pierwszego:

870a3cbd6541ee86.png

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

affdc444517ba805.png

Zapamiętaj identyfikator projektu, który jest niepowtarzalną nazwą we wszystkich projektach Google Cloud (powyższa nazwa jest już zajęta i nie będzie Ci odpowiadać). W dalszej części tego ćwiczenia z programowania będzie on określany jako PROJECT_ID.

Następnie musisz włączyć płatności w Developers Console, aby korzystać z zasobów Google Cloud i włączyć Cloud Trace API.

15d0ef27a8fbab27.png

Wykonanie tych ćwiczeń w programie nie powinno kosztować więcej niż kilka dolarów, ale może być droższe, jeśli zdecydujesz się na więcej zasobów lub pozostawisz je włączone (patrz sekcja „Czyszczenie” na końcu tego dokumentu). Ceny Google Cloud Trace, Google Kubernetes Engine i Google Artifact Registry podano w oficjalnej dokumentacji.

Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnego okresu próbnego w wysokości 300 USD, dzięki czemu te ćwiczenia z programowania są całkowicie bezpłatne.

Konfiguracja Google Cloud Shell

Usługi Google Cloud i Google Cloud Trace można obsługiwać zdalnie z poziomu laptopa, ale w tym ćwiczeniu z programowania wykorzystamy Google Cloud Shell – środowisko wiersza poleceń działające 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 zwiększa wydajność sieci i uwierzytelnianie. Oznacza to, że do tego ćwiczenia z programowania wystarczy przeglądarka (tak, działa ona na Chromebooku).

Aby aktywować Cloud Shell z poziomu konsoli Cloud, kliknij Aktywuj Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (udostępnienie środowiska i połączenie z nim powinno zająć tylko chwilę).

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

Zrzut ekranu 2017-06-14 o 10.13.43 PM.png

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest już uwierzytelniony, a projekt jest już ustawiony na PROJECT_ID.

gcloud auth list

Dane wyjściowe polecenia

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

Dane wyjściowe polecenia

[core]
project = <PROJECT_ID>

Jeśli z jakiegoś powodu projekt nie jest skonfigurowany, uruchom po prostu to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź identyfikator użyty w krokach konfiguracji lub wyszukaj go w panelu Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell ustawia też domyślnie niektóre zmienne środowiskowe, które mogą być przydatne podczas uruchamiania kolejnych poleceń.

echo $GOOGLE_CLOUD_PROJECT

Dane wyjściowe 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.

Skonfiguruj język

W tym ćwiczeniu w Codelabs cały kod źródłowy używamy w języku Go. Uruchom to polecenie w Cloud Shell i sprawdź, czy wersja Go to 1.17 lub nowsza

go version

Dane wyjściowe polecenia

go version go1.18.3 linux/amd64

Konfigurowanie klastra Google Kubernetes

W ramach tego ćwiczenia w Codelabs uruchomisz klaster mikroserwisów w Google Kubernetes Engine (GKE). Procedura w ramach ćwiczenia z programowania wygląda tak:

  1. Pobierz projekt bazowy 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 narzędzi śledzenia
  6. Przejdź do kroku 2

Włączanie Kubernetes Engine

Najpierw skonfigurowaliśmy klaster Kubernetes, w którym aplikacja Shakesapp działa w GKE, więc musimy włączyć GKE. Przejdź do menu „Kubernetes Engine” i naciśnij przycisk WŁĄCZ.

548cfd95bc6d344d.png

Teraz możesz utworzyć klaster Kubernetes.

Tworzenie klastra Kubernetes

Aby utworzyć klaster Kubernetes, uruchom w Cloud Shell to polecenie. Potwierdź, że wartość strefy znajduje się w regionie, który będzie używany do tworzenia 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

Dane wyjściowe 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 skaffold

Klaster Kubernetes jest teraz gotowy do wdrożenia. W kolejnym kroku przygotujemy rejestr kontenerów do wypychania i wdrażania kontenerów. Aby wykonać te czynności, musimy skonfigurować Artifact Registry (GAR) i skaffold, aby z niego korzystać.

Konfiguracja Artifact Registry

Przejdź do menu „Artifact Registry” i naciśnij przycisk WŁĄCZ.

45e384b87f7cf0db.png

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

d6a70f4cb4ebcbe3.png

W ramach tego ćwiczenia w Codelabs nadam nowemu repozytorium nazwę trace-codelab. Format artefaktu to „Docker” a typ lokalizacji to „Region”. Wybierz region bliski temu, który został ustawiony dla domyślnej strefy Google Compute Engine. W tym przykładzie wybrano „us-central1-f”. powyżej, więc tutaj wybieramy „us-central1 (Iowa)”. Następnie kliknij przycisk „UTWÓRZ”, Przycisk

9c2d1ce65258ef70.png

Teraz widzisz „trace-codelab”. w przeglądarce repozytoriów.

7a3c1f47346bea15.png

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

Konfiguracja Skaffold

Skaffold to przydatne narzędzie podczas tworzenia mikroserwisów działających w Kubernetes. Obsługuje przepływ pracy związany z tworzeniem, wypychaniem i wdrażaniem kontenerów aplikacji za pomocą niewielkiego zestawu poleceń. Skaffold domyślnie używa Docker Registry jako rejestru kontenerów, dlatego musisz skonfigurować skaffold tak, aby rozpoznawał GAR przy przekazywaniu kontenerów do kontenerów.

Ponownie otwórz Cloud Shell i sprawdź, czy narzędzie skaffold jest zainstalowane. Cloud Shell domyślnie instaluje skaffold w środowisku. Uruchom następujące polecenie, aby wyświetlić wersję skaffold.

skaffold version

Dane wyjściowe polecenia

v1.38.0

Teraz możesz zarejestrować domyślne repozytorium, którego chcesz używać dla skaffold. Aby uzyskać ścieżkę rejestru, przejdź do panelu Artifact Registry i kliknij nazwę repozytorium utworzonego w poprzednim kroku.

7a3c1f47346bea15.png

Następnie na górze strony zobaczysz ścieżki menu nawigacyjnego. Kliknij ikonę e157b1359c3edc06.png, aby skopiować ścieżkę rejestru do schowka.

e0f2ae2144880b8b.png

Po kliknięciu przycisku kopiowania na dole okna przeglądarki pojawi się okno z komunikatem takim jak:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; została skopiowana

Wróć do Cloud Shell. Uruchom polecenie skaffold config set default-repo z wartością skopiowaną właśnie z panelu.

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

Dane wyjściowe 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 pod kątem konfiguracji Dockera. Uruchom to polecenie:

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

Dane wyjściowe 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 konfigurowania kontenera Kubernetes w GKE.

Podsumowanie

W tym kroku skonfigurujesz środowisko ćwiczeń z programowania:

  • Konfigurowanie Cloud Shell
  • Utworzono repozytorium Artifact Registry dla rejestru kontenerów
  • Skonfiguruj skaffold, aby korzystać z rejestru kontenerów
  • Utworzono klaster Kubernetes, w którym działają mikroserwisy ćwiczeń z programowania

Dalsze czynności

W następnym kroku w usłudze serwera wdrożysz agenta ciągłego profilowania.

3. Tworzenie, przekazywanie i wdrażanie mikroserwisów

Pobierz materiały do ćwiczeń z programowania

W poprzednim kroku skonfigurowaliśmy wszystkie wymagania wstępne tego ćwiczenia z programowania. Teraz możesz uruchamiać na nich całe mikroserwisy. Materiały do ćwiczeń z programowania 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 wygląda tak:

.
├── 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
  • pliki manifestu: pliki manifestu Kubernetes.
  • proto: definicja protokołu dla komunikacji między klientem a serwerem
  • src: katalogi kodu źródłowego poszczególnych usług
  • skaffold.yaml: plik konfiguracji dla skaffold

W ramach tego ćwiczenia w Codelabs zaktualizujesz kod źródłowy znajdujący się w folderze step4. Możesz też zapoznać się z kodem źródłowym w step[1-6] folderach, aby uzyskać informacje o zmianach od początku. (część 1 dotyczy kroków od kroków 0 do kroków 4, a część 2 dotyczy kroków 5 i 6);

Uruchom polecenie skaffold

Możesz też zacząć kompilować, przenosić i wdrażać całe treści w utworzonym właśnie klastrze Kubernetes. Wygląda na to, że składa się z wielu kroków, ale w rzeczywistości skaffold robi wszystko za Ciebie. Spróbujmy to zrobić za pomocą tego polecenia:

cd step4
skaffold dev

Natychmiast po uruchomieniu polecenia zostaną wyświetlone dane wyjściowe dziennika docker build i będziesz mieć pewność, że zostały one przekazane do rejestru.

Dane wyjściowe 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 przekazaniu wszystkich kontenerów usługi wdrożenia Kubernetes uruchamiają się automatycznie.

Dane wyjściowe 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 rzeczywiste logi aplikacji wysyłane do stdout w poszczególnych kontenerach w następujący sposób:

Dane wyjściowe 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

Na tym etapie chcesz wyświetlić wszystkie wiadomości z serwera. OK, na koniec możesz zacząć instrumentować swoją aplikację za pomocą OpenTelemetry na potrzeby rozproszonego śledzenia usług.

Zanim zaczniesz instrumentować usługę, wyłącz klaster, naciskając klawisze Ctrl + C.

Dane wyjściowe 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 masz już przygotowane materiały z ćwiczeń z programowania w swoim środowisku oraz potwierdzone uruchomienia skaffold zgodnie z oczekiwaniami.

Dalsze czynności

W następnym kroku zmodyfikujesz kod źródłowy usługi loadgen, aby dostosować dane śledzenia.

4. Narzędzia agenta Cloud Profiler

Pojęcie profilowania ciągłego

Zanim objaśnimy pojęcie profilowania ciągłego, musimy zrozumieć, czym jest profilowanie. Profilowanie to jeden ze sposobów dynamicznego analizowania aplikacji (dynamiczna analiza programu). Jest on zwykle wykonywany podczas tworzenia aplikacji, testowania jej obciążenia itd. To jednorazowe działanie mające na celu pomiar wskaźników systemowych, takich jak wykorzystanie procesora i pamięci, w wybranym okresie. Po zebraniu danych do profilu deweloperzy analizują je bezpośrednio w kodzie.

Profilowanie ciągłe to rozszerzona metoda profilowania normalnego: okresowo uruchamia profile krótkich okien w przypadku długotrwałej aplikacji i gromadzi sporą ilość danych profilowych. Następnie automatycznie generuje analizę statystyczną na podstawie określonego atrybutu aplikacji, takiego jak numer wersji, strefa wdrożenia, czas pomiaru itd. Więcej informacji na ten temat znajdziesz w naszej dokumentacji.

Ponieważ celem jest uruchomiona aplikacja, można okresowo gromadzić dane profilowe i wysyłać je do backendu, który następnie przetwarza dane statystyczne. To jest agent Cloud Profiler, który wkrótce umieścisz w usłudze serwera.

Umieszczanie agenta Cloud Profiler

Otwórz edytor Cloud Shell, naciskając przycisk 776a11bfb2122549.png w prawym górnym rogu Cloud Shell. Otwórz step4/src/server/main.go w eksploratorze w panelu po lewej stronie i znajdź funkcję główną.

step4/src/server/main.go

func main() {
        ...
        // step2. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        svc := NewServerService()
        // step2: add interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        srv := grpc.NewServer(
                grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
                grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
        )
        // step2: end adding interceptor
        shakesapp.RegisterShakespeareServiceServer(srv, svc)
        healthpb.RegisterHealthServer(srv, svc)
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("error serving server: %v", err)
        }
}

W funkcji main jest widoczny kod konfiguracji dla OpenTelemetry i gRPC, który został wykonany w części 1 ćwiczenia w Codelabs. Teraz dodasz tutaj instrumentację dla agenta Cloud Profiler. Podobnie jak w przypadku funkcji initTracer(), możesz napisać funkcję o nazwie initProfiler(), aby uzyskać czytelność.

step4/src/server/main.go

import (
        ...
        "cloud.google.com/go/profiler" // step5. add profiler package
        "cloud.google.com/go/storage"
        ...
)

// step5: add Profiler initializer
func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.0.0",
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Przyjrzyjmy się opcjom określonym w obiekcie profiler.Config{}.

  • Usługa: nazwa usługi, którą możesz wybrać i włączyć w panelu narzędzia do profilowania.
  • ServiceVersion (Wersja usługi): nazwa wersji usługi. Na podstawie tej wartości możesz porównywać zbiory danych profilu.
  • NoHeapProfiling: wyłącz profilowanie wykorzystania pamięci
  • NoAllocProfiling: wyłącz profilowanie alokacji pamięci
  • NoGoroutineProfiling: wyłącz profilowanie goruoutine.
  • NoCPUProfiling: wyłącz profilowanie procesora

W tym ćwiczeniu w Codelabs włączamy tylko profilowanie procesora.

Teraz musisz wywołać tę funkcję w funkcji main. Pamiętaj, aby zaimportować pakiet Cloud Profiler do bloku importu.

step4/src/server/main.go

func main() {
        ...
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        // step5. start profiler
        go initProfiler()
        // step5. end

        svc := NewServerService()
        // step2: add interceptor
        ...
}

Pamiętaj, że wywołujesz funkcję initProfiler() za pomocą słowa kluczowego go. Ponieważ zasada profiler.Start() blokuje działanie, trzeba je uruchomić w innym kodzie. Możesz go teraz utworzyć. Pamiętaj, aby przed wdrożeniem uruchomić go mod tidy.

go mod tidy

Teraz wdróż klaster z nową usługą serwera.

skaffold dev

Wykres płomienia w Cloud Profiler pojawia się zwykle po kilku minutach. Wpisz „profiler” w polu wyszukiwania u góry i kliknij ikonę programu profilującego.

3d8ca8a64b267a40.png

Następnie zobaczysz następujący wykres płomieni.

7f80797dddc0128d.png

Podsumowanie

W tym kroku osadzono agenta Cloud Profiler w usłudze serwera i potwierdziłeś, że generuje ono wykres płomieniowy.

Dalsze czynności

W następnym kroku za pomocą wykresu płomieniowego zbadasz przyczynę wąskiego gardła w aplikacji.

5. Analizowanie wykresu płomieniowego Cloud Profiler

Czym jest wykres płomieniowy?

Wykres płomieniowy to jeden ze sposobów wizualizacji danych profilowych. Szczegółowe wyjaśnienie można znaleźć w naszym dokumencie. Jego krótkie podsumowanie to:

  • Każdy słupek opisuje wywołanie metody/funkcji w aplikacji.
  • Kierunek pionowy to stos wywołań. stos wywołań rośnie od góry do dołu
  • Kierunek poziomy to wykorzystanie zasobów. im dłuższe, tym gorsze.

Spójrzmy na uzyskany wykres płomieniowy.

7f80797dddc0128d.png

Analizowanie wykresu płomienia

W poprzedniej sekcji informowaliśmy, że każdy słupek na wykresie płomieniowym wyraża wywołanie funkcji/metody, a jego długość oznacza wykorzystanie zasobów przez funkcję/metodę. Wykres płomienia w Cloud Profiler sortuje słupek w porządku malejącym lub według długości od lewej do prawej. Możesz zacząć od lewego górnego rogu wykresu.

6d90760c6c1183cd.png

W naszym przypadku wyraźnie widać, że funkcja grpc.(*Server).serveStreams.func1.2 zużywa najwięcej czasu procesora, a po przeanalizowaniu stosu wywołań od góry do dołu widać, że większość czasu jest poświęcana przez main.(*serverService).GetMatchCount, który jest modułem obsługi serwera gRPC w usłudze serwera.

W sekcji GetMatchCount znajdziesz szereg funkcji wyrażeń regularnych: regexp.MatchString i regexp.Compile. Pochodzą one z pakietu standardowego: oznacza to, że powinny być dobrze przetestowane pod wieloma względami, w tym także pod względem skuteczności. Wynik pokazuje jednak, że wykorzystanie zasobów czasu procesora jest wysokie w regexp.MatchString i regexp.Compile. Biorąc pod uwagę te fakty, zakładamy, że użycie właściwości regexp.MatchString ma związek z problemami z wydajnością. Spójrzmy więc na kod źródłowy, w którym jest używana ta funkcja.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line, query := strings.ToLower(line), strings.ToLower(req.Query)
                        isMatch, err := regexp.MatchString(query, line)
                        if err != nil {
                                return resp, err
                        }
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

To miejsce, w którym nazywa się regexp.MatchString. Czytając kod źródłowy, możesz zauważyć, że funkcja jest wywoływana wewnątrz zagnieżdżonej pętli for. Dlatego jej użycie może być nieprawidłowe. Wyszukajmy element GoDoc dotyczący elementu wyrażenie regularne.

80b8a4ba1931ff7b.png

Zgodnie z dokumentem w każdym wywołaniu funkcja regexp.MatchString kompiluje wzorzec wyrażenia regularnego. Tak więc przyczyna dużego zużycia zasobów brzmi tak.

Podsumowanie

W tym kroku przyjęliśmy założenie o przyczynie zużycia zasobów, analizując graf płomieniowy.

Dalsze czynności

W następnym kroku zaktualizujesz kod źródłowy usługi serwera i potwierdzisz zmianę z wersji 1.0.0.

6. Zaktualizować kod źródłowy i rozróżnić grafy płomieniowe

Aktualizowanie kodu źródłowego

W poprzednim kroku przyjęto założenie, że użycie regexp.MatchString ma związek z dużym zużyciem zasobów. Rozwiążmy ten problem. Otwórz kod i trochę go zmień.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }

        // step6. considered the process carefully and naively tuned up by extracting
        // regexp pattern compile process out of for loop.
        query := strings.ToLower(req.Query)
        re := regexp.MustCompile(query)
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line = strings.ToLower(line)
                        isMatch := re.MatchString(line)
                        // step6. done replacing regexp with strings
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Jak widać, proces kompilacji wzorca wyrażeń regularnych jest teraz wyodrębniany z tabeli regexp.MatchString i wycofywany z zagnieżdżonej pętli „for”.

Zanim wdrożysz ten kod, pamiętaj o zaktualizowaniu ciągu znaków wersji w funkcji initProfiler().

step4/src/server/main.go

func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.1.0", // step6. update version
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Zobaczmy, jak to działa. Wdróż klaster za pomocą polecenia skaffold.

skaffold dev

Po chwili załaduj ponownie panel Cloud Profiler i sprawdź, jak to działa.

283cfcd4c13716ad.png

Pamiętaj, aby zmienić wersję na "1.1.0", tak aby widoczne były tylko profile z wersji 1.1.0. Jak widać, słupek GetMatchCount został zmniejszony, a współczynnik wykorzystania czasu pracy procesora (tj. słupek skrócił się).

e3a1456b4aada9a5.png

Nie tylko patrząc na wykres płomieniowy pojedynczej wersji, możesz także porównać różnice między dwiema wersjami.

841dec77d8ba5595.png

Zmiana wartości opcji „Porównaj z” z listy „Wersja” i zmienić wartość „Porównywana wersja” do wersji oryginalnej „1.0.0”.

5553844292d6a537.png

Zobaczysz taki wykres płomieniowy. Kształt wykresu jest taki sam jak w przypadku wersji 1.1.0, ale kolory są inne. W trybie porównawczym kolory to:

  • Niebieski: wartość (wykorzystanie zasobów) zmniejszona
  • Pomarańczowy: uzyskana wartość (wykorzystanie zasobów).
  • Szary: neutralny

Mając na uwadze legendę, przyjrzyjmy się bliżej tej funkcji. Po kliknięciu paska, który chcesz powiększyć, zobaczysz więcej szczegółów w grupie. Kliknij pasek main.(*serverService).GetMatchCount. Najeżdżając kursorem na słupek, zobaczysz także szczegóły porównania.

ca08d942dc1e2502.png

Oznacza to, że łączny czas pracy procesora został zmniejszony z 5,26 s do 2,88 s (łącznie 10 s = okno próbkowania). To ogromna poprawa!

Teraz możesz zwiększyć wydajność swojej aplikacji dzięki analizie danych profilowych.

Podsumowanie

W tym kroku wprowadziliśmy zmianę w usłudze serwera i potwierdziliśmy poprawę w trybie porównania Cloud Profiler.

Dalsze czynności

W następnym kroku zaktualizujesz kod źródłowy usługi serwera i potwierdzisz zmianę z wersji 1.0.0.

7. Dodatkowy krok: sprawdź ulepszenie kaskady śledzenia

Różnica między logiem rozproszonym a profilowaniem ciągłym

W części 1 ćwiczeń z programowania udało Ci się potwierdzić, że jesteś w stanie znaleźć usługę wąskiego gardła we wszystkich mikroserwisach na ścieżce żądań i że nie jesteś w stanie ustalić dokładnej przyczyny wąskiego gardła w konkretnej usłudze. Z części 2 w Codelabs wiesz, że profilowanie ciągłe umożliwia wykrywanie wąskiego gardła w obrębie pojedynczej usługi na podstawie stosu wywołań.

W tym kroku przyjrzyjmy się wykresowi kaskadowemu na podstawie rozproszonego logu czasu (Cloud Trace) i zobaczmy różnicę w stosunku do profilowania ciągłego.

Ten wykres kaskadowy to jeden ze śladów z zapytaniem „miłość”. Łącznie zajmuje to około 6,7 s (6700 ms).

e2b7dec25926ee51.png

A to wszystko po ulepszeniu tego samego zapytania. Całkowity czas oczekiwania wynosi teraz 1, 5 s (1500 ms), co jest ogromnym usprawnieniem w porównaniu z poprzednią implementacją.

feeb7207f36c7e5e.png

Pamiętaj, że na wykresie kaskadowym rozproszonego logu czasu informacje o stosie wywołań nie są dostępne, chyba że przyrządzane są rozpiętości dookoła. Również rozproszone ślady koncentrują się tylko na czasie oczekiwania w usługach, natomiast profilowanie ciągłe skupia się na zasobach komputerowych (CPU, pamięć, wątki systemu operacyjnego) jednej usługi.

W innym aspekcie rozproszony log czasu stanowi podstawę zdarzeń, a profil ciągły ma charakter statystyczny. Każdy log czasu ma inny wykres czasu oczekiwania. Aby poznać trend zmian czasu oczekiwania, potrzebujesz innego formatu, np. dystrybucji.

Podsumowanie

W tym kroku sprawdzisz różnicę między logiem rozproszonym a profilowaniem ciągłym.

8. Gratulacje

Udało Ci się utworzyć rozproszone logi czasu przy użyciu OpenTelemery i potwierdzone opóźnienia żądań w mikroserwisie w Google Cloud Trace.

W przypadku dłuższych ćwiczeń możesz samodzielnie wypróbować poniższe tematy.

  • Obecna implementacja wysyła wszystkie spany wygenerowane przez kontrolę stanu. (grpc.health.v1.Health/Check) W jaki sposób odfiltrowujesz te spany z Cloud Trace? Wskazówka znajdziesz tutaj.
  • Skoreluj logi zdarzeń ze spanami i sprawdź, jak działa to w Google Cloud Trace i Google Cloud Logging. Wskazówka znajdziesz tutaj.
  • Zastąp jakąś usługę usługą w innym języku i spróbuj dopasować ją do usługi OpenTelemetry dla tego języka.

Jeśli chcesz dowiedzieć się więcej o narzędziu do profilowania, przejdź do części 2. W takim przypadku możesz pominąć sekcję Czyszczenie znajdującą się poniżej.

Czyszczenie danych

Po ukończeniu tego ćwiczenia w Codelabs zatrzymaj klaster Kubernetes i pamiętaj o usunięciu projektu, aby nie otrzymywać nieoczekiwanych opłat w Google Kubernetes Engine, Google Cloud Trace czy Google Artifact Registry.

Najpierw usuń klaster. Jeśli używasz klastra skaffold dev, wystarczy nacisnąć Ctrl + C. Jeśli używasz klastra z wersją skaffold run, uruchom to polecenie:

skaffold delete

Dane wyjściowe 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 „Uprawnienia Administrator > „Ustawienia”, a następnie kliknij „WYŁĄCZ” Przycisk

45aa37b7d5e1ddd1.png

Następnie wpisz identyfikator projektu (nie nazwę projektu) w formularzu w oknie i potwierdź zamknięcie.