Instrumentar para melhorar o desempenho do seu app em Go (parte 1: rastreamento)

1. Introdução

505827108874614d.png

Última atualização:15/07/2022

Observabilidade do aplicativo

Observabilidade e OpenTelemetry

Observabilidade é o termo usado para descrever um atributo de um sistema. Um sistema com observabilidade permite que as equipes depurem ativamente o sistema. Nesse contexto, três pilares da observabilidade (registros, métricas e rastreamentos) são a instrumentação fundamental para que o sistema adquira observabilidade.

O OpenTelemetry é um conjunto de especificações, bibliotecas e agentes que aceleram a instrumentação e a exportação de dados de telemetria (registros, métricas e rastreamentos) necessários para a observabilidade. O OpenTelemetry é um padrão aberto e um projeto orientado pela comunidade da CNCF. Ao usar bibliotecas fornecidas pelo projeto e pelo ecossistema dele, os desenvolvedores podem instrumentar os aplicativos de maneira neutra em relação ao fornecedor e em várias arquiteturas.

Além dos três pilares da observabilidade, a criação de perfis contínuos é outro componente essencial para a observabilidade e está aumentando a base de usuários no setor. O Cloud Profiler é um dos criadores e oferece uma interface fácil para detalhar as métricas de desempenho nas pilhas de chamadas de aplicativos.

Este codelab é a parte 1 da série e aborda a instrumentação de traces distribuídos em microsserviços com o OpenTelemetry e o Cloud Trace. A Parte 2 vai abordar a criação contínua de perfis com o Cloud Profiler.

Rastreamento distribuído

Entre registros, métricas e rastreamentos, o rastreamento é a telemetria que informa a latência de uma parte específica do processo no sistema. Principalmente na era dos microsserviços, o rastreamento distribuído é o principal fator para descobrir gargalos de latência no sistema distribuído geral.

Ao analisar rastreamentos distribuídos, a visualização dos dados é fundamental para entender as latências gerais do sistema rapidamente. No rastreamento distribuído, processamos um conjunto de chamadas para processar uma única solicitação ao ponto de entrada do sistema na forma de um rastreamento que contém vários períodos.

Um intervalo representa uma unidade individual de trabalho realizada em um sistema distribuído, registrando horários de início e término. Os intervalos geralmente têm relações hierárquicas entre si. Na imagem abaixo, todos os intervalos menores são intervalos filhos de um intervalo grande /messages e são reunidos em um trace que mostra o caminho do trabalho em um sistema.

Um trace

O Google Cloud Trace é uma das opções de back-end de rastreamento distribuído e está bem integrado a outros produtos do Google Cloud.

O que você vai criar

Neste codelab, você vai instrumentar informações de rastreamento nos serviços chamados "Shakespeare application" (também conhecido como Shakesapp), que são executados em um cluster do Google Kubernetes Engine. A arquitetura do Shakesapp é descrita abaixo:

44e243182ced442f.png

  • O Loadgen envia uma string de consulta ao cliente em HTTP.
  • Os clientes transmitem a consulta do loadgen para o servidor em gRPC.
  • O servidor aceita a consulta do cliente, busca todas as obras de Shakespeare em formato de texto no Google Cloud Storage, pesquisa as linhas que contêm a consulta e retorna o número da linha correspondente ao cliente.

Você vai instrumentar as informações de rastreamento em toda a solicitação. Depois disso, você vai incorporar um agente de criação de perfil no servidor e investigar o gargalo.

O que você vai aprender

  • Como começar a usar as bibliotecas de rastreamento do OpenTelemetry em um projeto Go
  • Como criar um intervalo com a biblioteca
  • Como propagar contextos de período pela rede entre componentes de apps
  • Como enviar dados de rastreamento para o Cloud Trace
  • Como analisar o rastreamento no Cloud Trace

Este codelab explica como instrumentar seus microsserviços. Para facilitar o entendimento, este exemplo contém apenas três componentes (gerador de carga, cliente e servidor), mas você pode aplicar o mesmo processo explicado neste codelab a sistemas maiores e mais complexos.

O que é necessário

  • Conhecimento básico de Go
  • Conhecimento básico do Kubernetes

2. Configuração e requisitos

Configuração de ambiente autoguiada

