Instrumento para obtener un mejor rendimiento en tu app en Go (parte 2: generador de perfiles)

1. Introducción

e0509e8a07ad5537.png

Última actualización: 14/07/2022

Observabilidad de la aplicación

Observabilidad y Continuous Profiler

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.

Además de los tres pilares de la observabilidad, la generación de perfiles continua es otro componente clave de la observabilidad y está expandiendo 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 2 de la serie y abarca la instrumentación de un agente de generación de perfiles continuo. La parte 1 abarca el seguimiento distribuido con OpenTelemetry y Cloud Trace, y aprenderás a identificar mejor el cuello de botella de los microservicios con la parte 1.

Qué compilarás

En este codelab, instrumentarás el agente del Profiler continuo en el servicio del servidor de la "aplicación Shakespeare" (también conocida como Shakesapp) que se ejecuta en un clúster de Google Kubernetes Engine. La arquitectura de Shakesapp es la que se describe a continuación:

44e243182ced442f.png

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

En la parte 1, descubriste que el cuello de botella se encuentra en algún lugar del servicio del servidor, pero no pudiste identificar la causa exacta.

Qué aprenderás

  • Cómo incorporar el agente de Profiler
  • Cómo investigar el cuello de botella en Cloud Profiler

En este codelab, se explica cómo instrumentar un agente de generación de perfiles continua en tu aplicación.

Requisitos

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

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

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

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

7a32e5469db69e9.png

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

7136b3ee36ebaf89.png

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

870a3cbd6541ee86.png

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

affdc444517ba805.png

Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se hará referencia a él más adelante en este codelab como PROJECT_ID.

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

15d0ef27a8fbab27.png

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

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

Configuración de Google Cloud Shell

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

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

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

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

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

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

gcloud auth list

Resultado del comando

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

Resultado del comando

[core]
project = <PROJECT_ID>

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

gcloud config set project <PROJECT_ID>

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

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

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

echo $GOOGLE_CLOUD_PROJECT

Resultado del comando

<PROJECT_ID>

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

gcloud config set compute/zone us-central1-f

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

Configuración del lenguaje Go

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

go version

Resultado del comando

go version go1.18.3 linux/amd64

Configura un clúster de Google Kubernetes

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

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

Habilita Kubernetes Engine

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

548cfd95bc6d344d.png

Ya puedes crear un clúster de Kubernetes.

Crea un clúster de Kubernetes

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

gcloud container clusters create otel-trace-codelab2 \
--zone us-central1-f \
--release-channel rapid \
--preemptible \
--enable-autoscaling \
--max-nodes 8 \
--no-enable-ip-alias \
--scopes cloud-platform

Resultado del comando

Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done.     
Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403
kubeconfig entry generated for otel-trace-codelab2.
NAME: otel-trace-codelab2
LOCATION: us-central1-f
MASTER_VERSION: 1.23.6-gke.1501
MASTER_IP: 104.154.76.89
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.23.6-gke.1501
NUM_NODES: 3
STATUS: RUNNING

Configuración de Artifact Registry y Skaffold

Ahora tenemos un clúster de Kubernetes listo para la implementación. A continuación, nos preparamos para un registro de contenedores para enviar e implementar contenedores. Para estos pasos, debemos configurar un registro de 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 unos instantes, verás el navegador de repositorios de GAR. Haz clic en el botón “CREATE REPOSITORY” y, luego, ingresa el nombre del repositorio.

d6a70f4cb4ebcbe3.png

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

9c2d1ce65258ef70.png

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

7a3c1f47346bea15.png

Volveremos aquí más tarde para verificar la ruta del registro.

Configuración de Skaffold

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

Abre Cloud Shell nuevamente y confirma si Skaffold está instalado. (Cloud Shell instala Skaffold en el entorno de forma predeterminada). Ejecuta el siguiente comando y observa la versión de Skaffold.

skaffold version

Resultado del comando

v1.38.0

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

7a3c1f47346bea15.png

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

e0f2ae2144880b8b.png

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

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

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

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

Resultado del comando

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

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

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

Resultado del comando

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

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

Resumen

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

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

