Instrumenta para mejorar el rendimiento de tu app en Go (parte 1: seguimiento)

1. Introducción

505827108874614d.png

Última actualización: 15/07/2022

Observabilidad de la aplicación

Observabilidad y OpenTelemetry

La observabilidad es el término que se usa para describir un atributo de un sistema. Un sistema con observabilidad permite a los equipos depurar de forma activa su sistema. En ese contexto, los tres pilares de la observabilidad (registros, métricas y seguimientos) son la instrumentación fundamental para que el sistema adquiera observabilidad.

OpenTelemetry es un conjunto de especificaciones, bibliotecas y agentes que aceleran la instrumentación y exportación de datos de telemetría (registros, métricas y seguimientos) que requiere la observabilidad. OpenTelemetry es un proyecto de estándar abierto y dirigido por la comunidad en CNCF. Cuando usan las bibliotecas que proporcionan el proyecto y su ecosistema, los desarrolladores pueden instrumentar sus aplicaciones de forma neutral en cuanto a proveedores y en varias arquitecturas.

Además de los tres pilares de la observabilidad, la generación de perfiles continua es otro componente clave para la observabilidad y expande la base de usuarios en la industria. Cloud Profiler es uno de los creadores y proporciona una interfaz sencilla para desglosar las métricas de rendimiento en las pilas de llamadas de la aplicación.

Este codelab es la parte 1 de la serie y abarca la instrumentación de seguimientos distribuidos en microservicios con OpenTelemetry y Cloud Trace. En la parte 2, se abordará el perfilado continuo con Cloud Profiler.

Registro distribuido

Entre los registros, las métricas y los seguimientos, el seguimiento es la telemetría que indica la latencia de una parte específica del proceso en el sistema. En especial en la era de los microservicios, el seguimiento distribuido es el factor clave para descubrir los cuellos de botella de latencia en el sistema distribuido general.

Cuando se analizan los seguimientos distribuidos, la visualización de datos de seguimiento es clave para comprender las latencias generales del sistema de un vistazo. En el seguimiento distribuido, controlamos un conjunto de llamadas para procesar una sola solicitud al punto de entrada del sistema en forma de seguimiento que contiene varios intervalos.

El intervalo representa una unidad individual de trabajo realizado en un sistema distribuido, que registra las horas de inicio y de detención. A menudo, los intervalos tienen relaciones jerárquicas entre sí. En la siguiente imagen, todos los intervalos más pequeños son intervalos secundarios de un intervalo grande de /messages y se ensamblan en un seguimiento que muestra la ruta de trabajo a través de un sistema.

Un seguimiento

Google Cloud Trace es una de las opciones para el backend de seguimiento distribuido y está bien integrado con otros productos de Google Cloud.

Qué compilarás

En este codelab, instrumentarás la información de seguimiento en los servicios llamados "aplicación de Shakespeare" (también conocida como Shakesapp) que se ejecuta en un clúster de Google Kubernetes Engine. La arquitectura de Shakesapp es la siguiente:

44e243182ced442f.png

  • Loadgen envía una cadena de consulta al cliente en HTTP.
  • Los clientes pasan la consulta de loadgen al servidor en gRPC.
  • El servidor acepta la consulta del cliente, recupera todas las obras de Shakespeare en formato de texto de Google Cloud Storage, busca las líneas que contienen la consulta y muestra el número de la línea que coincide con el cliente.

Instrumentarás la información de seguimiento en toda la solicitud. Luego, incorporarás un agente de generador de perfiles en el servidor y, además, investigarás el cuello de botella.

Qué aprenderás

  • Cómo comenzar a usar las bibliotecas de seguimiento de OpenTelemetry en un proyecto de Go
  • Cómo crear un intervalo con la biblioteca
  • Cómo propagar contextos de intervalo a través de la conexión entre componentes de la app
  • Cómo enviar datos de seguimiento a Cloud Trace
  • Cómo analizar el seguimiento en Cloud Trace

