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 activamente su sistema. En ese contexto, tres pilares de observabilidad: los registros, las métricas y los 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 la 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 impulsado por la comunidad bajo la CNCF. Con el uso de las bibliotecas que proporcionan el proyecto y su ecosistema, los desarrolladores pueden instrumentar sus aplicaciones de manera independiente del proveedor y en función de múltiples arquitecturas.

Además de los tres pilares de observabilidad, la creación de perfiles continua es otro componente clave para la observabilidad y expande la base de usuarios en el sector. 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 primera parte de la serie y abarca la instrumentación de seguimientos distribuidos en microservicios con OpenTelemetry y Cloud Trace. En la parte 2, se abordará la generación de perfiles continua con Cloud Profiler.

Seguimiento distribuido

Entre registros, métricas y seguimientos, Trace 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 fuerte controlador para descubrir cuellos de botella de latencia en el sistema distribuido en general.

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

Un intervalo representa una unidad de trabajo individual que se realiza en un sistema distribuido y que registra los tiempos de inicio y finalización. Los intervalos suelen tener relaciones jerárquicas entre sí. En la imagen que aparece a continuación, todos los intervalos más pequeños son intervalos secundarios de un intervalo /messages grande y se ensamblan en un solo seguimiento que muestra la ruta del trabajo a través de un sistema.

Un seguimiento

Google Cloud Trace es una de las opciones de backend de seguimiento distribuido y está bien integrado a 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 conocido como Shakesapp) que se ejecuta en un clúster de Google Kubernetes Engine. A continuación, se describe la arquitectura de Shakesapp:

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 todos los trabajos de Shakespare en formato de texto de Google Cloud Storage, busca las líneas que contienen la consulta y devuelve el número de la línea que coincidió con el cliente.

instrumentarás la información de seguimiento en toda la solicitud. Después de eso, incorporarás un agente de generación de perfiles en el servidor e investigarás el cuello de botella.

Qué aprenderás

  • Cómo comenzar a usar las bibliotecas de Trace de OpenTelemetry en el proyecto de Go
  • Cómo crear un intervalo con la biblioteca
  • Cómo propagar contextos de intervalos entre los componentes de una 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 facilitar la comprensión, este ejemplo solo contiene 3 componentes (generador de cargas, cliente y servidor), pero puedes aplicar el mismo proceso que se explica en este codelab en sistemas más grandes y complejos.

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:

O

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 mencionará más adelante en este codelab como PROJECT_ID.

A continuación, si aún no lo hiciste, 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 forma 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, simplemente haz clic en Activar Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (el aprovisionamiento y la conexión al entorno debería tardar solo unos momentos).

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 de idioma en 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 superior.

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

Habilitar Kubernetes Engine

Primero, configuramos un clúster de Kubernetes en el que Shakesapp se ejecuta en GKE, así que debemos habilitar GKE. Navega al menú “Kubernetes Engine”. y presiona el botón HABILITAR.

548cfd95bc6d344d.png

Ya está todo listo para 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é debajo de la región que usarás para la creación del 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, prepararemos un registro de contenedores para implementar y enviar contenedores. Para estos pasos, debemos configurar Artifact Registry (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 un momento, verás el navegador del repositorio de GAR. Haz clic en "CREAR REPOSITORIO". e ingresa el nombre del repositorio.

d6a70f4cb4ebcbe3.png

En este codelab, asignaré el nombre trace-codelab al nuevo repositorio. El formato del artefacto es “Docker”. y el tipo de ubicación es "Región". Elige una región cercana a la que estableciste para la zona predeterminada de Google Compute Engine. Por ejemplo, este ejemplo eligió “us-central1-f” arriba, así que aquí elegimos "us-central1 (Iowa)". Luego, haz clic en "CREATE" .

8c2d1ce65258ef70.png

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

7a3c1f47346bea15.png

Regresaremos más adelante 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 se envían 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 hasta el 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 los recorridos de 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 registro en el portapapeles.

e0f2ae2144880b8b.png

Cuando hagas clic en el botón de copiar, verás un diálogo en la parte inferior del navegador con el siguiente mensaje:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; se copió

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 establecer 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 está todo listo para el próximo paso que te permitirá 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 Container Registry.
  • Configura Skaffold para usar Container Registry
  • 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 y, luego, 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 está todo listo para ejecutar microservicios completos sobre ellos. El material del codelab está alojado 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
  • 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 de los siguientes pasos. (La parte 1 abarca los pasos 0 y 4, y la parte 2 abarca los pasos 5 y 6).

Ejecuta el comando de Skaffold

Por último, estarás 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 Skaffold hace todo por ti. Probemos eso 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 enviar 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 de aplicación reales 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 los mensajes del servidor. 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 tu 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 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 sencillo.

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 loadgen hasta el servicio del servidor.

Las aplicaciones deben enviar metadatos de Trace, como el ID de seguimiento y el ID de intervalo, para que Cloud Trace ensambla todos los intervalos que tienen el mismo ID de Trace 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) cuando se solicitan servicios downstream, para que puedan saber qué contexto de seguimiento están manejando.

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 ayuden a analizar seguimientos