Se você ainda não tem uma Conta do Google (Gmail ou Google Apps), crie uma. Faça login no Console do Google Cloud Platform ( console.cloud.google.com) e crie um novo projeto.

Se você já tiver um projeto, clique no menu suspenso de seleção no canto superior esquerdo do console:

7a32e5469db69e9.png

e clique no botão "NEW PROJECT" na caixa de diálogo exibida para criar um novo projeto:

7136b3ee36ebaf89.png

Se você ainda não tiver um projeto, uma caixa de diálogo como esta será exibida para criar seu primeiro:

870a3cbd6541ee86.png

A caixa de diálogo de criação de projeto subsequente permite que você insira os detalhes do novo projeto:

affdc444517ba805.png

Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Ele será indicado mais adiante neste codelab como PROJECT_ID.

Em seguida, será preciso ativar o faturamento no Developers Console para usar os recursos do Google Cloud e ativar a API Cloud Trace, caso ainda não tenha feito isso.

15d0ef27a8fbab27.png

A execução por meio deste codelab terá um custo baixo, mas poderá ser mais se você decidir usar mais recursos ou se deixá-los em execução. Consulte a seção "limpeza" no final deste documento. Os preços do Google Cloud Trace, do Google Kubernetes Engine e do Google Artifact Registry estão na documentação oficial.

Novos usuários do Google Cloud Platform estão qualificados para uma avaliação sem custo financeiro de US$300, o que torna este codelab totalmente sem custo financeiro.

Configuração do Google Cloud Shell

Embora o Google Cloud e o Google Cloud Trace possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

O Cloud Shell é uma máquina virtual com base em Debian que contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Isso significa que tudo que você precisa para este codelab é um navegador (sim, funciona em um Chromebook).

Para ativar o Cloud Shell no Console do Cloud, basta clicar em Ativar o Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A. Leva apenas alguns instantes para provisionar e se conectar ao ambiente.

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

Screen Shot 2017-06-14 às 10.13.43 PM.png

Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto estará configurado com seu PROJECT_ID.

gcloud auth list

Resposta ao comando

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

Resposta ao comando

[core]
project = <PROJECT_ID>

Se, por algum motivo, o projeto não estiver definido, basta emitir o seguinte comando:

gcloud config set project <PROJECT_ID>

Quer encontrar seu PROJECT_ID? Veja qual ID você usou nas etapas de configuração ou procure-o no painel do Console do Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

O Cloud Shell também define algumas variáveis de ambiente por padrão, o que pode ser útil ao executar comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resposta ao comando

<PROJECT_ID>

Defina a zona padrão e a configuração do projeto:

gcloud config set compute/zone us-central1-f

É possível escolher uma variedade de zonas diferentes. Para mais informações, consulte Regiões e zonas.

Configuração da linguagem Go

Neste codelab, usamos Go para todo o código-fonte. Execute o comando a seguir no Cloud Shell e confirme se a versão do Go é 1.17 ou mais recente:

go version

Resposta ao comando

go version go1.18.3 linux/amd64

Configurar um cluster do Google Kubernetes

Neste codelab, você vai executar um cluster de microsserviços no Google Kubernetes Engine (GKE). O processo deste codelab é o seguinte:

  1. Baixar o projeto de linha de base no Cloud Shell
  2. Criar microsserviços em contêineres
  3. Fazer upload de contêineres para o Google Artifact Registry (GAR)
  4. Implantar contêineres no GKE
  5. Modificar o código-fonte dos serviços para instrumentação de rastreamento
  6. Acesse a etapa 2

Ativar o Kubernetes Engine

Primeiro, configuramos um cluster do Kubernetes em que o Shakesapp é executado no GKE. Portanto, precisamos ativar o GKE. Acesse o menu "Kubernetes Engine" e pressione o botão "ATIVAR".

548cfd95bc6d344d.png

Agora você está pronto para criar um cluster do Kubernetes.

Criar cluster do Kubernetes

No Cloud Shell, execute o comando a seguir para criar um cluster do Kubernetes. Confirme se o valor da zona está na região que você vai usar para criar o repositório do Artifact Registry. Mude o valor da zona us-central1-f se a região do repositório não estiver cobrindo a zona.

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

Resposta ao comando

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

Configuração do Artifact Registry e do skaffold