En este codelab, se explica cómo instrumentar tus microservicios. Para que sea fácil de entender, este ejemplo solo contiene 3 componentes (generador de carga, cliente y servidor), pero puedes aplicar el mismo proceso que se explica en este codelab a sistemas más complejos y grandes.

Requisitos

  • Conocimientos básicos de Go
  • Conocimientos básicos de Kubernetes

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

Si aún no tienes una Cuenta de Google (Gmail o Google Apps), debes crear una. Accede a Google Cloud Platform Console (console.cloud.google.com) y crea un proyecto nuevo.

Si ya tienes un proyecto, haz clic en el menú desplegable de selección de proyectos en la parte superior izquierda de la Console:

7a32e5469db69e9.png

y haz clic en el botón “PROYECTO NUEVO” en el diálogo resultante para crear un proyecto nuevo:

7136b3ee36ebaf89.png

Si aún no tienes un proyecto, deberías ver un cuadro de diálogo como este para crear el primero:

870a3cbd6541ee86.png

El cuadro de diálogo de creación posterior del proyecto te permite ingresar los detalles de tu proyecto nuevo:

affdc444517ba805.png

Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Más adelante en este codelab, se lo llamará PROJECT_ID.

A continuación, si aún no lo has hecho, deberás habilitar la facturación en Play Console para usar los recursos de Google Cloud y habilitar la API de Cloud Trace.

15d0ef27a8fbab27.png

Ejecutar este codelab debería costar solo unos pocos dólares, pero su costo podría aumentar si decides usar más recursos o si los dejas en ejecución (consulta la sección “Limpiar” al final de este documento). Los precios de Google Cloud Trace, Google Kubernetes Engine y Google Artifact Registry se indican en la documentación oficial.

Los usuarios nuevos de Google Cloud Platform están aptas para obtener una prueba gratuita de $300, por lo que este codelab es completamente gratuito.

Configuración de Google Cloud Shell

Si bien Google Cloud y Google Cloud Trace se pueden operar de manera remota desde tu laptop, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

Esta máquina virtual basada en Debian está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Esto significa que todo lo que necesitarás para este Codelab es un navegador (sí, funciona en una Chromebook).

Para activar Cloud Shell desde la consola de Cloud, solo haz clic en Activar Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (el aprovisionamiento y la conexión al entorno deberían llevar solo unos minutos).

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

Captura de pantalla del 14 de junio de 2017 a las 10.13.43 p.m. .png

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu PROJECT_ID:

gcloud auth list

Resultado del comando

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

Resultado del comando

[core]
project = <PROJECT_ID>

Si, por algún motivo, el proyecto no está configurado, solo emite el siguiente comando:

gcloud config set project <PROJECT_ID>

Si no conoce su PROJECT_ID, Observa el ID que usaste en los pasos de configuración o búscalo en el panel de la consola de Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell también configura algunas variables de entorno de forma predeterminada, lo que puede resultar útil cuando ejecutas comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resultado del comando

<PROJECT_ID>

Establece la zona predeterminada y la configuración del proyecto.

gcloud config set compute/zone us-central1-f

Puedes elegir una variedad de zonas diferentes. Para obtener más información, consulta Regiones y zonas.

Configuración del lenguaje Go

En este codelab, usamos Go para todo el código fuente. Ejecuta el siguiente comando en Cloud Shell y confirma si la versión de Go es 1.17 o posterior.

go version

Resultado del comando

go version go1.18.3 linux/amd64

Cómo configurar un clúster de Google Kubernetes

En este codelab, ejecutarás un clúster de microservicios en Google Kubernetes Engine (GKE). El proceso de este codelab es el siguiente:

  1. Descarga el proyecto de modelo de referencia en Cloud Shell
  2. Compila microservicios en contenedores
  3. Sube contenedores a Google Artifact Registry (GAR)
  4. Implementa contenedores en GKE
  5. Modifica el código fuente de los servicios para la instrumentación de seguimiento
  6. Ve al paso 2

