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

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 registros de seguimiento) 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 la exportación de datos de telemetría (registros, métricas y seguimientos) que requiere la observabilidad. OpenTelemetry es un estándar abierto y un proyecto impulsado por la comunidad bajo la CNCF. Al utilizar las bibliotecas que proporcionan el proyecto y su ecosistema, los desarrolladores pueden instrumentar sus aplicaciones de forma independiente del proveedor y en varias arquitecturas.

Además de los tres pilares de la observabilidad, la generación de perfiles continua es otro componente clave de la observabilidad y amplía la base de usuarios en la industria. Cloud Profiler es uno de los originadores y proporciona una interfaz sencilla para analizar en detalle 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 de seguimiento distribuido

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

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

Un tramo representa una unidad de trabajo individual realizada en un sistema distribuido, y registra las horas de inicio y finalización. Los tramos suelen tener relaciones jerárquicas entre sí. En la siguiente imagen, todos los tramos más pequeños son tramos secundarios de un tramo /messages grande y se ensamblan en un solo registro de seguimiento que muestra la ruta de trabajo a través de un sistema.

Un registro

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 ejecutan en un clúster de Google Kubernetes Engine. La arquitectura de Shakesapp es la que se describe a continuación:

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 búsqueda del cliente, recupera todas las obras de Shakespeare en formato de texto de Google Cloud Storage, busca las líneas que contienen la búsqueda y devuelve al cliente el número de la línea que coincidió.

Instrumentarás la información de seguimiento en toda la solicitud. Luego, incorporarás un agente de generación de perfiles en el servidor y analizará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 tramo a través del cable entre componentes de la app
  • Cómo enviar datos de seguimiento a Cloud Trace
  • Cómo analizar el registro 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). Se hará referencia a él más adelante en este codelab como PROJECT_ID.

A continuación, si aún no lo has hecho, deberás habilitar la facturación en Developers 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ía 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

Configura 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 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. Ir 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 HABILITAR.

548cfd95bc6d344d.png

Ya puedes crear un clúster de Kubernetes.

Crea 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 la zona us-central1-f si la región de tu repositorio no abarca 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 preparamos para un registro de contenedores para enviar e implementar contenedores. Para estos pasos, debemos configurar un registro de artefactos (GAR) y Skaffold para usarlo.

Configuración de Artifact Registry

Navega al menú de "Artifact Registry" y presiona el botón HABILITAR.

45e384b87f7cf0db.png

Después de unos instantes, 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, llamo al nuevo repositorio trace-codelab. 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 el ejemplo anterior, se eligió "us-central1-f", 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 de repositorios.

7a3c1f47346bea15.png

Volveremos aquí 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 configurar Skaffold para que reconozca GAR cuando envíes contenedores.

Abre Cloud Shell nuevamente y confirma si Skaffold está instalado. (Cloud Shell instala Skaffold en el entorno de forma predeterminada). Ejecuta el siguiente comando y observa 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 al 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 rutas de navegación en la parte superior de la página. Haz clic en el ícono de e157b1359c3edc06.png para copiar la ruta de registro en el portapapeles.

e0f2ae2144880b8b.png

Cuando haces clic en el botón de copiar, ves 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

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

Resumen

En este paso, configurarás tu entorno de 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 puedes ejecutar microservicios completos sobre ellos. El material del codelab está alojado en GitHub, por lo que debes descargarlo 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
  • manifiestos: Archivos de manifiesto de Kubernetes
  • proto: 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: Archivo de configuración de Skaffold

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

Ejecuta el comando skaffold

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

cd step0
skaffold dev

En cuanto ejecutes el comando, verás el resultado del registro de docker build y podrás confirmar que se enviaron 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 la inserción de todos los contenedores de servicio, 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 en 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. De acuerdo. Por último, ya puedes 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 según lo previsto.

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 registros

Antes de editar el código fuente, permítanme explicar brevemente cómo funcionan los registros distribuidos en un diagrama simple.

6be42e353b9bfd1d.png

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

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

OpenTelemetry te ayuda a hacer lo siguiente:

  • Generar un ID de seguimiento y un ID de intervalo únicos
  • Exportar el ID de seguimiento y el ID de tramo al backend
  • para propagar contextos de seguimiento a otros servicios
  • para incorporar metadatos adicionales que ayudan a analizar los registros

Componentes en el registro 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. Configura TextMapPropagaror para establecer el método de propagación
  4. Obtén el objeto Tracer del objeto TracerProvider
  5. Genera un intervalo a partir del objeto Tracer

Por el momento, no es necesario que comprendas las propiedades detalladas de cada componente, pero lo más importante que debes recordar es lo siguiente:

  • Aquí, el exportador se puede conectar a TracerProvider.
  • TracerProvider contiene toda la configuración relacionada con el muestreo y la exportación de registros.
  • Todos los registros se agrupan en el objeto Tracer.

Ahora que comprendes esto, pasemos al trabajo de codificación real.

Instrumenta el primer intervalo

Instrumenta el servicio del generador de carga

Presiona el botón 776a11bfb2122549.png en la parte superior derecha de Cloud Shell para abrir el editor 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, verás el bucle que llama a la función run. 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 del intervalo para hacer un seguimiento de la latencia de la llamada a la función.

Primero, como se indicó en la sección anterior, configuremos todas las opciones 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 facilitar la lectura, 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 el que se describe en la sección anterior. En esta implementación, usamos un exportador de stdout que exporta toda la información de seguimiento a stdout en un formato estructurado.