Cuál es el próximo paso

En el siguiente paso, instrumentarás el agente del analizador continuo en el servicio del servidor.

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 step4. También puedes consultar el código fuente en las carpetas step[1-6] para ver los cambios desde el principio. (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 step4
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 del agente de Cloud Profiler

Concepto de creación continua de perfiles

Antes de explicar el concepto de generación de perfiles continua, primero debemos comprender el de generación de perfiles. La creación de perfiles es una de las formas de analizar la aplicación de forma dinámica (análisis dinámico del programa) y, por lo general, se realiza durante el desarrollo de la aplicación en el proceso de prueba de carga, etcétera. Esta es una actividad de una sola vez para medir las métricas del sistema, como el uso de CPU y memoria, durante el período específico. Después de recopilar los datos de perfil, los desarrolladores los analizan fuera del código.

La generación de perfiles continua es el enfoque extendido de la generación de perfiles normal: ejecuta perfiles de períodos cortos en la aplicación de ejecución prolongada de forma periódica y recopila una gran cantidad de datos de perfil. Luego, genera automáticamente el análisis estadístico en función de un atributo determinado de la aplicación, como el número de versión, la zona de implementación, el tiempo de medición, etcétera. Encontrarás más detalles sobre el concepto en nuestra documentación.

Dado que el destino es una aplicación en ejecución, hay una forma de recopilar datos de perfil de forma periódica y enviarlos a un backend que posprocese los datos estadísticos. Ese es el agente de Cloud Profiler, y lo incorporarás al servicio del servidor en breve.

Cómo incorporar el agente de Cloud Profiler

Presiona el botón 776a11bfb2122549.png en la parte superior derecha de Cloud Shell para abrir el editor de Cloud Shell. Abre step4/src/server/main.go desde el explorador en el panel izquierdo y busca la función principal.

step4/src/server/main.go

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

        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)
        healthpb.RegisterHealthServer(srv, svc)
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("error serving server: %v", err)
        }
}

En la función main, verás código de configuración para OpenTelemetry y gRPC, que se realizó en la parte 1 del codelab. Ahora, agregarás la instrumentación para el agente de Cloud Profiler aquí. Al igual que lo hicimos con initTracer(), puedes escribir una función llamada initProfiler() para mejorar la legibilidad.

step4/src/server/main.go

import (
        ...
        "cloud.google.com/go/profiler" // step5. add profiler package
        "cloud.google.com/go/storage"
        ...
)

// step5: add Profiler initializer
func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.0.0",
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Analicemos en detalle las opciones especificadas en el objeto profiler.Config{}.

  • Servicio: Es el nombre del servicio que puedes seleccionar y activar en el panel del generador de perfiles.
  • ServiceVersion: Es el nombre de la versión del servicio. Puedes comparar conjuntos de datos de perfiles según este valor.
  • NoHeapProfiling: Inhabilita la generación de perfiles de consumo de memoria.
  • NoAllocProfiling: Inhabilita la generación de perfiles de asignación de memoria.
  • NoGoroutineProfiling: Inhabilita la generación de perfiles de goroutines
  • NoCPUProfiling: Inhabilita la generación de perfiles de CPU.

En este codelab, solo habilitaremos la generación de perfiles de la CPU.

Ahora, lo único que debes hacer es llamar a esta función en la función main. Asegúrate de importar el paquete de Cloud Profiler en el bloque de importación.

step4/src/server/main.go

func main() {
        ...
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        // step5. start profiler
        go initProfiler()
        // step5. end

        svc := NewServerService()
        // step2: add interceptor
        ...
}

Ten en cuenta que llamas a la función initProfiler() con la palabra clave go. Esto se debe a que profiler.Start() se bloquea, por lo que debes ejecutarlo en otra goroutine. Ahora está listo para compilarse. Asegúrate de ejecutar go mod tidy antes de la implementación.

go mod tidy

Ahora implementa tu clúster con el nuevo servicio de servidor.

skaffold dev

Por lo general, el gráfico de llamas tarda unos minutos en aparecer en Cloud Profiler. Escribe "profiler" en el cuadro de búsqueda de la parte superior y haz clic en el ícono de Profiler.