Agora temos um cluster do Kubernetes pronto para implantação. Em seguida, preparamos um registro de contêineres para enviar e implantar contêineres. Para essas etapas, precisamos configurar um Artifact Registry (GAR) e o skaffold para usá-lo.

Configuração do Artifact Registry

Navegue até o menu "Artifact Registry" e pressione o botão "ATIVAR".

45e384b87f7cf0db.png

Depois de alguns instantes, o navegador de repositório do GAR vai aparecer. Clique no botão "CRIAR REPOSITÓRIO" e insira o nome dele.

d6a70f4cb4ebcbe3.png

Neste codelab, vou chamar o novo repositório de trace-codelab. O formato do artefato é "Docker" e o tipo de local é "Região". Escolha a região próxima àquela que você definiu para a zona padrão do Google Compute Engine. Por exemplo, o exemplo acima escolheu "us-central1-f", então aqui escolhemos "us-central1 (Iowa)". Em seguida, clique no botão "CRIAR".

9c2d1ce65258ef70.png

Agora você vai ver "trace-codelab" no navegador do repositório.

7a3c1f47346bea15.png

Vamos voltar aqui mais tarde para verificar o caminho do registro.

Configuração do Skaffold

O Skaffold é uma ferramenta útil para criar microsserviços que são executados no Kubernetes. Ele lida com o fluxo de trabalho de criação, envio e implantação de contêineres de aplicativos com um pequeno conjunto de comandos. Por padrão, o Skaffold usa o Docker Registry como registro de contêiner. Portanto, é necessário configurar o Skaffold para reconhecer o GAR ao enviar contêineres.

Abra o Cloud Shell novamente e confirme se o Skaffold está instalado. O Cloud Shell instala o Skaffold no ambiente por padrão. Execute o comando a seguir e confira a versão do skaffold.

skaffold version

Resposta ao comando

v1.38.0

Agora, você pode registrar o repositório padrão para o Skaffold usar. Para conseguir o caminho do registro, acesse o painel do Artifact Registry e clique no nome do repositório que você acabou de configurar na etapa anterior.

7a3c1f47346bea15.png

Em seguida, você verá trilhas de navegação estrutural na parte de cima da página. Clique no ícone e157b1359c3edc06.png para copiar o caminho do registro para a área de transferência.

e0f2ae2144880b8b.png

Ao clicar no botão de cópia, você verá a caixa de diálogo na parte de baixo do navegador com a mensagem:

"us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab" foi copiado

Volte para o Cloud Shell. Execute o comando skaffold config set default-repo com o valor que você acabou de copiar do painel.

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

Resposta ao comando

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

Além disso, é necessário configurar o registro para a configuração do Docker. Execute este comando:

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

Resposta ao comando

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

Agora você está pronto para a próxima etapa, que é configurar um contêiner do Kubernetes no GKE.

Resumo

Nesta etapa, você vai configurar o ambiente do codelab:

  • Configurar o Cloud Shell
  • Criou um repositório do Artifact Registry para o registro de contêineres
  • Configurar o Skaffold para usar o Container Registry
  • Criar um cluster do Kubernetes em que os microsserviços do codelab são executados

A seguir

Na próxima etapa, você vai criar, enviar e implantar seus microsserviços no cluster.

3. Criar, enviar e implantar os microsserviços

Baixe o material do codelab

Na etapa anterior, configuramos todos os pré-requisitos para este codelab. Agora você já pode executar microsserviços inteiros neles. O material do codelab está hospedado no GitHub. Faça o download dele para o ambiente shell do Cloud Shell com o seguinte comando git.

cd ~
git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git
cd opentelemetry-trace-codelab-go

A estrutura de diretórios do projeto é a seguinte:

.
├── 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: arquivos de manifesto do Kubernetes
  • proto: definição proto para a comunicação entre cliente e servidor
  • src: diretórios para o código-fonte de cada serviço
  • skaffold.yaml: arquivo de configuração do skaffold

Neste codelab, você vai atualizar o código-fonte localizado na pasta step0. Você também pode consultar o código-fonte nas pastas step[1-6] para conferir as respostas nas etapas a seguir. A Parte 1 abrange as etapas 0 a 4, e a Parte 2, as etapas 5 e 6.

Executar o comando skaffold