Habilita Kubernetes Engine

Primero, configuramos un clúster de Kubernetes en el que se ejecuta Shakesapp en GKE, por lo que debemos habilitar GKE. Navega al menú "Kubernetes Engine" y presiona el botón ENABLE.

548cfd95bc6d344d.png

Ya puedes crear un clúster de Kubernetes.

Cómo crear un clúster de Kubernetes

En Cloud Shell, ejecuta el siguiente comando para crear un clúster de Kubernetes. Confirma que el valor de la zona esté dentro de la región que usarás para crear el repositorio de Artifact Registry. Cambia el valor de zona us-central1-f si la región de tu repositorio no cubre la 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

Resultado del 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

Configuración de Artifact Registry y skaffold

Ahora tenemos un clúster de Kubernetes listo para la implementación. A continuación, nos prepararemos para un registro de contenedores para enviar y, luego, implementar contenedores. Para estos pasos, debemos configurar un Artifact Registry (GAR) y skaffold para usarlo.

Configuración de Artifact Registry

Navega al menú "Registro de artefactos" y presiona el botón HABIlitar.

45e384b87f7cf0db.png

Después de unos momentos, verás el navegador de repositorios de GAR. Haz clic en el botón "CREATE REPOSITORY" y, luego, ingresa el nombre del repositorio.

d6a70f4cb4ebcbe3.png

En este codelab, le asigno el nombre trace-codelab al repositorio nuevo. El formato del artefacto es “Docker” y el tipo de ubicación es “Región”. Elige la región cercana a la que configuraste para la zona predeterminada de Google Compute Engine. Por ejemplo, en este ejemplo, se eligió “us-central1-f” anteriormente, por lo que aquí elegimos “us-central1 (Iowa)”. Luego, haz clic en el botón "CREAR".

9c2d1ce65258ef70.png

Ahora verás "trace-codelab" en el navegador del repositorio.

7a3c1f47346bea15.png

Volveremos a esta conversación más tarde para verificar la ruta de registro.

Configuración de Skaffold

Skaffold es una herramienta útil cuando trabajas en la compilación de microservicios que se ejecutan en Kubernetes. Controla el flujo de trabajo de compilación, envío e implementación de contenedores de aplicaciones con un pequeño conjunto de comandos. De forma predeterminada, Skaffold usa Docker Registry como registro de contenedores, por lo que debes configurarlo para que reconozca GAR cuando envíes contenedores.

Vuelve a abrir Cloud Shell y confirma si skaffold está instalado. (Cloud Shell instala skaffold en el entorno de forma predeterminada). Ejecuta el siguiente comando y consulta la versión de skaffold.

skaffold version

Resultado del comando

v1.38.0

Ahora, puedes registrar el repositorio predeterminado para que lo use skaffold. Para obtener la ruta de acceso del registro, navega al panel de Artifact Registry y haz clic en el nombre del repositorio que acabas de configurar en el paso anterior.

7a3c1f47346bea15.png

Luego, verás las rutas de navegación en la parte superior de la página. Haz clic en el ícono e157b1359c3edc06.png para copiar la ruta de acceso del registro en el portapapeles.

e0f2ae2144880b8b.png

Cuando haces clic en el botón de copia, aparece el diálogo en la parte inferior del navegador con el siguiente mensaje:

Se copió "us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab"

Regresa a Cloud Shell. Ejecuta el comando skaffold config set default-repo con el valor que acabas de copiar del panel.

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

Resultado del 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

Además, debes configurar el registro en la configuración de Docker. Ejecuta el siguiente comando:

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

Resultado del 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

Ya puedes continuar con el siguiente paso para configurar un contenedor de Kubernetes en GKE.

Resumen

