Seguimiento distribuido en una aplicación de microservicios

Fecha de actualización: 24-10-2023

Este documento es el tercero de una serie de cuatro partes sobre el diseño, la compilación y la implementación de microservicios. En esta serie, se describen los diversos elementos de una arquitectura de microservicios. En ella, se incluye información de los beneficios y las desventajas del patrón de arquitectura de microservicios, y cómo aplicarlo.

  1. Introducción a los microservicios
  2. Refactoriza de aplicaciones monolíticas en microservicios
  3. Comunicación entre servicios en una configuración de microservicios
  4. Seguimiento distribuido en una aplicación de microservicios (este documento)

Esta serie está dirigida a desarrolladores y arquitectos de aplicaciones que diseñan y, luego, implementan la migración para refactorizar una aplicación monolítica en una aplicación de microservicios.

En un sistema distribuido, es importante saber cómo fluye una solicitud de un servicio a otro y cuánto tiempo lleva realizar una tarea en cada servicio. Considera la aplicación Online Boutique basada en microservicios que implementaste en el documento anterior, Refactorización de una aplicación monolítica en microservicios. La aplicación consta de varios servicios. Por ejemplo, en la siguiente captura de pantalla, se muestra la página de detalles del producto, que recupera información de los servicios de frontend, recomendación y anuncios.

La página de detalles del producto.

Para procesar la página de detalles del producto, el servicio de frontend se comunica con el servicio de recomendación y el servicio de anuncios, como se muestra en el siguiente diagrama:

El servicio de frontend se comunica con el servicio de recomendación, el catálogo de productos y el servicio de anuncios.

Figura 1. Servicios escritos en diferentes lenguajes

En la figura 1, el servicio de frontend está escrito en Go. El servicio de recomendación, que está escrito en Python, usa gRPC para comunicarse con el servicio de frontend. El servicio de anuncios, que está escrito en Java, también usa gRPC para comunicarse con el servicio de frontend. Además de gRPC, el método de comunicación entre servicios también puede estar en HTTP de REST.

Cuando compilas un sistema distribuido, deseas que las herramientas de observabilidad proporcionen las siguientes estadísticas:

  • Los servicios por los que pasó una solicitud.
  • Dónde se produjeron retrasos cuando una solicitud fue lenta.
  • Dónde se produjo el error si falló la solicitud.
  • Cómo difiere la ejecución de la solicitud del comportamiento normal del sistema.
  • Si las diferencias en la ejecución de la solicitud se relacionan con el rendimiento (si algunas llamadas de servicio tardaron más o más tiempo de lo habitual).

Objetivos

  • Usar los archivos de manifiesto de kustomize para configurar la infraestructura.
  • Implementar la aplicación de ejemplo de Online Boutique en Google Kubernetes Engine (GKE).
  • Usar Cloud Trace para revisar el recorrido de un usuario en la aplicación de ejemplo.

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando termines esta guía, puedes borrar los recursos que creaste para evitar que se sigan facturando. Para obtener más información, consulta Realiza una limpieza.

Antes de comenzar

Si ya configuraste un proyecto con el documento anterior de esta serie, Comunicación entre servicios en una configuración de microservicios, puedes volver a usar el proyecto. Completa los siguientes pasos para habilitar las API adicionales y configurar las variables de entorno.

  1. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyectos

  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  3. En la consola de Google Cloud, activa Cloud Shell.

    Activar Cloud Shell

  4. Habilita las API para Compute Engine, GKE, Cloud SQL, Artifact Analysis, Trace y Container Registry:

     gcloud services enable \
       compute.googleapis.com \
       sql-component.googleapis.com \
       servicenetworking.googleapis.com\
       container.googleapis.com \
       containeranalysis.googleapis.com \
       containerregistry.googleapis.com \
       sqladmin.googleapis.com
    

Seguimiento distribuido

El seguimiento distribuido adjunta metadatos contextuales a cada solicitud y garantiza que los metadatos se compartan entre las solicitudes. Usa puntos de seguimiento para instrumentar el seguimiento distribuido. Por ejemplo, puedes instrumentar tus servicios (frontend, recomendación y anuncios) con dos puntos de seguimiento para controlar una solicitud del cliente a fin de ver los detalles de un producto: un punto de seguimiento para enviar la solicitud y otro seguimiento. para recibir la respuesta. En el diagrama siguiente, se muestra cómo funciona la instrumentación de este punto de seguimiento:

Una instrumentación de punto de seguimiento que tiene dos puntos de seguimiento.

Figura 2. Cada llamada entre servicios tiene dos puntos de seguimiento que consisten en un par de solicitud-respuesta.