Por fim, você está pronto para criar, enviar e implantar todo o conteúdo no cluster do Kubernetes que acabou de criar. Parece que contém várias etapas, mas o skaffold faz tudo por você. Vamos tentar com o seguinte comando:

cd step0
skaffold dev

Assim que você executar o comando, vai ver a saída de registro de docker build e poderá confirmar que eles foram enviados ao registro.

Resposta ao comando

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

Depois do push de todos os contêineres de serviço, as implantações do Kubernetes começam automaticamente.

Resposta ao comando

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

Após a implantação, você verá os registros reais do aplicativo emitidos para stdout em cada contêiner, assim:

Resposta ao comando

[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

Neste momento, você quer ver todas as mensagens do servidor. Agora você está pronto para começar a instrumentar seu aplicativo com o OpenTelemetry para rastreamento distribuído dos serviços.

Antes de começar a instrumentar o serviço, desligue o cluster com Ctrl-C.

Resposta ao comando

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

Resumo

Nesta etapa, você preparou o material do codelab no seu ambiente e confirmou que o Skaffold é executado conforme o esperado.

A seguir

Na próxima etapa, você vai modificar o código-fonte do serviço loadgen para instrumentar as informações de rastreamento.

4. Instrumentação para HTTP

Conceito de instrumentação e propagação de rastreamento

Antes de editar o código-fonte, vou explicar brevemente como os rastreamentos distribuídos funcionam em um diagrama simples.

6be42e353b9bfd1d.png

Neste exemplo, instrumentamos o código para exportar informações de rastreamento e intervalo para o Cloud Trace e propagar o contexto de rastreamento na solicitação do serviço loadgen para o serviço de servidor.

Os aplicativos precisam enviar metadados de trace, como ID do trace e ID do período, para que o Cloud Trace reúna todos os períodos com o mesmo ID do trace em um só trace. Além disso, o aplicativo precisa propagar contextos de rastreamento (a combinação de ID de rastreamento e ID do período do período pai) ao solicitar serviços downstream, para que eles saibam qual contexto de rastreamento estão processando.

O OpenTelemetry ajuda você a:

  • para gerar um ID de trace e um ID de período exclusivos
  • para exportar o ID do trace e o ID do período para o back-end
  • para propagar contextos de rastreamento para outros serviços
  • para incorporar metadados extras que ajudam a analisar rastreamentos

Componentes no OpenTelemetry Trace

b01f7bb90188db0d.png

O processo para instrumentar o trace do aplicativo com o OpenTelemetry é o seguinte:

  1. Criar um exportador
  2. Crie uma vinculação TracerProvider do exportador em 1 e defina como global.
  3. Defina TextMapPropagaror para definir o método de propagação.
  4. Receber o rastreador do TracerProvider
  5. Gerar um período do rastreador

Por enquanto, não é necessário entender as propriedades detalhadas de cada componente, mas é importante lembrar que:

  • O exportador aqui é conectável ao TracerProvider
  • O TracerProvider contém todas as configurações relacionadas à amostragem e exportação de rastreamentos.
  • todos os traces são agrupados no objeto Tracer

Com isso em mente, vamos passar para o trabalho de programação propriamente dito.

Instrumentar o primeiro período

Instrumentar o serviço de gerador de carga

Abra o Editor do Cloud Shell pressionando o botão 776a11bfb2122549.png no canto superior direito do Cloud Shell. Abra step0/src/loadgen/main.go no explorador do painel esquerdo e encontre a função principal.

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++
        }
}

Na função principal, você vê o loop chamando a função run. Na implementação atual, a seção tem duas linhas de registro que gravam o início e o fim da chamada de função. Agora vamos instrumentar as informações de período para rastrear a latência da chamada de função.

Primeiro, conforme observado na seção anterior, vamos configurar todas as configurações do OpenTelemetry. Adicione os pacotes do OpenTelemetry da seguinte maneira:

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
)

Para facilitar a leitura, criamos uma função de configuração chamada initTracer e a chamamos na função 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
}

O procedimento para configurar o OpenTelemetry é o mesmo descrito na seção anterior. Nessa implementação, usamos um exportador stdout que exporta todas as informações de rastreamento para o stdout em um formato estruturado.

Em seguida, chame-o da função principal. Chame o initTracer() e o TracerProvider.Shutdown() quando fechar o aplicativo.

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