En este paso, configurarás el entorno de tu codelab:

  • Configura Cloud Shell
  • Creaste un repositorio de Artifact Registry para el registro de contenedores.
  • Configura Skaffold para usar el registro de contenedores
  • Creaste un clúster de Kubernetes en el que se ejecutan los microservicios del codelab

Cuál es el próximo paso

En el siguiente paso, compilarás, enviarás e implementarás tus microservicios en el clúster.

3. Compila, envía e implementa los microservicios

Descarga el material del codelab

En el paso anterior, configuramos todos los requisitos previos para este codelab. Ahora tienes todo listo para ejecutar microservicios completos sobre ellos. El material del codelab se aloja en GitHub, así que descárgalo en el entorno de Cloud Shell con el siguiente comando de git.

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

La estructura de directorios del proyecto es la siguiente:

.
├── 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: Archivos de manifiesto de Kubernetes
  • proto: Es la definición de proto para la comunicación entre el cliente y el servidor.
  • src: Directorios para el código fuente de cada servicio
  • skaffold.yaml: Es el archivo de configuración de Skaffold.

En este codelab, actualizarás el código fuente que se encuentra en la carpeta step0. También puedes consultar el código fuente en las carpetas step[1-6] para encontrar las respuestas en los siguientes pasos. (la Parte 1 abarca del paso 0 al 4, y la Parte 2 abarca los pasos 5 y 6).

Ejecuta el comando skaffold

Por último, tienes todo listo para compilar, enviar e implementar todo el contenido en el clúster de Kubernetes que acabas de crear. Parece que contiene varios pasos, pero en realidad skaffold hace todo por ti. Probemos con el siguiente comando:

cd step0
skaffold dev

En cuanto ejecutas el comando, ves el resultado del registro de docker build y puedes confirmar que se envían correctamente al registro.

Resultado del 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

Después de enviar todos los contenedores de servicios, las implementaciones de Kubernetes se inician automáticamente.

Resultado del 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

Después de la implementación, verás los registros reales de la aplicación emitidos a stdout en cada contenedor de la siguiente manera:

Resultado del 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

Ten en cuenta que, en este punto, quieres ver todos los mensajes del servidor. Bien, por fin tienes todo listo para comenzar a instrumentar tu aplicación con OpenTelemetry para el seguimiento distribuido de los servicios.

Antes de comenzar a instrumentar el servicio, cierra el clúster con Ctrl C.

Resultado del 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

Resumen

En este paso, preparaste el material del codelab en tu entorno y confirmaste que skaffold se ejecuta como se esperaba.

Cuál es el próximo paso

En el siguiente paso, modificarás el código fuente del servicio de loadgen para instrumentar la información de seguimiento.

4. Instrumentación para HTTP

Concepto de instrumentación y propagación de seguimiento

Antes de editar el código fuente, permíteme explicarte brevemente cómo funcionan los seguimientos distribuidos en un diagrama simple.

6be42e353b9bfd1d.png

En este ejemplo, instrumentamos el código para exportar información de seguimiento y de intervalo a Cloud Trace y propagar el contexto de seguimiento en la solicitud del servicio de carga al servicio del servidor.

Las aplicaciones deben enviar metadatos de seguimiento, como el ID de seguimiento y el ID de intervalo, para que Cloud Trace compile todos los intervalos que tengan el mismo ID de seguimiento en un solo seguimiento. Además, la aplicación debe propagar contextos de seguimiento (la combinación del ID de seguimiento y el ID de intervalo del intervalo superior) en los servicios downstream solicitados para que puedan saber qué contexto de seguimiento están controlando.

OpenTelemetry te ayuda a hacer lo siguiente:

  • para generar un ID de seguimiento y un ID de intervalo únicos
  • para exportar el ID de seguimiento y el ID de intervalo al backend
  • para propagar contextos de seguimiento a otros servicios
  • para incorporar metadatos adicionales que ayudan a analizar los seguimientos

Componentes en el seguimiento de OpenTelemetry