Componentes en OpenTelemetry Trace

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 de forma global.
  3. Configura TextMapPropagaror para configurar el método de propagación
  4. Obtén el rastreador de TracerProvider
  5. Cómo generar intervalos desde el rastreador

A partir de ahora, no es necesario que comprendas las propiedades detalladas de cada componente, pero lo más importante que debes recordar es lo siguiente:

  • El exportador se puede conectar a TracerProvider.
  • TracerProvider conserva toda la configuración relacionada con el muestreo y la exportación de seguimientos
  • todos los seguimientos se agrupan en objetos Tracer

Ahora que conoces esto, pasemos al trabajo real de programación.

Primer intervalo del instrumento

Servicio de generador de cargas de instrumentos

Para abrir el editor de Cloud Shell, presiona el botón 776a11bfb2122549.png ubicado 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, 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 comienzo y el final de la llamada a función. Ahora instrumentaremos la información del intervalo para hacer un seguimiento de la latencia de la llamada a función.

Primero, como se indicó en la sección anterior, configuremos todos los parámetros de configuración de OpenTelemetry. Agrega 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
}

Tal vez 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 stdout que exporta toda la información de seguimiento al stdout en un formato estructurado.

Luego, la llamarás 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)
        ...

Cuando termines la configuración, deberás crear un intervalo con un ID de seguimiento y un ID de intervalo únicos. OpenTelemetry proporciona una biblioteca práctica para ello. Agrega paquetes nuevos adicionales al cliente HTTP de instrumento.

step0/src/loadgen/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/http/httptrace" // step1. add packages
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        // step1. end add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
)

Debido a que 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 de los paquetes httptrace y otelhttp.

Primero, se agregó 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. Lo que harás es lo siguiente:

  1. Obtenga un rastreador 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 una sincronización arbitraria (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 el servicio de cliente

En la sección anterior, instrumentamos la parte delimitada en el rectángulo rojo en el siguiente dibujo. Implementamos 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 del servicio de generador de cargas es que el servicio de cliente necesita extraer la información del ID de Trace que se propaga del servicio de generador de cargas en el encabezado HTTP y usar el ID para generar intervalos.

bcaccd06691269f8.png

Abre el editor de Cloud Shell y agrega los paquetes requeridos como lo hicimos para el servicio del 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 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 intervalos. Debido a que el servicio de cliente necesita aceptar solicitudes HTTP del servicio 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 el controlador HTTP está registrado 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) controlador() y agrega instrumentación de intervalo 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 facilitar el análisis de los intervalos, agrega un atributo adicional que almacene el recuento de coincidencias para 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 para ejecutar 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 del seguimiento. Además, descubres 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 de cliente que se comunican en HTTP y confirmaste que pudiste propagar correctamente el Contexto de seguimiento en todos los servicios y exportar la información de intervalo 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 Trace Context 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, intentamos instrumentar la comunicación de gRPC entre el servicio del cliente y el servicio del servidor. (Rectángulos verde y púrpura en la imagen de abajo)

75310d8e0e3b1a30.png

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

El ecosistema de OpenTelemetry ofrece una gran cantidad de bibliotecas prácticas que ayudan a los desarrolladores a instrumentar aplicaciones. En el paso anterior, usamos instrumentación previa a la compilación para el paquete net/http. En este paso, ya que intentamos propagar Trace Context 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 de cliente es un cliente de gRPC para 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 intervalos nuevos cada vez que el cliente realiza 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 necesitas hacerlo.