3d8ca8a64b267a40.png

Luego, verás el siguiente gráfico de llamas.

7f80797dddc0128d.png

Resumen

En este paso, incorporaste el agente de Cloud Profiler al servicio del servidor y confirmaste que genera un gráfico de llamas.

Cuál es el próximo paso

En el siguiente paso, investigarás la causa del cuello de botella en la aplicación con el gráfico de llamas.

5. Analiza el gráfico tipo llama de Cloud Profiler

¿Qué es el gráfico de llamas?

El gráfico de llamas es una de las formas de visualizar los datos del perfil. Para obtener una explicación detallada, consulta nuestro documento, pero el resumen es el siguiente:

  • Cada barra expresa la llamada a un método o función en la aplicación.
  • La dirección vertical es la pila de llamadas, que crece de arriba hacia abajo.
  • La dirección horizontal representa el uso de recursos; cuanto más larga, peor.

Teniendo en cuenta esto, veamos el gráfico de llamas obtenido.

7f80797dddc0128d.png

Cómo analizar el gráfico de llamas

En la sección anterior, aprendiste que cada barra del gráfico de llamas expresa la llamada a la función o al método, y su longitud significa el uso de recursos en la función o el método. El gráfico de llamas de Cloud Profiler ordena la barra en orden descendente o la longitud de izquierda a derecha, por lo que puedes comenzar por la parte superior izquierda del gráfico.

6d90760c6c1183cd.png

En nuestro caso, es explícito que grpc.(*Server).serveStreams.func1.2 consume la mayor parte del tiempo de CPU y, si observamos la pila de llamadas de arriba hacia abajo, se dedica la mayor parte del tiempo a main.(*serverService).GetMatchCount, que es el controlador del servidor de gRPC en el servicio del servidor.

En GetMatchCount, verás una serie de funciones regexp: regexp.MatchString y regexp.Compile. Son del paquete estándar, es decir, deben estar bien probadas desde muchos puntos de vista, incluido el rendimiento. Sin embargo, el resultado aquí muestra que el uso de recursos de tiempo de CPU es alto en regexp.MatchString y regexp.Compile. Teniendo en cuenta esos hechos, la suposición aquí es que el uso de regexp.MatchString tiene algo que ver con los problemas de rendimiento. Así que leamos el código fuente en el que se usa la función.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line, query := strings.ToLower(line), strings.ToLower(req.Query)
                        isMatch, err := regexp.MatchString(query, line)
                        if err != nil {
                                return resp, err
                        }
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Este es el lugar donde se llama a regexp.MatchString. Si lees el código fuente, notarás que se llama a la función dentro del bucle for anidado. Por lo tanto, el uso de esta función puede ser incorrecto. Busquemos el GoDoc de regexp.

80b8a4ba1931ff7b.png

Según el documento, regexp.MatchString compila el patrón de expresión regular en cada llamada. Por lo tanto, la causa del gran consumo de recursos suena así.

Resumen

En este paso, analizaste el gráfico de llamas para suponer la causa del consumo de recursos.

Cuál es el próximo paso

En el siguiente paso, actualizarás el código fuente del servicio del servidor y confirmarás el cambio desde la versión 1.0.0.

6. Actualiza el código fuente y compara los gráficos de llamas

Actualiza el código fuente

En el paso anterior, supusiste que el uso de regexp.MatchString tiene algo que ver con el gran consumo de recursos. Así que resolvamos esto. Abre el código y cambia un poco esa parte.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }

        // step6. considered the process carefully and naively tuned up by extracting
        // regexp pattern compile process out of for loop.
        query := strings.ToLower(req.Query)
        re := regexp.MustCompile(query)
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line = strings.ToLower(line)
                        isMatch := re.MatchString(line)
                        // step6. done replacing regexp with strings
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Como puedes ver, ahora el proceso de compilación del patrón de expresión regular se extrae de regexp.MatchString y se mueve fuera del bucle for anidado.

Antes de implementar este código, asegúrate de actualizar la cadena de versión en la función initProfiler().

step4/src/server/main.go