Depois de concluir a configuração, crie um período com um ID de trace e um ID de período exclusivos. O OpenTelemetry oferece uma biblioteca útil para isso. Adicione outros pacotes novos ao cliente HTTP do instrumento.

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

Como o gerador de carga está chamando o serviço do cliente em HTTP com net/http na função runQuery, usamos o pacote contrib para net/http e ativamos a instrumentação com a extensão do pacote httptrace e otelhttp.

Primeiro, adicione uma variável global de pacote httpClient para chamar solicitações HTTP usando o cliente instrumentado.

step0/src/loadgen/main.go

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

Em seguida, adicione a instrumentação à função runQuery para criar o período personalizado usando o OpenTelemetry e o período gerado automaticamente do cliente HTTP personalizado. Você vai fazer o seguinte:

  1. Receber um rastreador do TracerProvider global com otel.Tracer()
  2. Criar um intervalo raiz com o método Tracer.Start()
  3. Encerre o intervalo raiz em um momento arbitrário (neste caso, o fim da função 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)
        }

Agora você concluiu a instrumentação no loadgen (aplicativo cliente HTTP). Atualize go.mod e go.sum com o comando go mod.

go mod tidy

Instrumentar o atendimento ao cliente

Na seção anterior, instrumentamos a parte envolvida no retângulo vermelho no desenho abaixo. Implementamos informações de extensão no serviço de gerador de carga. Assim como o serviço de gerador de carga, agora precisamos instrumentar o serviço do cliente. A diferença do serviço de gerador de carga é que o serviço de cliente precisa extrair as informações do ID de rastreamento propagadas do serviço de gerador de carga no cabeçalho HTTP e usar o ID para gerar intervalos.

bcaccd06691269f8.png

Abra o Cloud Shell Editor e adicione os pacotes necessários, como fizemos para o serviço de gerador de carga.

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
)

Novamente, precisamos configurar o OpenTelemetry. Basta copiar e colar a função initTracer do loadgen e chamá-la também na função main do serviço de cliente.

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
}

Agora é hora de instrumentar intervalos. Como o serviço do cliente precisa aceitar solicitações HTTP do serviço loadgen, é necessário instrumentar o manipulador. O servidor HTTP no serviço do cliente é implementado com net/http, e você pode usar o pacote otelhttp, como fizemos no loadgen.

Primeiro, substituímos o registro do gerenciador pelo gerenciador otelhttp. Na função main, encontre as linhas em que o gerenciador HTTP está registrado com 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)

Em seguida, instrumentamos o intervalo real dentro do manipulador. Encontre o manipulador func (*clientService) handler() e adicione a instrumentação de extensão com 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
        ...

Com essa instrumentação, você recebe os intervalos do início ao fim do método handler. Para facilitar a análise dos intervalos, adicione um atributo extra que armazena a contagem correspondente à consulta. Logo antes da linha de registro, adicione o seguinte código.

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

Com toda a instrumentação acima, você concluiu a instrumentação de rastreamento entre o loadgen e o cliente. Como ele funciona. Execute o código com o Skaffold novamente.

skaffold dev

Depois de algum tempo executando os serviços no cluster do GKE, você vai ver uma grande quantidade de mensagens de registro como esta:

Resposta ao comando

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

O exportador stdout emite essas mensagens. Você vai notar que os pais de todos os intervalos por loadgen têm TraceID: 00000000000000000000000000000000, porque esse é o intervalo raiz, ou seja, o primeiro intervalo no rastreamento. Além disso, o atributo de incorporação "query" tem a string de consulta transmitida ao serviço do cliente.

Resumo

Nesta etapa, você instrumentou o serviço de gerador de carga e o serviço de cliente que se comunicam em HTTP e confirmou que foi possível propagar o contexto de rastreamento entre os serviços e exportar informações de intervalo de ambos os serviços para stdout.

A seguir

Na próxima etapa, você vai instrumentar o serviço de cliente e o serviço de servidor para confirmar como propagar o contexto de rastreamento via gRPC.

5. Instrumentação para gRPC

Na etapa anterior, instrumentamos a primeira metade da solicitação nesses microsserviços. Nesta etapa, tentamos instrumentar a comunicação gRPC entre o serviço do cliente e o serviço do servidor. (Retângulo verde e roxo na imagem abaixo)

75310d8e0e3b1a30.png

