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 generador de perfiles continuos

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.

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 segunda parte de la serie y abarca la instrumentación de un agente de generador de perfiles continuo. En la parte 1, se aborda el seguimiento distribuido con OpenTelemetry y Cloud Trace, y aprenderás más sobre cómo identificar mejor el cuello de botella de los microservicios en la parte 1.

Qué compilarás

En este codelab, instrumentarás un agente de generador de perfiles continuo en el servicio del servidor de la "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.

En la parte 1, descubriste que el cuello de botella existe 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 generador de perfiles continuo 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:

837b3e8554645.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 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 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 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, instrumentarás un agente de generador de perfiles 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 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 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 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 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 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 del agente de Cloud Profiler

Concepto de la generación de perfiles continua

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

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

Debido a que el destino es una aplicación en ejecución, hay una manera de recopilar datos de perfil periódicamente y enviarlos a algún backend que procese posteriormente los datos estadísticos. Ese es el agente de Cloud Profiler, que pronto lo incorporarás al servicio del servidor.

Incorporar el agente de Cloud Profiler

Para abrir el editor de Cloud Shell, presiona el botón 776a11bfb2122549.png ubicado en la parte superior derecha 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 un código de configuración para OpenTelemetry y gRPC, como se hizo en la parte 1 del codelab. Ahora agregarás instrumentación para el agente de Cloud Profiler aquí. Al igual que hicimos con initTracer(), puedes escribir una función llamada initProfiler() para facilitar la lectura.

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

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

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

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

Ahora, lo 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. Porque profiler.Start() bloquea, por lo que debes ejecutarlo en otra goroutine. Ya está listo para la compilación. Asegúrate de ejecutar go mod tidy antes de la implementación.

go mod tidy

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

skaffold dev

Por lo general, el gráfico tipo llama en Cloud Profiler tarda un par de minutos. Escribe "Generador de perfiles" 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 tipo llama.

7f80797dddc0128d.png

Resumen

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

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 tipo llama.

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

¿Qué es Flame Graph?

Flame Graph es una de las formas de visualizar los datos del perfil. Para obtener una explicación detallada, consulte nuestro documento. Sin embargo, el resumen breve es el siguiente:

  • Cada barra expresa la llamada al método o a la función en la aplicación
  • La dirección vertical es la pila de llamadas. la pila de llamadas crece de arriba abajo
  • La dirección horizontal es el uso de recursos. cuanto más largas sean, peores.

Por consiguiente, veamos el gráfico tipo llama obtenido.

7f80797dddc0128d.png

Análisis del gráfico tipo llama

En la sección anterior, aprendiste que cada barra del gráfico tipo llama expresa la llamada a 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 tipo llama de Cloud Profiler ordena la barra en orden descendente o por su longitud de izquierda a derecha. Puedes comenzar a mirar primero la parte superior izquierda del gráfico.

6d90760c6c1183cd.png

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

En GetMatchCount, puedes ver una serie de funciones regexp: regexp.MatchString y regexp.Compile. Provienen del paquete estándar, es decir, deben probarse correctamente desde muchos puntos de vista, incluido el rendimiento. Sin embargo, el resultado muestra que el uso de recursos de tiempo de CPU es alto en regexp.MatchString y regexp.Compile. Teniendo en cuenta estos hechos, la suposición aquí es que el uso de regexp.MatchString está relacionado con problemas de rendimiento. 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. Al leer el código fuente, puedes observar 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 expresiones regulares en cada llamada. Así que la causa del gran consumo de recursos suena así:

Resumen

En este paso, realizaste la suposición de la causa del consumo de recursos mediante el análisis del gráfico tipo llama.

Cuál es el próximo paso

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

6. Actualiza el código fuente y compara los gráficos tipo llama

Actualiza el código fuente

En el paso anterior, suponiste que el uso de regexp.MatchString tiene algo que ver con el gran consumo de recursos. 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 de patrones de expresiones regulares se extrae de regexp.MatchString y se quita 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)
        }
}

Veamos cómo funciona. Implementa el clúster con el comando de Skaffold.

skaffold dev

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

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, se redujo la longitud de la barra de GetMatchCount y la proporción de uso del tiempo de CPU (es decir, la barra se acortó).

e3a1456b4aada9a5.png

No solo si observas el gráfico tipo llama de una sola versión, también puedes comparar las diferencias entre dos versiones.

841dec77d8ba5595.png

Cambiar el valor de "Comparar con" lista desplegable a "Versión". y cambia el valor de "Comparada versión" a "1.0.0", la versión original.

5553844292d6a537.png

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

  • Azul: el valor (consumo de recursos) reducido
  • Naranja: El valor (consumo de recursos) obtenido
  • Gris: neutro

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

ca08d942dc1e2502.png

Dice que el tiempo de CPU total se reduce de 5.26 s a 2.88 s (el total es 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 de perfil.

Resumen

En este paso, realizaste una edición en el servicio de 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 de servidor y confirmarás el cambio de la versión 1.0.0.

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

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

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

En este paso, revisaremos el grafo de cascada del seguimiento distribuido (Cloud Trace) y veremos la diferencia en la generación de perfiles continua.

Este gráfico de cascada es uno de los seguimientos con la consulta "love". Esto está tardando alrededor de 6.7 s (6,700 ms) en total.

e2b7dec25926ee51.png

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

feeb7207f36c7e5e.png

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

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

Resumen

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

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.