func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.1.0", // step6. update version
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Ahora veamos cómo funciona. Implementa el clúster con el comando skaffold.

skaffold dev

Después de un tiempo, vuelve a cargar el panel de Cloud Profiler y observa cómo se ve.

283cfcd4c13716ad.png

Asegúrate de cambiar la versión a "1.1.0" para que solo veas los perfiles de la versión 1.1.0. Como puedes ver, la longitud de la barra de GetMatchCount se redujo y la proporción de uso del tiempo de CPU (es decir, la barra se acortó).

e3a1456b4aada9a5.png

No solo puedes observar el gráfico de llamas de una sola versión, sino que también puedes comparar las diferencias entre dos versiones.

841dec77d8ba5595.png

Cambia el valor de la lista desplegable "Comparar con" a "Versión" y el valor de "Versión comparada" a "1.0.0", la versión original.

5553844292d6a537.png

Verás este tipo de gráfico de llamas. La forma del gráfico es la misma que la de la versión 1.1.0, pero el color es diferente. En el modo de comparación, el significado de los colores es el siguiente:

  • Azul: El valor (consumo de recursos) se redujo.
  • Naranja: El valor (consumo de recursos) obtenido
  • Gris: Neutral

Dada la leyenda, analicemos la función con más detalle. Si haces clic en la barra que deseas acercar, podrás ver más detalles dentro de la pila. Haz clic en la barra main.(*serverService).GetMatchCount. Si colocas el cursor sobre la barra, también verás los detalles de la comparación.

ca08d942dc1e2502.png

Indica que el tiempo total de CPU se redujo de 5.26 s a 2.88 s (el total es de 10 s = ventana de muestreo). ¡Es una gran mejora!

Ahora puedes mejorar el rendimiento de tu aplicación a partir del análisis de los datos del perfil.

Resumen

En este paso, editaste el servicio del servidor y confirmaste la mejora en el modo de comparación de Cloud Profiler.

Cuál es el próximo paso

En el siguiente paso, actualizarás el código fuente del servicio del servidor y confirmarás el cambio desde la versión 1.0.0.

7. Paso adicional: Confirma la mejora en la cascada de Trace

Diferencia entre el registro de seguimiento distribuido y la generación de perfiles continua

En la parte 1 del codelab, confirmaste que podías identificar el servicio de cuello de botella en los microservicios de una ruta de solicitud y que no podías identificar la causa exacta del cuello de botella en el servicio específico. En este codelab de la parte 2, aprendiste que el perfilado continuo te permite identificar el cuello de botella dentro del servicio único a partir de las pilas de llamadas.

En este paso, revisemos el gráfico de cascada del registro de seguimiento distribuido (Cloud Trace) y veamos la diferencia con el registro continuo de perfil.

Este gráfico de cascada es el de los registros con la búsqueda "amor". Tarda alrededor de 6.7 s (6,700 ms) en total.

e2b7dec25926ee51.png

Y esto es después de la mejora para la misma búsqueda. Como puedes ver, la latencia total ahora es de 1.5 s (1,500 ms), lo que representa una gran mejora con respecto a la implementación anterior.

feeb7207f36c7e5e.png

El punto importante aquí es que, en el gráfico de cascada del registro de seguimiento distribuido, la información de la pila de llamadas no está disponible, a menos que instrumentes los intervalos en todas partes. Además, los registros de seguimiento distribuidos solo se enfocan en la latencia entre los servicios, mientras que la generación de perfiles continua se enfoca en los recursos de la computadora (CPU, memoria, subprocesos del SO) de un solo servicio.

En otro aspecto, el registro de seguimiento distribuido es la base de eventos, mientras que el perfil continuo es estadístico. Cada registro tiene un gráfico de latencia diferente, y necesitas un formato diferente, como una distribución, para obtener la tendencia de los cambios de latencia.

Resumen

En este paso, verificaste la diferencia entre el registro de seguimiento distribuido y la generación de perfiles continua.

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 encuentra a continuación.

Realiza una limpieza

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

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

skaffold delete

Resultado del comando

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

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

45aa37b7d5e1ddd1.png

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