Hemos visto a lo largo del curso de Distribt que vamos a crear microservicios para realizar diferentes acciones dentro de un sistema mas grande.
En el post sobre el patrón SAGA, vimos como una transacción sencilla en el mundo monolítico se convertía en una muy compleja dentro de los microservicios. Y mencioné que es muy importante tener una idea clara de todo lo que ha pasado, y cuando ha pasado en nuestro sistema.
Índice
1 - Observabilidad de nuestro sistema distribuido
Como acabo de mencionar es importante saber cuándo y cómo un evento en nuestro sistema ha sucedido, para ello tenemos 3 formas de hacerlo.
- Logs: los cuales van a decir el error concreto, o el evento en concreto que estamos indicando, vimos un post sobre ellos hace un tiempo.
- Métricas: Las cuales nos dicen las estadísticas puras sobre el sistema; Por ejemplo una métrica es el tiempo de carga de determinada página en nuestra web o cuántas veces se ha cargado
- Traces: El contexto de porque las cosas pasan. El curso de una acción a lo largo de nuestro sistema, de principio a fin. Si ponemos como ejemplo el caso de uso de la saga de creación de un pedido, cada evento tendrá una propiedad llamada “traceId” y en caso de tener un error únicamente debemos buscar por el ID en nuestro sistema de monitorización para encontrar todos los eventos relacionados.
2 - Qué es OpenTelemetry?
Cuando tenemos un sistema distribuido tenemos múltiples aplicaciones y estas aplicaciones pueden estar escritas todas en el mismo lenguaje de programación o en lenguajes diferentes.
A la hora de crear sistemas es crucial tener métricas de datos para así ser capaces de detectar antes que el usuario cuando el sistema va lento o cuando algo está fallando.
Antes de la llegada de OpenTelemetry cada lenguaje generaba las métricas de una manera, y cada servicio que utilizas las quería recibir de otra forma diferente. Por ejemplo New Relic quería recibir los datos en el formato A y Prometheus los quiere en el Formato B, etc.
Esto causaba varios problemas, tener que implementar lo mismo varias veces, o la imposibilidad de cambiar de un proveedor a otro ya que bueno, tenías que escribir todo desde 0.
Aquí es donde entra OpenTelemetry, estandariza la forma en la que las aplicaciones envían las métricas, para que así, esté en el lenguaje que esté tu aplicación escrita la información de las métricas será generada de la misma forma.
Como puedes ver en la imagen, disponemos de un elemento que se llama OpenTelemetry collector, que es básicamente el servicio que va a recoger esos datos, procesarlos y mostrarlos, ya que OpenTelemetry no es una aplicación back end, sino un estándar que provee APIs y SDKs en los diferentes lenguajes así como la forma de exportar la información.
Por cierto, no está en la imagen, pero una máquina virtual, la pipeline de CI/CD, otros servicios de tu infraestructura, etc también pueden generar métricas.
2.1 - OpenTelemetry Collector
Lo que llamamos Opentelemetry Collector es una aplicación o servicio que vamos a desplegar junto a nuestras aplicaciones, para que, dicha aplicación envíe los datos de las métricas a ese servicio.
Tenemos dos formas de desplegar dicho collector
- Agente: cuando despliegas la aplicación debes correr otro servicio junto a ella, en el mismo host. Esta acción se hace normalmente con un sidecar si usas k8s o un daemonset, etc.
- Puerta de entrada: el colector se despliega como un servicio más normal y corriente.
Puedes encontrar más información en la web oficial.
- Nota: en nuestro ejemplo lo veremos desplegado en docker como un servicio.
3 - Visualizar métricas
Hasta ahora, únicamente estamos exportando la información pero no estamos haciendo nada con ella, debemos procesarla y almacenarla para posteriormente visualizarla, aquí es donde entran Prometheus, Grafana y Zipkin; Aunque la realidad es que puedes utilizar los servicios que quieras o tengas disponibles en tu proveedor de servicios si estas en la nube, aún así, la idea es la misma.
3.1 - Qué es Prometheus?
Lo primero que necesitamos es un backend para almacenar la información, que procese dichos datos y los almacene, por lo tanto que actúe de base de datos.
Aquí es donde entra Prometheus, una base de datos que hace de almacenamiento para nuestros sistemas de monitorización y métricas.
Y su interfaz no es nada del otro mundo la verdad, algo feucha:
Nota: Podemos enviar los datos directamente desde la aplicación a prometheus sin necesidad de pasar por OpenTelemetry, como siempre, dependerá de la infraestructura que quieres implementar.
3.2 - Qué es Grafana?
Una vez tenemos la información almacenada y procesada, vamos a querer poder verla, pero no verla de forma plana, sino con gráficos y tablas chulas. Ahi entra Grafana, es la interfaz de usuario que nos va a permitir ver las métricas.
Como puedes observar, yo soy un desastre haciendo gráficas, pero bueno, los datos están ahí para crear unos gráficos chulos.
3.3 - Que es Zipkin?
Igual que Grafana nos da gráficos y diagramas sobre las métricas de nuestro sistema o de nuestra aplicación, lo que hace Zipkin es permitirnos ver la trazabilidad de las llamadas, donde va cada llamada y en qué punto tarda más o menos tiempo.
NOTA: En este post no voy a mostrar como añadir traces personalizados o métricas personalizadas, ya que esta fuera del scope del post y sería muy largo. Estas acciones las mostraré en el futuro en post independientes.
4 - Necesitamos OpenTelemetry?
Ahora que ya he explicado que Open Telemetry es un estándar y que los que de verdad hacen el trabajo de almacenar, procesar y mostrar los datos son Prometheus y Grafana (u otros servicios), la pregunta es clara.
¿Debemos implementar Open Telemetry en nuestros servicios?
Pues depende un poco, como todo, siempre tenemos pros y contras.
Un pro es que si cambiamos de proveedor, por ejemplo migramos de prometheus y grafana a New Relic no vamos a tener que cambiar el código.
Pero a la vez, New Relic entiende el output de Prometheus. Esto quiere decir que si exportas a prometheus y cambias a new relic, no tienes que cambiar nada tampoco.
- Nota: no se lo que pasa en otros servicios, estos son los que tengo experiencia.
Una contra es que para que OpenTelemetry funcione, debemos tener nuestro servicio/sidecar configurado lo que conlleva trabajo adicional, tanto en el mantenimiento del servicio como en los recursos del sistema que tenemos que asignar.
Así que tendremos dos opciones, ir por la vía “rápida” donde enviamos los logs desde la aplicación al sistema backend (Prometheus), o pasamos por un intermediario para normalizar los datos (OpenTelemetry)?
Como siempre radicará en los recursos que queramos invertir.
5 - Configurar OpenTelemetry en .NET
Para este ejemplo lo voy a hacer con OpenTelemetry, lo que quiere decir que nuestras aplicaciones van a utilizar el opentelemetry collector y Prometheus se conectará a él para leer y procesar dicha información.
5.1 - Creación de la infraestructura para la observabilidad
Lo primero que haremos será crear toda la configuración que necesitamos en nuestro sistema, para ello vamos a nuestro fichero de docker compose y añadimos los contenedores de OpenTelemetry, Prometheus, Grafana y Zipkin
opentelemetry-collector:
image: otel/opentelemetry-collector:latest
container_name: open_telemetry_collector
command: [ "--config=/etc/otel-collector-config.yaml" ]
volumes:
- ./tools/telemetry/otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./tools/telemetry/logs:/etc/output:rw # Store the logs (not commited in git)
ports:
- "8888:8888" # Prometheus metrics exposed by the collector
- "8889:8889" # Prometheus exporter metrics
- "4317:4317" # OTLP gRPC receiver
prometheus:
image: bitnami/prometheus
container_name: prometheus
volumes:
- ./tools/telemetry/prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
- 9090:9090
grafana:
image: grafana/grafana
container_name: grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- ./tools/telemetry/grafana_datasources.yaml:/etc/grafana/provisioning/datasources/all.yaml
ports:
- 3000:3000
zipkin:
container_name: zipkin-traces
image: openzipkin/zipkin:latest
ports:
- "9411:9411"
Como puedes observar, dentro del contenedor de OpenTelemetry estamos utilizando un fichero de configuración llmado otel-collector-config.yaml
, el cual contiene la configuración que incluye protocolos disponibles y donde la información recolectada va a ser exportada:
# https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver
receivers:
otlp:
protocols:
grpc:
# Configure exporters
exporters:
# Export prometheus endpoint
prometheus:
endpoint: "0.0.0.0:8889"
# log to the console
logging:
# Export to zipkin
zipkin:
endpoint: "http://zipkin:9411/api/v2/spans"
format: proto
# Export to a file
file:
path: /etc/output/logs.json
# https://opentelemetry.io/docs/collector/configuration/#processors
processors:
batch:
# https://opentelemetry.io/docs/collector/configuration/#service
# https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/design.md#pipelines
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [logging, zipkin]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [logging, prometheus]
logs:
receivers: [otlp]
processors: []
exporters: [logging, file]
Este es el de prometheus (prometheus.yaml
), como vemos le estamos indicando que consulte el contenedor de opentelemetry para recibir los datos
scrape_configs:
- job_name: 'collect-metrics'
scrape_interval: 10s
static_configs:
- targets: ['opentelemetry-collector:8889']
- targets: ['opentelemetry-collector:8888']
Y finalmente el de grafana donde indicamos Prometheus como una fuente de datos y donde está ubicada:
datasources:
- name: 'prometheus'
type: 'prometheus'
access: 'proxy'
url: 'http://prometheus:9090'
- Nota: el usuario y la contraseña para conectarnos a grafana es
admin:admin
Y ahora ya podemos hacer docker-compose up
para ejecutar nuestra infraestructura --.
5.2 - Implementar Opentelemetry en código C#
Lo primero que tenemos que hacer es añadir los siguientes paquetes:
- OpenTelemetry.Exporter.OpenTelemetryProtocol
- OpenTelemetry.Extensions.Hosting (prerelease)
- OpenTelemetry.Instrumentation.AspNetCore (prerelease)
Si en vez del paquete Exporter.OpenTelemetryProtocol
instalamos el paquete Exporter.Prometheus
podemos enviar la información directamente a prometheus sin pasar por el collector.
- Nota: Si estás en el proyecto de Distribt, este paquete está añadido dentro del proyecto
Distribt.Shared.Setup
, lo que quiere decir que todas nuestras aplicaciones van a tener opentelemetry implementado por defecto.
Con estos paquetes podemos incluir tanto tracing, como métricas como logs.
5.2.1 - Añadir Tracing a una aplicación .NET con OpenTelemetry
Únicamente debemos utilizar el método AddOpenTelemetryTracing()
y pasar en el builder la configuración necesaria.
public static void AddTracing(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddOpenTelemetryTracing(builder => builder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(configuration["AppName"]))
.AddAspNetCoreInstrumentation()
.AddOtlpExporter(exporter =>
{
//TODO: call the discovery service to retrieve the correctUrl dinamically
exporter.Endpoint = new Uri("http://localhost:4317");
})
);;
}
Como puedes ver estamos mandando un nombre para el servicio, el cual lo vamos a tener configurado en los ficheros de appsettings.json
de cada una de nuestras aplicaciones.
5.2.2 - Añadir Metrics a una aplicación .NET con OpenTelemetry
Similar al caso anterior, debemos utilizar el método .AddOpenTelemetryMetrics()
:
public static void AddMetrics(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddOpenTelemetryMetrics(builder => builder
// Configure the resource attribute `service.name` to MyServiceName
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyServiceName"))
// Add metrics from the AspNetCore instrumentation library
.AddAspNetCoreInstrumentation()
.AddOtlpExporter(exporter =>
{
//TODO: call the discovery service to retrieve the correctUrl dinamically
exporter.Endpoint = new Uri("http://localhost:4317");
}));
}
5.2.3 - Añadir Logs a una aplicación .NET con OpenTelemetry
Similar al caso anterior, debemos utilizar el método .ConfigureLogging()
:
public static void AddLogging(this IHostBuilder builder, IConfiguration configuration)
{
builder.ConfigureLogging(logging => logging
//Next line optional to remove other providers
.ClearProviders()
.AddOpenTelemetry(options =>
{
options.IncludeFormattedMessage = true;
options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(configuration["AppName"]));
options.AddConsoleExporter();
}));
}
En el caso de la librería de Distribt este es más opcional ya que vimos como configurar la aplicación para utilizar graylog.
Y ya está, si ejecutamos las aplicaciones podemos ver el resultado. Las imágenes que has visto en este post son sacadas del propio resultado.
6 - Añadir observabilidad a otras partes de nuestra infraestructura
Podemos añadir observabilidad a muchas partes de la infraestructura, o incluso a todas, si utilizamos servicios en la nube estos ya vienen preparados para tener observabilidad, traces, etc, out of the box, osea que no hay que configurar nada, simplemente vienen disponibles por defecto.
6.1 - Conectar RabbitMQ con PromeTheus y Grafana
En otro post sobre el curso de sistemas distribuidos, vimos que es un service bus, en concreto RabbitMQ, ahora vamos a ver como añadir informacion a Prometheus/Grafana.
Lo primero que tenemos que hacer es, en nuestra infraestructura, modificar el servicio dentro del fichero docker-compose
para indicar que pasaremos un fichero llamado enabled_plugins
a través de los volumenes:
rabbitmq:
image: rabbitmq:3.8.34-management-alpine #management version needed to be able to have a User interface
container_name: rabbitmq
ports:
- 5672:5672
- 15672:15672
volumes:
- ./tools/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
- ./tools/rabbitmq/definitions.json:/etc/rabbitmq/definitions.json
- ./tools/rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins
Este fichero contiene una lista con los plugins que vamos a activar en RabbitMQ, en este caso, el que nos interesa es rabbitmq_prometheus
pero yo ento habilitado alguno mas.
[rabbitmq_prometheus, rabbitmq_amqp1_0, rabbitmq_management, rabbitmq_web_dispatch, rabbitmq_management_agent, rabbitmq_stomp].
Y finalmente modificar nuestro fichero de prometheus.yaml para añadir el nuevo target para recoger la información.
scrape_configs:
- job_name: 'collect-metrics'
scrape_interval: 10s
static_configs:
- targets: ['opentelemetry-collector:8889']
- targets: ['opentelemetry-collector:8888']
- targets: [ 'rabbitmq:15692' ]
Y ahora podemos ejecutar docker-compose up -d
.
Una vez lo tenemos corriendo, tenemos que hacer algún paso manual, recuerda, esto lo harás en producción una única vez.
Debemos importar el dashboard de la página oficial de Grafana. Esto se debe a que los propios de Grafana tienen una gran comunidad que comparten información.
Cuando lo importes, estate seguro de que has cambiado el datasource a Prometheus, ya que es el que tiene dicha información disponible.
Y este es el resultado final, como podemos ver en la esquina superior nos sale el número de colas que tenemos disponibles en RabbitMQ.
Y si ejecutamos las propias aplicaciones y generamos unos cuantos eventos, podemos ver como el resto de gráficas también cambian:
Conclusión
En este post hemos visto que es opentelemetry y como entra dentro de la observabilidad.
Cómo utilizar OpenTelemetry con .NET y Prometheus.
Cómo utilizar Prometheus con Grafana.
Cómo utilizar Prometheus con Zipkin.
Cómo añadir observabilidad a otras partes de nuestra infraestructura.