b01f7bb90188db0d.png

El proceso para instrumentar el seguimiento de la aplicación con OpenTelemetry es el siguiente:

  1. Crea un exportador
  2. Crea un TracerProvider que vincule el exportador en 1 y configúralo como global.
  3. Cómo configurar TextMapPropagator para establecer el método de propagación
  4. Obtén el generador de registros de TracerProvider
  5. Genera un Span desde el generador de registros

Por ahora, no necesitas comprender las propiedades detalladas de cada componente, pero lo más importante que debes recordar es lo siguiente:

  • El exportador aquí es conectable a TracerProvider.
  • TracerProvider contiene toda la configuración relacionada con el muestreo y la exportación de registros.
  • Todos los seguimientos se agrupan en el objeto Tracer.

Ahora que comprendes esto, pasemos al trabajo de programación en sí.

Primer intervalo del instrumento

Servicio de generador de cargas de instrumentos

Para abrir el editor de Cloud Shell, presiona el botón 776a11bfb2122549.png en la parte superior derecha de Cloud Shell. Abre step0/src/loadgen/main.go desde el explorador en el panel izquierdo y busca la función 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++
        }
}

En la función principal, ves el bucle que llama a la función run en ella. En la implementación actual, la sección tiene 2 líneas de registro que registran el inicio y el final de la llamada a la función. Ahora, instrumentemos la información de Span para hacer un seguimiento de la latencia de la llamada a función.

Primero, como se señaló en la sección anterior, configuremos toda la configuración de OpenTelemetry. Agrega los paquetes de OpenTelemetry de la siguiente manera:

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 mejorar la legibilidad, creamos una función de configuración llamada initTracer y la llamamos en la función 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
}

Es posible que te des cuenta de que el procedimiento para configurar OpenTelemetry es como se describe en la sección anterior. En esta implementación, usamos un exportador stdout que exporta toda la información de seguimiento a stdout en un formato estructurado.

Luego, llámala desde la función principal. Llama a initTracer() y asegúrate de llamar a TracerProvider.Shutdown() cuando cierres la aplicación.

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

Una vez que termines la configuración, debes crear un Span con un ID de seguimiento y un ID de Span únicos. OpenTelemetry proporciona una biblioteca útil para ello. Agrega paquetes nuevos adicionales al cliente HTTP de instrumentación.

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 el generador de cargas llama al servicio de cliente en HTTP con net/http en la función runQuery, usamos el paquete contrib para net/http y habilitamos la instrumentación con la extensión del paquete httptrace y otelhttp.

Primero, agrega una variable global del paquete httpClient para llamar a las solicitudes HTTP a través del cliente instrumentado.

step0/src/loadgen/main.go

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

A continuación, agrega instrumentación en la función runQuery para crear el intervalo personalizado con OpenTelemetry y el intervalo generado automáticamente desde el cliente HTTP personalizado. Sigue estos pasos:

  1. Obtén un generador de registros de TracerProvider global con otel.Tracer()
  2. Crea un intervalo raíz con el método Tracer.Start()
  3. Finaliza el intervalo raíz en un tiempo arbitrario (en este caso, el final de la función 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)
        }

Ya terminaste con la instrumentación en loadgen (aplicación cliente HTTP). Asegúrate de actualizar go.mod y go.sum con el comando go mod.

go mod tidy

Instrumenta la atención al cliente

En la sección anterior, instrumentamos la parte encerrada en el rectángulo rojo del siguiente dibujo. Instrumentamos la información de intervalo en el servicio del generador de cargas. De manera similar al servicio del generador de cargas, ahora debemos instrumentar el servicio del cliente. La diferencia con el servicio del generador de cargas es que el servicio del cliente debe extraer la información del ID de seguimiento propagada desde el servicio del generador de cargas en el encabezado HTTP y usar el ID para generar tramos.

bcaccd06691269f8.png

