1. Introducción

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

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:

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

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

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

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

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.

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.
- Precios de Google Cloud's operations suite | Operations Suite
- Precios | Documentación de Kubernetes Engine
- Precios de Artifact Registry | Documentación de Artifact Registry
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
(el aprovisionamiento y la conexión al entorno debería llevar solo unos minutos).


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:

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:
- Descarga el proyecto de referencia en Cloud Shell
- Compila microservicios en contenedores
- Sube contenedores a Google Artifact Registry (GAR)
- Implementa contenedores en GKE
- Modifica el código fuente de los servicios para la instrumentación de seguimiento
- 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.

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.

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.

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

Ahora verás "trace-codelab" en el navegador de repositorios.

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.

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

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.

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

El proceso para instrumentar el seguimiento de la aplicación con OpenTelemetry es el siguiente:
- Crea un exportador
- Crea un TracerProvider que vincule el exportador en 1 y configúralo como global.
- Configura TextMapPropagaror para establecer el método de propagación
- Obtén el objeto Tracer del objeto TracerProvider
- 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
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:
- Obtén un Tracer de
TracerProviderglobal conotel.Tracer() - Crea un tramo raíz con el método
Tracer.Start() - 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.

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

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. 
Luego, verás que se distribuyen muchos puntos azules en el gráfico de latencia. Cada punto representa un solo seguimiento.

Haz clic en uno de ellos para ver los detalles dentro del registro. 
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.

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)

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

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