Instrumentação pré-build para cliente gRPC

O ecossistema do OpenTelemetry oferece muitas bibliotecas úteis que ajudam os desenvolvedores a instrumentar aplicativos. Na etapa anterior, usamos a instrumentação pré-build para o pacote net/http. Nesta etapa, como estamos tentando propagar o contexto de rastreamento pelo gRPC, usamos a biblioteca para isso.

Primeiro, importe o pacote gRPC pré-criado chamado 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"
)

Desta vez, o serviço de cliente é um cliente gRPC em relação ao serviço de servidor. Portanto, é necessário instrumentar o cliente gRPC. Encontre a função mustConnGRPC e adicione interceptadores gRPC que instrumentam novos intervalos sempre que o cliente faz solicitações ao servidor.

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

Como você já configurou o OpenTelemetry na seção anterior, não é necessário fazer isso.

Instrumentação pré-criada para servidor gRPC

Assim como fizemos para o cliente gRPC, chamamos a instrumentação pré-criada para o servidor gRPC. Adicione um novo pacote à seção de importação, como:

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

Como esta é a primeira vez que você instrumenta o servidor, é necessário configurar o OpenTelemetry primeiro, assim como fizemos para o loadgen e os serviços de cliente.

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

Em seguida, adicione interceptores de servidor. Na função main, encontre o local em que grpc.NewServer() é chamado e adicione interceptadores à função.

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

Executar o microsserviço e confirmar o rastreamento

Em seguida, execute o código modificado com o comando skaffold.

skaffold dev

Agora, você vê várias informações de período em stdout.

Resposta ao comando

...
[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] }
...

Você percebe que não incorporou nomes de intervalos e criou intervalos manualmente com trace.Start() ou span.SpanFromContext(). Ainda assim, você recebe um grande número de intervalos porque os interceptores gRPC os geraram.

Resumo

Nesta etapa, você instrumentou a comunicação baseada em gRPC com o suporte das bibliotecas do ecossistema OpenTelemetry.

A seguir

Na próxima etapa, você vai visualizar o trace com o Cloud Trace e aprender a analisar os intervalos coletados.

6. Visualizar o rastreamento com o Cloud Trace

Você instrumentou traces em todo o sistema com o OpenTelemetry. Você já aprendeu a instrumentar serviços HTTP e gRPC. Embora você tenha aprendido a instrumentá-los, ainda não aprendeu a analisá-los. Nesta seção, você vai substituir os exportadores stdout pelos do Cloud Trace e aprender a analisar seus rastreamentos.

Usar o exportador do Cloud Trace

Uma das características poderosas do OpenTelemetry é a capacidade de ser conectado. Para visualizar todos os períodos coletados pela instrumentação, basta substituir o exportador stdout pelo exportador do Cloud Trace.

Abra os arquivos main.go de cada serviço e encontre a função initTracer(). Exclua a linha para gerar um exportador stdout e crie um exportador do 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
}

Você também precisa editar a mesma função no serviço de cliente e servidor.

Executar o microsserviço e confirmar o rastreamento

Depois da edição, execute o cluster normalmente com o comando skaffold.

skaffold dev

Agora você não vê muitas informações de período no formato de registros estruturados em stdout, porque substituiu o exportador por um do Cloud Trace.

Resposta ao comando

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

Agora, vamos confirmar se todos os períodos foram enviados corretamente ao Cloud Trace. Acesse o console do Cloud e navegue até "Lista de traces". É fácil acessar na caixa de pesquisa. Caso contrário, clique no menu no painel à esquerda. 8b3f8411bd737e06.png

Então, você vai ver muitos pontos azuis distribuídos no gráfico de latência. Cada ponto representa um único trace.

3ecf131423fc4c40.png

Clique em um deles para conferir os detalhes no trace. 4fd10960c6648a03.png

Mesmo com essa análise rápida e simples, você já tem muitos insights. Por exemplo, no gráfico de cascata, é possível ver que a causa da latência é principalmente o intervalo chamado shakesapp.ShakespeareService/GetMatchCount. Confira o número 1 na imagem acima. A coluna mais à direita mostra a duração de cada período. Além disso, o rastreamento foi para a consulta "amigo". (Confira o número 2 na imagem acima)