Para que los puntos de seguimiento comprendan qué solicitud ejecutar cuando se invoca el servicio, el servicio de origen pasa un ID de seguimiento a lo largo del flujo de ejecución. El proceso que pasa el ID de seguimiento se denomina propagación de metadatos o propagación de contexto distribuido. La propagación de contexto transfiere metadatos a través de llamadas de red cuando los servicios de una aplicación distribuida se comunican entre sí durante la ejecución de una solicitud determinada. En el siguiente diagrama, se muestra la propagación de metadatos:

La propagación de metadatos pasa el ID de seguimiento.

Figura 3. Los metadatos de seguimiento se pasan entre servicios. Los metadatos incluyen información como qué servicio llama a qué servicio y sus marcas de tiempo.

En el ejemplo de Online Boutique, un seguimiento comienza cuando un usuario envía una solicitud inicial para recuperar los detalles del producto. Se genera un ID de seguimiento nuevo y cada solicitud sucesiva está decorada con encabezados que contienen metadatos contextuales de la solicitud original.

Cada operación individual que se invoca como parte de la entrega de la solicitud del usuario final se denomina intervalo. El servicio de origen etiqueta cada intervalo con su propio ID único y el ID de seguimiento del intervalo superior. En el siguiente diagrama, se muestra una visualización del gráfico de Gantt de un seguimiento:

Las operaciones individuales se etiquetan como intervalos.

Figura 4. Un intervalo superior incluye el tiempo de respuesta de los intervalos secundarios.

En la figura 4, se muestra un árbol de seguimiento en el que el servicio de frontend llama al servicio de recomendación y al servicio de anuncios. El servicio de frontend es el intervalo superior, que describe el tiempo de respuesta que observa el usuario final. Los intervalos secundarios describen cómo se llamaron y respondieron el servicio de recomendaciones y el de anuncios, incluida la información del tiempo de respuesta.

Una malla de servicios como Istio habilita el seguimiento distribuido del tráfico de servicio a servicio sin que se necesite instrumentación dedicada. Sin embargo, puede haber situaciones en las que desees tener más control sobre los seguimientos o que necesites realizar un seguimiento del código que no se ejecuta en una malla de servicios.

En este documento, se usa OpenTelemetry para habilitar la instrumentación de aplicaciones de microservicios distribuidas a fin de recopilar seguimientos y métricas. OpenTelemetry te permite recopilar métricas y seguimientos y, luego, exportarlos a backends, como Prometheus, Cloud Monitoring, Datadog, Graphite, Zipkin y Jaeger.

Instrumentación mediante OpenTelemetry

En las siguientes secciones, se muestra cómo usar la propagación de contexto para permitir que se agreguen intervalos de varias solicitudes a un solo seguimiento superior.

En este ejemplo, se usan las bibliotecas JavaScript, Python y Go de OpenTelemetry para instrumentar una implementación de seguimiento para los servicios de pago, recomendación y frontend. Según la verbosidad de la instrumentación, los datos de seguimiento pueden afectar el costo del proyecto (facturación de Cloud Trace). A fin de mitigar las inquietudes sobre los costos, la mayoría de los sistemas de seguimiento usan varias formas de muestreo para capturar solo un cierto porcentaje de los seguimientos observados. En los entornos de producción, es posible que tu organización tenga motivos para lo que desea mostrar y razones por las que desea hacerlo. Te recomendamos que personalices tu estrategia de muestreo en función de la administración de costos, el enfoque en seguimientos interesantes o el filtrado del ruido. Para obtener más información sobre el muestreo, consulta Muestreo de OpenTelemetry.

En este documento, se usa Trace para visualizar los seguimientos distribuidos. Usa un exportador de OpenTelemetry para enviar seguimientos a Trace.

Registra exportadores de seguimiento

En esta sección, se muestra cómo registrar el exportador de seguimiento en cada servicio mediante la adición de líneas al código del microservicio.

Para el servicio de frontend (escrito en Go), en la siguiente muestra de código, se registra el exportador:

[...]
exporter, err := otlptracegrpc.New(
        ctx,
        otlptracegrpc.WithGRPCConn(svc.collectorConn))
    if err != nil {
        log.Warnf("warn: Failed to create trace exporter: %v", err)
    }
tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithSampler(sdktrace.AlwaysSample()))
    otel.SetTracerProvider(tp)

Para el servicio de recomendación (escrito en Python), con la siguiente muestra de código, se registra el exportador:

if os.environ["ENABLE_TRACING"] == "1":
    trace.set_tracer_provider(TracerProvider())
    otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317")
    trace.get_tracer_provider().add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(
            endpoint = otel_endpoint,
            insecure = True
            )
        )
    )

Para el servicio de pago (escrito en JavaScript), con la siguiente muestra de código, se registra el exportador:

provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({url: collectorUrl})));
provider.register();

Configura la propagación de contexto

El sistema de seguimiento debe seguir una especificación de contexto de seguimiento que defina el formato para propagar el contexto de seguimiento entre los servicios. Los ejemplos del formato de propagación incluyen el formato B3 de Zipkin y X-Google-Cloud-Trace.

OpenTelemetry propaga el contexto mediante el TextMapPropagator global. En este ejemplo, se usa el propagador de contexto de seguimiento, que utiliza el formato de traceparent W3C. Las bibliotecas de instrumentación, como las bibliotecas HTTP y gRPC de OpenTelemetry, usan el propagador global para agregar contexto de seguimiento como metadatos a las solicitudes HTTP o gRPC. Para que la propagación de contexto se realice de forma correcta, el cliente y el servidor deben usar el mismo formato de propagación.

Propagación del contexto en HTTP

El servicio de frontend inserta un contexto de seguimiento en los encabezados de la solicitud HTTP. Los servicios de backend extraen el contexto de seguimiento. En la siguiente muestra de código, se observa cómo el servicio de frontend está instrumentado para configurar el contexto de seguimiento:

otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))

if os.Getenv("ENABLE_TRACING") == "1" {
    log.Info("Tracing enabled.")
    initTracing(log, ctx, svc)
} else {
    log.Info("Tracing disabled.")
}

...

var handler http.Handler = r
handler = &logHandler{log: log, next: handler}     // add logging
handler = ensureSessionID(handler)                 // add session ID
handler = otelhttp.NewHandler(handler, "frontend") // add OpenTelemetry tracing

Propagación de contexto mediante gRPC

Considera el flujo en el que el servicio de confirmación de la compra realiza el pedido según el producto que el usuario selecciona. Estos servicios se comunican a través de gRPC.

En la siguiente muestra de código, se usa un interceptor de llamada de gRPC que intercepta las llamadas salientes y, luego, inserta el contexto de seguimiento:

var srv *grpc.Server

// Propagate trace context always
otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))
srv = grpc.NewServer(
    grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
    grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)

Después de recibir la solicitud, el servicio de pago o catálogo de productos (ListProducts) extrae el contexto de los encabezados de la solicitud y usa los metadatos de seguimiento superior para generar un intervalo secundario.

En las siguientes secciones, se proporcionan detalles sobre cómo configurar y revisar el seguimiento distribuido para la aplicación Online Boutique de ejemplo.

Implementa la aplicación

Si ya tienes una aplicación en ejecución por haber finalizado el documento anterior de esta serie, Comunicación entre servicios en una configuración de microservicios, puedes pasar a la siguiente sección, Revisa los seguimientos. De lo contrario, completa los siguientes pasos para implementar el ejemplo de Online Boutique:

  1. Para configurar la infraestructura, clona el repositorio de GitHub en Cloud Shell:

    git clone https://github.com/GoogleCloudPlatform/microservices-demo.git
    
  2. Para la implementación nueva, restablece las variables de entorno:

    PROJECT_ID=PROJECT_ID
    REGION=us-central1
    GSA_NAME=microservices-sa
    GSA_EMAIL=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com
    

    Reemplaza lo siguiente:

    • PROJECT_ID: El identificador de tu ID del proyecto.
  3. Opcional: Crea un clúster nuevo o vuelve a usar un clúster existente si existe:

    gcloud container clusters create-auto online-boutique --project=${PROJECT_ID}
      --region=${REGION}
    
  4. Crea una cuenta de servicio de Google:

    gcloud iam service-accounts create $GSA_NAME \
      --project=$PROJECT_ID
    
  5. Habilita las API:

    gcloud services enable \
    monitoring.googleapis.com \
    cloudtrace.googleapis.com \
    cloudprofiler.googleapis.com \
      --project ${PROJECT_ID}
    
  6. Otorga las funciones necesarias para el seguimiento de nube a la GSA:

    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudtrace.agent
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/monitoring.metricWriter
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudprofiler.agent
    
    gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]"
    
  7. Anota tu cuenta de servicio de Kubernetes (default/default para el espacio de nombres predeterminado) a fin de usar la cuenta de servicio de IAM de Google:

    kubectl annotate serviceaccount default \
        iam.gke.io/gcp-service-account=${GSA_EMAIL}
    
  8. Habilita la configuración de las operaciones en la nube para GKE, lo que habilita el seguimiento:

    cd ~/microservices-demo/kustomize && \
    kustomize edit add component components/google-cloud-operations
    
  9. Esto actualizará el archivo kustomize/kustomization.yaml, que podría ser similar a lo siguiente:

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    resources:
    - base
    components:
    - components/google-cloud-operations
    [...]
    
  10. Implementa los microservicios:

    kubectl apply -k .
    
  11. Verifica el estado de la implementación:

    kubectl rollout status deployment/frontend
    kubectl rollout status deployment/paymentservice
    kubectl rollout status deployment/recommendationservice
    kubectl rollout status deployment/adservice
    

    El resultado de cada comando se verá de la manera siguiente:

    Waiting for deployment "" rollout to finish: 0 of 1 updated replicas are available...
    deployment "" successfully rolled out
    
  12. Obtén la dirección IP de la aplicación implementada:

    kubectl get service frontend-external | awk '{print $4}'
    

    Espera que se publique la dirección IP del balanceador de cargas. Para salir del comando, presiona Ctrl+C. Anota la dirección IP del balanceador de cargas y, luego, accede a la aplicación en la URL http://IP_ADDRESS. El balanceador de cargas puede tardar un tiempo en estar en buen estado y comenzar a pasar tráfico.

