Primeras Métricas
Ahora vamos a instrumentar las métricas. Hasta el momento solo hicimos la parte de trace.
Instalamos todos esos paquetes, pero ni usamos todos aún.
npm install @opentelemetry/sdk-node \
@opentelemetry/api \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/sdk-metrics \ ###ESTE
@opentelemetry/sdk-trace-node
Si estamos hablando de métricas tenemos que tener una herramienta para guardar las métricas, entonces vamos a añadir en nuestro stack aquí el prometheus.
Prometheus es quien hace el scrape de la métrica que la aplicación muestra en el endpoint /metrics. Entonces tenemos que configurarlo apuntando cuáles son los endpoints que buscará y necesitamos pasar los endpoints de las aplicaciones.
Vamos a crear una carpeta prometheus que será mapeada al contenedor del prometheus en /etc/prometheus y colocar el archivo prometheus.yaml con la configuración de Prometheus dentro para que prometheus suba con esa configuración.
global:
# Por defecto es 1 minuto solo vamos a disminuir para que sea rápido
scrape_interval: "5s"
scrape_configs:
- job_name: 'opentelemetry'
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- todo:9464
- auth:9464
Y quedamos así en el proyecto
.
├── docker
│ ├── Dockerfile.auth
│ └── Dockerfile.todo
├── docker-compose.yml
├── package-lock.json
├── package.json
├── prometheus
│ └── prometheus.yaml
├── src
│ └── services
│ ├── auth-service.ts
│ ├── instrumentation.ts
│ └── todo-service.ts
└── tsconfig.json
5 directories, 10 files
Nuestro docker-compose quedará así.
version: '3.8'
services:
auth:
build:
context: .
dockerfile: docker/Dockerfile.auth
volumes:
- .:/usr/src/app:rw
- /usr/src/app/node_modules
ports:
- 8080:8080
# Para acceder al /metrics que vamos a crear
- 9464:9464
todo:
build:
context: .
dockerfile: docker/Dockerfile.todo
volumes:
- .:/usr/src/app:rw
- /usr/src/app/node_modules
ports:
- 8081:8080
# Para acceder al /metrics que vamos a crear
- 9465:9464
depends_on:
- redis
redis:
image: redis:alpine
ports:
- '6380:6379'
jaeger:
image: jaegertracing/all-in-one
ports:
- 4318
- 16686:16686
environment:
COLLECTOR_OTLP_ENABLED: true
prometheus:
image: prom/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yaml'
ports:
# Puerto del prometheus
- 9090:9090
volumes:
- ./prometheus/:/etc/prometheus/
Y ahora necesitamos hacer que las aplicaciones expongan el endpoint /metrics con varias métricas diferentes.
Vamos a entender un poco las cosas antes de continuar.
-
El meterProvider es una especie de hub de métricas. Las métricas dentro de él pueden ser muestreadas en varios lugares al mismo tiempo. Solo para enfatizar, vamos a exportar las métricas al mismo tiempo en el endpoint /metrics generado por prometheusExporter pero también en la consola de la aplicación. Existe también el OTLPMetricExporter para enviar métricas a otros sistemas que soportan el protocolo OTLP, como:
- OpenTelemetry Collector
- New Relic
- Datadog
- Dynatrace
- Grafana Agent
-
Las métricas del sistema operativo (CPU, memoria, etc.) necesitan un tratamiento especial. Por necesitar permisos para acceder a recursos del sistema, son recolectadas por un proceso separado (HostMetrics). Estas métricas son configuradas para usar el mismo meterProvider como destino de las métricas.
/*instrumentation.ts*/
// Importaciones principales de OpenTelemetry
import { NodeSDK } from '@opentelemetry/sdk-node'; // SDK base para Node.js
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; // Instrumentación automática
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; // Exportador de traces para Jaeger
import { metrics } from '@opentelemetry/api';
// Recursos para identificación del servicio
import { Resource } from '@opentelemetry/resources'; // Funcionará como una etiqueta
import {
ATTR_SERVICE_NAME,
} from '@opentelemetry/semantic-conventions';
// Componentes para métricas de Prometheus
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
import {
MeterProvider,
ConsoleMetricExporter,
PeriodicExportingMetricReader
} from '@opentelemetry/sdk-metrics';
import { HostMetrics} from '@opentelemetry/host-metrics';
function start(serviceName: string) {
// Define un resource con el nombre del servicio - será usado tanto para traces como métricas
const resource = new Resource({
[ATTR_SERVICE_NAME]: serviceName,
});
// Configura el exportador de Prometheus
// - Crea un endpoint /metrics en el puerto 9464
// - Este endpoint será usado por Prometheus para recolectar métricas
const prometheusExporter = new PrometheusExporter({
port: PrometheusExporter.DEFAULT_OPTIONS.port,
endpoint: PrometheusExporter.DEFAULT_OPTIONS.endpoint,
}, () => {
console.log(
`prometheus scrape endpoint: http://localhost:${PrometheusExporter.DEFAULT_OPTIONS.port}${PrometheusExporter.DEFAULT_OPTIONS.endpoint}`,
);
});
// Creamos el ConsoleMetricExporter, pero para que sea usado en meterProvider es necesario estar en un consoleReader
const consoleExporter = new ConsoleMetricExporter();
const consoleReader = new PeriodicExportingMetricReader({
exporter: consoleExporter,
exportIntervalMillis: 10000, // Mostrará en consola cada 10 segundos
});
// Configuramos el MeterProvider con ambos exporters
const meterProvider = new MeterProvider({
resource,
readers: [prometheusExporter, consoleReader]
});
const hostMetrics = new HostMetrics({
meterProvider: meterProvider,
name: `${serviceName}-host-metrics`,
});
hostMetrics.start();
// Crea un meter específico para este servicio
const meter = meterProvider.getMeter(serviceName);
// Define el MeterProvider global
metrics.setGlobalMeterProvider(meterProvider);
// Instrumentaciones de aplicación
const instrumentations = [
getNodeAutoInstrumentations({
// Cada uno de estos aún tiene sus opciones
'@opentelemetry/instrumentation-express': { },
'@opentelemetry/instrumentation-ioredis': { },
'@opentelemetry/instrumentation-http': { },
'@opentelemetry/instrumentation-dns': { },
'@opentelemetry/instrumentation-net': { },
'@opentelemetry/instrumentation-fs': { }
//
})
];
// Configura el exportador de traces para enviar a Jaeger
const traceExporter = new OTLPTraceExporter({
url: 'http://jaeger:4318/v1/traces',
});
const sdk = new NodeSDK({
resource,
traceExporter,
instrumentations
});
// Inicia el SDK - comienza a recolectar telemetría
sdk.start();
// En la salida vamos a entregar el meter y el meterProvider que será utilizado posteriormente para crear métricas personalizadas.
return { meter, meterProvider }
}
export default start
No vamos a cambiar nada aún en los servicios, eso es tarea para más tarde.
Aquí una muestra del /metrics para la aplicación TODO.