Abre el editor de Cloud Shell y agrega los paquetes necesarios, como lo hicimos para el servicio de generador de cargas.

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
)

Una vez más, debemos configurar OpenTelemtry. Solo tienes que copiar y pegar la función initTracer de loadgen y llamarla también en la función main del servicio 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
}

Ahora es el momento de instrumentar los intervalos. Debido a que el servicio de cliente debe aceptar solicitudes HTTP del servicio de loadgen, debe instrumentar el controlador. El servidor HTTP en el servicio de cliente se implementa con net/http, y puedes usar el paquete otelhttp como lo hicimos en loadgen.

Primero, reemplazamos el registro del controlador por el controlador otelhttp. En la función main, busca las líneas en las que se registra el controlador HTTP con 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)

Luego, instrumentamos el intervalo real dentro del controlador. Busca func (*clientService) handler(), y agrega la instrumentación de span con 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
        ...

Con esta instrumentación, obtienes los intervalos desde el principio del método handler hasta el final. Para que los intervalos sean fáciles de analizar, agrega un atributo adicional que almacene el recuento de coincidencias en la consulta. Agrega el siguiente código justo antes de la línea de registro.

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

Con toda la instrumentación anterior, completaste la instrumentación de seguimiento entre loadgen y el cliente. Veamos cómo funciona. Vuelve a ejecutar el código con skaffold.

skaffold dev

Después de ejecutar los servicios en el clúster de GKE durante un tiempo, verás una gran cantidad de mensajes de registro como este:

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

El exportador de stdout emite estos mensajes. Notarás que los elementos superiores de todos los intervalos de loadgen tienen TraceID: 00000000000000000000000000000000, ya que este es el intervalo raíz, es decir, el primer intervalo del seguimiento. También verás que el atributo de incorporación "query" tiene la cadena de consulta que se pasa al servicio de cliente.

Resumen

En este paso, instrumentaste el servicio del generador de cargas y el servicio del cliente que se comunican en HTTP y confirmaste que puedes propagar correctamente el contexto de seguimiento entre los servicios y exportar la información de Span de ambos servicios a stdout.

Cuál es el próximo paso

En el siguiente paso, instrumentarás el servicio de cliente y el servicio de servidor para confirmar cómo propagar el contexto de seguimiento a través de gRPC.

5. Instrumentación para gRPC

En el paso anterior, instrumentamos la primera mitad de la solicitud en este microservicio. En este paso, intentamos instrumentar la comunicación de gRPC entre el servicio de cliente y el servicio de servidor. (rectángulo verde y morado en la imagen de abajo)

75310d8e0e3b1a30.png

Instrumentación previa a la compilación para el cliente de gRPC

El ecosistema de OpenTelemetry ofrece muchas bibliotecas prácticas que ayudan a los desarrolladores a instrumentar aplicaciones. En el paso anterior, usamos la instrumentación previa a la compilación para el paquete net/http. En este paso, como intentamos propagar el contexto de seguimiento a través de gRPC, usamos la biblioteca para ello.

Primero, importas el paquete gRPC compilado previamente llamado 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"
)

Esta vez, el servicio de cliente es un cliente de gRPC en relación con el servicio de servidor, por lo que debes instrumentar el cliente de gRPC. Busca la función mustConnGRPC y agrega interceptores de gRPC que instrumenten nuevos intervalos cada vez que el cliente realice solicitudes al 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 ya configuraste OpenTelemetry en la sección anterior, no es necesario que lo hagas.

Instrumentación precompilada para el servidor de gRPC

Al igual que lo que hicimos para el cliente de gRPC, llamamos a la instrumentación precompilada para el servidor de gRPC. Agrega un paquete nuevo a la sección de importación, como el siguiente:

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 es la primera vez que se instrumenta el servidor, primero debes configurar OpenTelemetry, de manera similar a lo que hicimos para loadgen y los servicios 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
        ...