Revisa los seguimientos con Cloud Trace

El recorrido de compra de un usuario en la aplicación Online Boutique tiene el siguiente flujo:

  • El usuario ve un catálogo de productos en la página de destino.
  • Para realizar una compra, el usuario hace clic en Comprar.
  • Se redirecciona al usuario a la página de detalles del producto, en la que agrega el artículo a su carrito.
  • Se redirecciona al usuario a la página de confirmación de la compra, en la que puede realizar un pago para completar el pedido.

Considera una situación en la que necesitas solucionar tiempos de respuesta altos cuando cargas la página de detalles del producto. Como se describió antes, la página de detalles del producto consta de varios microservicios. Para determinar dónde y por qué se produce la latencia alta, puedes ver los grafos de seguimiento distribuidos a fin de revisar el rendimiento de toda la solicitud en los diferentes servicios.

Para revisar los grafos de seguimiento distribuido, haz lo siguiente:

  1. Accede a la aplicación y haz clic en cualquier producto. Se mostrará la página de detalles del producto.
  2. En la consola de Google Cloud, ve a la página Lista de seguimiento y revisa el cronograma.
  3. Para ver los resultados del seguimiento distribuido, haz clic en Frontend en la columna del URI.
  4. La vista de cascada de Trace muestra los intervalos asociados con el URI:

    La vista de cascada de seguimiento muestra los intervalos.

    En la captura de pantalla anterior, el seguimiento de un producto contiene los siguientes intervalos:

    • El intervalo Frontend captura la latencia de extremo a extremo (150.349 ms) que el cliente observa cuando carga la página de detalles del producto.
    • El intervalo Servicio de recomendación captura la latencia de las llamadas al backend en la recuperación de recomendaciones (4.246 ms) relacionadas con el producto.
    • El intervalo Ad Service captura la latencia de las llamadas al backend en la recuperación de anuncios (4.511 ms) que son relevantes para la página del producto.

Para solucionar tiempos de respuesta altos, puedes revisar las estadísticas centradas en el servicio que incluyan gráficos de distribución de latencia de las solicitudes de valores atípicos cuando las dependencias del servicio no cumplan con sus objetivos de nivel de servicio (SLO). También puedes usar Cloud Trace para obtener estadísticas de rendimiento y crear informes de análisis a partir de los datos de muestra.

Soluciona problemas

Si no aparecen los seguimientos en la administración del rendimiento de las aplicaciones, revisa el Explorador de registros para ver si hay un error de permiso denegado. El permiso denegado se produce cuando la cuenta de servicio no tiene acceso para exportar los seguimientos. Revisa los pasos sobre cómo otorgar roles necesarios para Cloud Trace y asegúrate de anotar la cuenta de servicio con el espacio de nombres correcto. Después de eso, reinicia opentelemetrycollector:

```
kubectl rollout restart deployment opentelemetrycollector
```

Limpia

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

Borra el proyecto

  1. En la consola de Google Cloud, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra recursos

Si deseas conservar el proyecto de Google Cloud que usaste en este instructivo, borra los recursos individuales:

  • En Cloud Shell, borra los recursos:

    gcloud container clusters delete online-boutique --project=${PROJECT_ID} --region=${REGION}
    

¿Qué sigue?