Com essas análises curtas, você pode perceber que precisa conhecer intervalos mais detalhados dentro do método GetMatchCount. Em comparação com as informações stdout, a visualização é poderosa. Para saber mais sobre os detalhes do Cloud Trace, acesse nossa documentação oficial.

Resumo

Nesta etapa, você substituiu o exportador stdout por um do Cloud Trace e visualizou os rastreamentos no Cloud Trace. Você também aprendeu a começar a analisar os rastreamentos.

A seguir

Na próxima etapa, você vai modificar o código-fonte do serviço do servidor para adicionar um subintervalo em GetMatchCount.

7. Adicionar subintervalo para uma análise melhor

Na etapa anterior, você descobriu que a causa do tempo de retorno observado no loadgen é principalmente o processo dentro do método GetMatchCount, o gerenciador gRPC, no serviço do servidor. No entanto, como não instrumentamos nada além do manipulador, não é possível encontrar mais insights no gráfico de cascata. Esse é um caso comum quando começamos a instrumentar microsserviços.

3b63a1e471dddb8c.png

Nesta seção, vamos instrumentar um subintervalo em que o servidor chama o Google Cloud Storage, porque é comum que algumas E/S de rede externa demorem muito no processo, e é importante identificar se a chamada é a causa.

Instrumentar um subintervalo no servidor

Abra main.go no servidor e encontre a função readFiles. Essa função está chamando uma solicitação ao Google Cloud Storage para buscar todos os arquivos de texto das obras de Shakespeare. Nessa função, você pode criar um subintervalo, como fez para a instrumentação do servidor HTTP no serviço do cliente.

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

E é só isso para adicionar um novo intervalo. Vamos ver como ele funciona executando o app.

Executar o microsserviço e confirmar o rastreamento

Depois da edição, execute o cluster normalmente com o comando skaffold.

skaffold dev

Escolha um rastreamento chamado query.request na lista. Você vai ver um gráfico de cascata de rastreamento semelhante, exceto por um novo intervalo em shakesapp.ShakespeareService/GetMatchCount. (O intervalo delimitado pelo retângulo vermelho abaixo)

3d4a891aa30d7a32.png

O que você pode concluir com esse gráfico é que a chamada externa para o Google Cloud Storage ocupa uma grande quantidade de latência, mas outras coisas ainda representam a maioria da latência.

Você já teve muitos insights com apenas algumas observações do gráfico de cascata do trace. Como você obtém mais detalhes de performance no seu aplicativo? É aqui que o criador de perfil entra em ação, mas, por enquanto, vamos encerrar este codelab e delegar todos os tutoriais do criador de perfil à parte 2.

Resumo

Nesta etapa, você instrumentou outro intervalo no serviço do servidor e obteve mais insights sobre a latência do sistema.

8. Parabéns

Você criou rastreamentos distribuídos com o OpenTelemetry e confirmou as latências de solicitação no microsserviço no Google Cloud Trace.

Para exercícios mais longos, você pode tentar os seguintes tópicos por conta própria.

  • A implementação atual envia todos os períodos gerados pela verificação de integridade. (grpc.health.v1.Health/Check) Como filtrar esses períodos do Cloud Trace? A dica está aqui.
  • Correlacione registros de eventos com períodos e saiba como isso funciona no Google Cloud Trace e no Google Cloud Logging. A dica está aqui.
  • Substitua um serviço por outro em um idioma diferente e tente instrumentá-lo com o OpenTelemetry para esse idioma.

Se quiser saber mais sobre o criador de perfis depois disso, passe para a parte 2. Nesse caso, você pode pular a seção de liberar espaço abaixo.

Limpeza

Depois deste codelab, pare o cluster do Kubernetes e exclua o projeto para evitar cobranças inesperadas no Google Kubernetes Engine, no Google Cloud Trace e no Google Artifact Registry.

Primeiro, exclua o cluster. Se você estiver executando o cluster com skaffold dev, basta pressionar Ctrl+C. Se você estiver executando o cluster com skaffold run, execute o seguinte comando:

skaffold delete

Resposta ao comando

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Depois de excluir o cluster, no painel de menu, selecione "IAM e administrador" > "Configurações" e clique no botão "ENCERRAR".

45aa37b7d5e1ddd1.png

Em seguida, insira o ID do projeto (e não o nome) no formulário da caixa de diálogo e confirme o encerramento.