A continuación, debes agregar interceptores de servidor. En la función main, busca el lugar en el que se llama a grpc.NewServer() y agrega interceptores a la función.

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

Ejecuta el microservicio y confirma el seguimiento

Luego, ejecuta el código modificado con el comando skaffold.

skaffold dev

Una vez más, verás mucha información de intervalo en stdout.

Resultado del 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] }
...

Observas que no incorporaste ningún nombre de intervalo y creaste intervalos de forma manual con trace.Start() o span.SpanFromContext(). De todas formas, obtienes una gran cantidad de tramos porque los generaron los interceptores de gRPC.

Resumen

En este paso, instrumentaste la comunicación basada en gRPC con la compatibilidad de las bibliotecas del ecosistema de OpenTelemetry.

Cuál es el próximo paso

En el siguiente paso, por fin visualizarás el seguimiento con Cloud Trace y aprenderás a analizar los intervalos recopilados.

6. Visualiza el seguimiento con Cloud Trace

Instrumentaste seguimientos en todo el sistema con OpenTelemetry. Hasta ahora, aprendiste a instrumentar servicios HTTP y gRPC. Aunque aprendiste a instrumentarlos, aún no sabes cómo analizarlos. En esta sección, reemplazarás los exportadores de stdout por exportadores de Cloud Trace y aprenderás a analizar tus seguimientos.

Cómo usar el exportador de Cloud Trace

Una de las características potentes de OpenTelemetry es su capacidad de conexión. Para visualizar todos los intervalos recopilados por tu instrumentación, solo debes reemplazar el exportador de stdout por el exportador de Cloud Trace.

Abre los archivos main.go de cada servicio y busca la función initTracer(). Borra la línea para generar un exportador de stdout y crea un exportador de 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
}

También debes editar la misma función en el servicio de cliente y servidor.

Ejecuta el microservicio y confirma el seguimiento

Después de la edición, ejecuta el clúster como de costumbre con el comando skaffold.

skaffold dev

Ahora, no ves mucha información de intervalo en formato de registros estructurados en stdout, porque reemplazaste el exportador por el de Cloud Trace.

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

Ahora, confirmemos si todos los intervalos se envían correctamente a Cloud Trace. Accede a la consola de Cloud y navega a "Lista de seguimientos". Es fácil acceder a él desde el cuadro de búsqueda. De lo contrario, puedes hacer clic en el menú del panel izquierdo. 8b3f8411bd737e06.png

Luego, verás que se distribuyen muchos puntos azules en el gráfico de latencia. Cada punto representa un solo seguimiento.

3ecf131423fc4c40.png

Haz clic en uno de ellos para ver los detalles dentro del seguimiento. 4fd10960c6648a03.png

Incluso con esta simple vista rápida, ya conoces muchas estadísticas. Por ejemplo, en el gráfico en cascada, puedes ver que la causa de la latencia se debe principalmente al intervalo llamado shakesapp.ShakespeareService/GetMatchCount. (consulta 1 en la imagen anterior). Puedes confirmarlo en la tabla de resumen. (La columna de la derecha muestra la duración de cada intervalo). Además, este seguimiento fue para la búsqueda "friend". (Consulta el punto 2 en la imagen de arriba).

A partir de estos breves análisis, es posible que te des cuenta de que necesitas conocer intervalos más detallados dentro del método GetMatchCount. En comparación con la información de stdout, la visualización es potente. Para obtener más información sobre los detalles de Cloud Trace, visita nuestra documentación oficial.

Resumen

En este paso, reemplazaste el exportador de stdout por el de Cloud Trace y visualizaste los seguimientos en Cloud Trace. También aprendiste a comenzar a analizar los seguimientos.

Cuál es el próximo paso

En el siguiente paso, modificarás el código fuente del servicio del servidor para agregar un subperíodo en GetMatchCount.

7. Se agregó un subperíodo para mejorar el análisis