Y aquí saliendo en el terminal también para mostrar que el meterProvider está publicando en dos lugares al mismo tiempo. Obviamente que en producción no vamos a hacer esto, ni tiene sentido.

Aquí en prometheus tenemos las métricas que fueron exportadas.

Claro que vamos a instrumentar la aplicación de acuerdo con el stack de tecnología usado. Solo por curiosidad tenemos varias cosas que podríamos querer usar.
Sobre estas instrumentaciones que estamos definiendo abajo, son instrumentaciones automáticas para node. Dentro de cada bloque de estas instrumentaciones tenemos opciones extras que necesitan ser estudiadas para saber lo que queremos habilitar y deshabilitar, en nuestro caso mantenemos todo por defecto.
// Aquí
const instrumentations = [
getNodeAutoInstrumentations({
// Cada uno de estos aún tiene sus opciones
'@opentelemetry/instrumentation-express': { },
'@opentelemetry/instrumentation-ioredis': { },
'@opentelemetry/instrumentation-http': { },
'@opentelemetry/instrumentation-dns': { },
'@opentelemetry/instrumentation-net': { },
'@opentelemetry/instrumentation-fs': { }
//
})
];
// ...
const sdk = new NodeSDK({
resource,
traceExporter,
instrumentations // E inyectando aquí
});
Si una métrica no es útil para ti, no la uses.
Muchas veces es más interesante crear nuestras propias métricas y eso es lo que vamos a hacer, pero antes vamos a entender sobre los tipos de métricas.
El código se encuentra en la rama otel_com_metric del proyecto.
Si quieres entender un poco más anticipadamente consulta en Configurando el OTel SDK.