Instrumentación compilada previamente para el servidor de gRPC

Al igual que con 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 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 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 seguimiento

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

skaffold dev

Una vez más, 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] }
...

Notaste que no incorporaste nombres de intervalos ni los creaste manualmente con trace.Start() o span.SpanFromContext(). De todas formas, 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 compatibilidad de las bibliotecas del ecosistema de OpenTelemetry.

Cuál es el próximo paso

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

6. Visualiza el seguimiento con Cloud Trace

Has instrumentado registros en todo el sistema con OpenTelemetry. Hasta ahora, aprendiste a instrumentar servicios HTTP y gRPC. Si bien has aprendido a instrumentarlas, todavía no has aprendido a analizarlas. En esta sección, reemplazarás los exportadores stdout por los exportadores de Cloud Trace y aprenderás a analizar los seguimientos.

Usar el exportador de Cloud Trace

Una de las características más importantes de OpenTelemetry es su capacidad de conexión. Para visualizar todos los intervalos recopilados por tu instrumentación, lo que debes hacer es 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 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 de cliente y del servidor.

Ejecuta el microservicio y confirma el seguimiento

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

skaffold dev

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

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 enviaron correctamente a Cloud Trace. Accede a la consola de Cloud y navega a “Lista de seguimientos”. Se puede acceder a él fácilmente desde el cuadro de búsqueda. De lo contrario, puedes hacer clic en el menú del panel izquierdo. 8b3f8411bd737e06.png

Verás que se distribuyen muchos puntos azules por 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 por esta simple mirada rápida, ya tienes mucha información útil. 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 1 en la imagen de arriba). Puedes confirmarlo en la tabla de resumen. (La columna de la derecha muestra la duración de cada intervalo). Además, este seguimiento era para la consulta "friend". (Consulta 2 de la imagen de arriba).

Dados 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 poderosa. Para obtener más información sobre los detalles de Cloud Trace, consulta nuestra documentación oficial.

Resumen

En este paso, reemplazaste el exportador stdout por Cloud Trace uno y visualizaste los seguimientos en Cloud Trace. También aprendiste 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 intervalo secundario en GetMatchCount.

7. Agrega un intervalo secundario 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, debido a que no instrumentamos nada más que el controlador, no podemos encontrar estadísticas adicionales del gráfico de cascada. Este es un caso común cuando empezamos a instrumentar microservicios.

3b63a1e471dddb8c.png

En esta sección, instrumentaremos un intervalo secundario en el que el servidor llama a Google Cloud Storage, ya que es común cuando alguna E/S de red externa tarda mucho tiempo en el proceso y es importante identificar si la causa es la llamada.

Instrumenta un intervalo secundario 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 intervalo secundario, como hiciste con 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 mediante la ejecución de la app.

Ejecuta el microservicio y confirma el seguimiento

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

skaffold dev

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

3d4a891aa30d7a32.png

Lo que se puede observar a partir de este gráfico ahora es que la llamada externa a Google Cloud Storage ocupa una gran cantidad de latencia, pero, de todas formas, otros factores hacen 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 los detalles de rendimiento adicionales en tu aplicación? Aquí entra en juego el generador de perfiles, pero, por ahora, llegaremos al final de este codelab y deleguemos 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 estadísticas más detalladas sobre la latencia del sistema.

8. Felicitaciones

Creaste correctamente seguimientos distribuidos con OpenTelemery y confirmaste latencias de solicitud en el microservicio de Google Cloud Trace.

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

  • La implementación actual envía todos los intervalos que genera la verificación de estado. (grpc.health.v1.Health/Check) ¿Cómo se filtran esos intervalos de Cloud Trace? Puedes ver una pista aquí.
  • Correlaciona los registros de eventos con los intervalos y descubre cómo funcionan en Google Cloud Trace y Google Cloud Logging. Puedes ver una pista aquí.
  • Reemplaza algún servicio por uno en otro idioma y trata de instrumentarlo con OpenTelemetry para ese idioma.

Además, si deseas aprender 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 aparece más abajo.

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 de Google Kubernetes Engine, Google Cloud Trace o 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 & Administrador &gt; “Configuración” y, luego, haz clic en “APAGAR” .

45aa37b7d5e1ddd1.png

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