En el paso anterior, descubriste que la causa del tiempo de ida y vuelta observado desde loadgen es principalmente el proceso dentro del método GetMatchCount, el controlador de gRPC, en el servicio del servidor. Sin embargo, como no instrumentamos nada más que el controlador, no podemos encontrar más estadísticas en el gráfico de cascada. Este es un caso común cuando comenzamos a instrumentar microservicios.

3b63a1e471dddb8c.png

En esta sección, instrumentaremos un subperíodo en el que el servidor llama a Google Cloud Storage, ya que es común que algunas operaciones de E/S de red externas demoren mucho tiempo en el proceso y es importante identificar si la llamada es la causa.

Instrumenta un sub tramo en el servidor

Abre main.go en el servidor y busca la función readFiles. Esta función realiza una solicitud a Google Cloud Storage para recuperar todos los archivos de texto de las obras de Shakespeare. En esta función, puedes crear un subperíodo, como lo hiciste para la instrumentación del servidor HTTP en el servicio 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
        ...

Eso es todo para agregar un nuevo intervalo. Veamos cómo funciona ejecutando la app.

Ejecuta el microservicio y confirma el seguimiento

Después de la edición, simplemente ejecuta el clúster como de costumbre con el comando skaffold.

skaffold dev

Y elige un registro llamado query.request de la lista de registros. Verás un gráfico de cascada de seguimiento similar, excepto por un nuevo intervalo en shakesapp.ShakespeareService/GetMatchCount. (el intervalo encerrado en el rectángulo rojo a continuación)

3d4a891aa30d7a32.png

Ahora puedes saber a partir de este gráfico que la llamada externa a Google Cloud Storage ocupa una gran cantidad de latencia, pero aún hay otros factores que generan la mayor parte de la latencia.

Ya obtuviste muchas estadísticas con solo un par de vistas del gráfico de cascada de seguimiento. ¿Cómo obtienes más detalles de rendimiento en tu aplicación? Aquí es donde entra en juego el generador de perfiles, pero, por ahora, finalicemos este codelab y delegemos todos los instructivos del generador de perfiles a la parte 2.

Resumen

En este paso, instrumentaste otro intervalo en el servicio del servidor y obtuviste más estadísticas sobre la latencia del sistema.

8. Felicitaciones

Creaste correctamente seguimientos distribuidos con OpenTelemetry y confirmaste las latencias de las solicitudes en el microservicio en Google Cloud Trace.

Para ejercicios extendidos, puedes probar los siguientes temas por tu cuenta.

  • La implementación actual envía todos los intervalos generados por la verificación de estado. (grpc.health.v1.Health/Check) ¿Cómo se filtran esos intervalos de Cloud Trace? La sugerencia está aquí.
  • Correlaciona los registros de eventos con los intervalos y observa cómo funciona en Google Cloud Trace y Google Cloud Logging. La sugerencia está aquí.
  • Reemplaza algún servicio por el de otro idioma y trata de instrumentarlo con OpenTelemetry para ese idioma.

Además, si quieres obtener información sobre el generador de perfiles después de esto, pasa a la parte 2. En ese caso, puedes omitir la sección de limpieza que aparece a continuación.

Realiza una limpieza

Después de este codelab, detén el clúster de Kubernetes y asegúrate de borrar el proyecto para no recibir cargos inesperados en Google Kubernetes Engine, Google Cloud Trace y Google Artifact Registry.

Primero, borra el clúster. Si ejecutas el clúster con skaffold dev, solo debes presionar Ctrl + C. Si ejecutas el clúster con skaffold run, ejecuta el siguiente comando:

skaffold delete

Resultado del comando

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

Después de borrar el clúster, en el panel de menú, selecciona "IAM y administración" > "Configuración" y, luego, haz clic en el botón "APAGAR".

45aa37b7d5e1ddd1.png

Luego, ingresa el ID del proyecto (no el nombre) en el formulario del diálogo y confirma el cierre.