Luego, la llamas 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, deberás crear un tramo con un ID de seguimiento y un ID de tramo ú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"
)

Dado que el generador de carga llama al servicio del 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 de paquete httpClient para llamar a 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. Esto es lo que harás:

  1. Obtén un Tracer de TracerProvider global con otel.Tracer()
  2. Crea un tramo raíz con el método Tracer.Start()
  3. Finaliza el intervalo raíz en un momento arbitrario (en este caso, al 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 tus go.mod y go.sum con el comando go mod.

go mod tidy

Servicio de instrumentos para clientes

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

bcaccd06691269f8.png

Abre el editor de Cloud Shell y agrega los paquetes necesarios, como lo hicimos para el servicio de generador 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
)

Nuevamente, debemos configurar OpenTelemetry. Solo copia y pega la función initTracer de loadgen y llámala también en la función main del servicio del 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 tramos. Dado que el servicio del cliente debe aceptar solicitudes HTTP del servicio de loadgen, debe instrumentar el controlador. El servidor HTTP en el servicio del 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 el controlador HTTP se registra 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 intervalos 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 comienzo del método handler hasta el final. Para que los tramos sean fáciles de analizar, agrega un atributo adicional que almacene el recuento coincidente en la consulta. Justo antes de la línea de registro, agrega el siguiente 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))
        ...

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 un tiempo de ejecución de los servicios en el clúster de GKE, 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 en el registro. También verás que el atributo de incorporación "query" tiene la cadena de consulta que se pasa al servicio del cliente.

Resumen

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

Cuál es el próximo paso

En el siguiente paso, instrumentarás el servicio del cliente y el servicio del 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 estos microservicios. En este paso, intentaremos instrumentar la comunicación de gRPC entre el servicio del cliente y el servicio del servidor. (Rectángulo verde y violeta en la siguiente imagen)

75310d8e0e3b1a30.png

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

El ecosistema de OpenTelemetry ofrece muchas bibliotecas útiles 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, importa el paquete de gRPC precompilado 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 del cliente es un cliente de gRPC en comparación con el servicio del 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 prediseñada para el servidor de gRPC

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

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 instrumentas el servidor, primero debes configurar OpenTelemetry, de manera similar a lo que hicimos para los servicios de loadgen y 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 donde 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 registro

Luego, ejecuta el código modificado con el comando de Skaffold.

skaffold dev

Ahora, de nuevo, verás mucha información de intervalos 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(). Aun así, obtendrás una gran cantidad de intervalos porque los interceptores de gRPC los generaron.

Resumen

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

Cuál es el próximo paso

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

6. Visualiza el registro con Cloud Trace

Instrumentaste seguimientos en todo el sistema con OpenTelemetry. Hasta ahora, aprendiste a instrumentar servicios HTTP y gRPC. Si bien 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 registros.

Usa el exportador de Cloud Trace

Una de las características más potentes de OpenTelemetry es su capacidad de conexión. Para visualizar todos los intervalos recopilados por tu instrumentación, solo debes reemplazar el exportador 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, en su lugar, 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 del cliente y del servidor.

Ejecuta el microservicio y confirma el registro

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

skaffold dev

Luego, no verás mucha información de intervalos en formato de registros estructurados en stdout, ya que 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 seguimiento". Se puede acceder fácilmente 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 registro. 4fd10960c6648a03.png

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

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

Resumen

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

Cuál es el próximo paso

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

7. Agrega un subintervalo para mejorar el análisis

En el paso anterior, descubriste que la causa del tiempo de ida y vuelta observado desde loadgen se debe principalmente al 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 obtener más estadísticas del gráfico de cascada. Este es un caso común cuando comenzamos a instrumentar microservicios.

3b63a1e471dddb8c.png

En esta sección, vamos a instrumentar un subsegmento en el que el servidor llama a Google Cloud Storage, ya que es común que algunas E/S de red externas tarden mucho tiempo en el proceso y es importante identificar si la llamada es la causa.

Instrumenta un subsegmento en el servidor

Abre main.go en el servidor y busca la función readFiles. Esta función llama a 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 subsegmento, como lo hiciste para la instrumentación del servidor HTTP en el servicio del 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 registro

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

skaffold dev

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 por el rectángulo rojo a continuación)

3d4a891aa30d7a32.png

Lo que puedes deducir de este gráfico es que la llamada externa a Google Cloud Storage ocupa una gran cantidad de latencia, pero otros factores siguen generando la mayor parte de la latencia.

Ya obtuviste muchas estadísticas con solo observar un par de veces el gráfico de cascada de seguimiento. ¿Cómo obtienes más detalles sobre el rendimiento en tu aplicación? Aquí es donde entra en juego el generador de perfiles, pero, por ahora, terminemos este codelab y deleguemos todos los instructivos del generador de perfiles en 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 Cloud Trace de Google Cloud.

En el caso de los 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 filtras esos intervalos de Cloud Trace? La pista está aquí.
  • Correlaciona los registros de eventos con los intervalos y observa cómo funciona en Google Cloud Trace y Google Cloud Logging. La pista está aquí.
  • Reemplaza algún servicio por uno en otro idioma y trata de instrumentarlo con OpenTelemetry para ese idioma.

Además, si deseas obtener información sobre el generador de perfiles después de esto, continúa con la parte 2. En ese caso, puedes omitir la sección de limpieza que se incluye 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 que no se te cobren cargos inesperados en Google Kubernetes Engine, Google Cloud Trace ni 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 del 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.