En esta práctica se monta una arquitectura profesional de observabilidad que permite monitorizar sistemas en tiempo real. Se construye un stack completo con tres componentes clave:

  • OpenTelemetry Collector: recolector universal de métricas desde múltiples fuentes (aplicaciones, sistemas operativos, contenedores).
  • Prometheus: motor de almacenamiento eficiente en base de datos de series temporales (TSDB) y consulta de datos mediante lenguaje PromQL.
  • Grafana: interfaz de visualización con dashboards interactivos y alertas configurables.

Este tipo de infraestructura es utilizada por empresas como Google (Borgmon), Netflix, Amazon (CloudWatch), Spotify, Uber y Airbnb para monitorizar sus sistemas de producción a escala global.

Estructura del proyecto

Organizar el proyecto en una estructura clara:

1mkdir -p ~/herramientas
2cd ~/herramientas
3
4mkdir -p grafana/datasources
5mkdir -p grafana/dashboards
6mkdir -p opentelemetry
7mkdir -p prometheus

Configuración de servicios

Archivo docker-compose.yaml

Creamos el archivo principal de orquestación:

1nano docker-compose.yaml
 1services:
 2  prometheus:
 3    image: prom/prometheus:latest
 4    container_name: prometheus
 5    ports:
 6      - "9090:9090"
 7    volumes:
 8      - ./prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml
 9      - prometheus-data:/prometheus
10    command:
11      - '--config.file=/etc/prometheus/prometheus.yml'
12      - '--storage.tsdb.path=/prometheus'
13      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
14      - '--web.console.templates=/usr/share/prometheus/consoles'
15    networks:
16      - monitoring
17    restart: unless-stopped
18
19  otel-collector:
20    image: otel/opentelemetry-collector-contrib:latest
21    container_name: otel-collector
22    ports:
23      - "4317:4317"
24      - "4318:4318"
25      - "8888:8888"
26      - "8889:8889"
27    volumes:
28      - ./opentelemetry/opentelemetry.yml:/etc/otel/config.yaml
29    command: ["--config=/etc/otel/config.yaml"]
30    networks:
31      - monitoring
32    restart: unless-stopped
33
34  grafana:
35    image: grafana/grafana:latest
36    container_name: grafana
37    ports:
38      - "3000:3000"
39    volumes:
40      - grafana-data:/var/lib/grafana
41      - ./grafana:/etc/grafana/provisioning
42    environment:
43      - GF_SECURITY_ADMIN_USER=admin
44      - GF_SECURITY_ADMIN_PASSWORD=********
45      - GF_USERS_ALLOW_SIGN_UP=false
46    networks:
47      - monitoring
48    restart: unless-stopped
49    depends_on:
50      - prometheus
51
52  app-demo:
53    image: nginx:alpine
54    container_name: app-demo
55    ports:
56      - "8080:80"
57    networks:
58      - monitoring
59    restart: unless-stopped
60
61volumes:
62  prometheus-data:
63  grafana-data:
64
65networks:
66  monitoring:
67    driver: bridge

Explicación del yaml:

Servicio prometheus:

  • image: Imagen oficial de Prometheus
  • ports: Expone la interfaz web en el puerto 9090
  • volumes: configuración (./prometheus/prometheus.yaml/etc/prometheus/prometheus.yml) y datos persistentes (prometheus-data/prometheus)
  • command: parámetros de inicio — --config.file (ubicación del archivo de configuración) y --storage.tsdb.path (directorio de almacenamiento TSDB)
  • networks: conecta a la red interna monitoring
  • restart: reinicia automáticamente salvo parada manual

Servicio otel-collector:

  • ports: 4317 (protocolo OTLP sobre gRPC), 4318 (OTLP sobre HTTP), 8888 (métricas internas del collector), 8889 (endpoint para scraping de Prometheus)
  • volumes: monta la configuración del collector

Servicio grafana:

  • ports: interfaz web en el puerto 3000
  • environment: GF_SECURITY_ADMIN_USER (usuario administrador), GF_SECURITY_ADMIN_PASSWORD (contraseña inicial), GF_USERS_ALLOW_SIGN_UP (deshabilita el registro público)
  • depends_on: indica dependencia de Prometheus

Servicio app-demo: Nginx Alpine, servidor web ligero para generar tráfico de prueba.

Volúmenes: prometheus-data (persistencia de métricas históricas) y grafana-data (persistencia de configuración y dashboards).

Redes: monitoring, red bridge interna para comunicación entre contenedores.

Configuración de Prometheus

Creamos el archivo de configuración:

1nano prometheus/prometheus.yaml
 1global:
 2  scrape_interval: 15s
 3  evaluation_interval: 15s
 4
 5  external_labels:
 6    monitor: 'monitoring-stack'
 7    environment: 'development'
 8
 9scrape_configs:
10  - job_name: 'prometheus'
11    static_configs:
12      - targets: ['localhost:9090']
13        labels:
14          service: 'prometheus'
15
16  - job_name: 'otel-collector'
17    static_configs:
18      - targets: ['otel-collector:8888']
19        labels:
20          service: 'otel-collector'
21
22  - job_name: 'otel-metrics'
23    static_configs:
24      - targets: ['otel-collector:8889']
25        labels:
26          service: 'otel-exported-metrics'

Explicación de la configuración:

  • global.scrape_interval: cada cuánto Prometheus recoge métricas de los targets (15s)
  • global.evaluation_interval: cada cuánto evalúa las reglas de alertas (15s)
  • global.external_labels: etiquetas que se añaden a todas las métricas (útil para identificar el origen)
  • scrape_configs: lista de fuentes de métricas (jobs), cada uno con job_name (identificador único), static_configs (configuración estática, sin descubrimiento dinámico), targets (endpoints donde hará scraping) y labels (etiquetas adicionales)

Cómo funciona el scraping:

  1. Prometheus hace peticiones HTTP GET a cada target cada 15 segundos
  2. Los targets exponen métricas en formato texto plano
  3. Prometheus parsea y almacena las métricas en su base de datos de series temporales (TSDB)

Configuración de OpenTelemetry Collector

Creamos el archivo de configuración:

1nano opentelemetry/opentelemetry.yml
 1receivers:
 2  otlp:
 3    protocols:
 4      grpc:
 5        endpoint: 0.0.0.0:4317
 6      http:
 7        endpoint: 0.0.0.0:4318
 8
 9  prometheus:
10    config:
11      scrape_configs:
12        - job_name: 'app-demo-nginx'
13          scrape_interval: 10s
14          static_configs:
15            - targets: ['app-demo:80']
16
17  hostmetrics:
18    collection_interval: 30s
19    scrapers:
20      cpu:
21      memory:
22      disk:
23      network:
24      load:
25      filesystem:
26
27processors:
28  batch:
29    timeout: 10s
30    send_batch_size: 1024
31
32  resource:
33    attributes:
34      - key: service.instance.id
35        value: otel-collector-01
36        action: insert
37      - key: deployment.environment
38        value: development
39        action: insert
40
41exporters:
42  prometheus:
43    endpoint: "0.0.0.0:8889"
44    namespace: "otel"
45    const_labels:
46      collector: "otel-collector"
47
48  debug:
49    verbosity: detailed
50
51  prometheusremotewrite:
52    endpoint: "http://prometheus:9090/api/v1/write"
53    tls:
54      insecure: true
55
56service:
57  pipelines:
58    metrics:
59      receivers: [otlp, prometheus, hostmetrics]
60      processors: [batch, resource]
61      exporters: [prometheus, debug, prometheusremotewrite]
62
63  telemetry:
64    logs:
65      level: info

Flujo de datos:

  1. Las métricas entran por cualquiera de los 3 receivers (otlp, prometheus, hostmetrics)
  2. Pasan por el procesador batch
  3. Pasan por el procesador resource
  4. Se exportan simultáneamente a los 3 exportadores

Configuración de Grafana

Creamos la configuración de la fuente de datos:

1nano grafana/datasources/prometheus.yml
 1apiVersion: 1
 2
 3datasources:
 4  - name: Prometheus
 5    type: prometheus
 6    access: proxy
 7    url: http://prometheus:9090
 8    isDefault: true
 9    editable: true
10    jsonData:
11      timeInterval: "15s"
12      httpMethod: POST

Explicación:

  • apiVersion: 1: versión del formato de provisión
  • name: Prometheus: nombre de la fuente de datos en Grafana
  • type: prometheus: tipo de fuente de datos
  • access: proxy: Grafana hace las peticiones (no el navegador)
  • url: URL interna de Prometheus (nombre del servicio Docker)
  • isDefault: true: fuente de datos por defecto
  • editable: true: permite editar desde la UI
  • timeInterval: "15s": intervalo mínimo entre queries (coincide con el scrape_interval)
  • httpMethod: POST: usar POST para queries largas (más eficiente)

Configuración de provisión de dashboards

Creamos la configuración de provisión:

1nano grafana/dashboards/dashboard.yml
 1apiVersion: 1
 2
 3providers:
 4  - name: 'Default'
 5    orgId: 1
 6    folder: ''
 7    type: file
 8    disableDeletion: false
 9    updateIntervalSeconds: 10
10    allowUiUpdates: true
11    options:
12      path: /etc/grafana/provisioning/dashboards

Explicación:

  • providers: lista de proveedores de dashboards
  • name: 'Default': nombre del proveedor
  • orgId: 1: organización de Grafana
  • folder: '': carpeta donde aparecerán los dashboards
  • type: file: los dashboards se cargan desde archivos
  • disableDeletion: false: permite borrar dashboards desde la UI
  • updateIntervalSeconds: 10: cada cuánto busca nuevos dashboards
  • allowUiUpdates: true: permite editar dashboards desde la UI
  • path: ruta donde busca los archivos JSON de dashboards

Dashboard de sistema

Creamos el dashboard básico:

1nano grafana/system.json

El dashboard define 4 paneles sobre la fuente de datos de Prometheus, cada uno con su propia query PromQL, umbrales de color y tipo de visualización:

  1. CPU Usage (%) — gráfico de series temporales

    • Query: 100 - (avg by (instance) (rate(otel_system_cpu_time_seconds_total{state="idle"}[1m])) * 100)
    • Cálculo: 100% − tiempo en idle = tiempo activo de CPU
    • Umbrales: verde (<70%), amarillo (70-85%), rojo (>85%)
  2. Memory Usage (%) — gauge (medidor)

    • Query: (otel_system_memory_usage_bytes{state="used"} / otel_system_memory_usage_bytes) * 100
    • Muestra el porcentaje de memoria usada
    • Umbrales: verde (<70%), amarillo (70-90%), rojo (>90%)
  3. Network I/O — gráfico de series temporales

    • Query RX: rate(otel_system_network_io_bytes_total{direction="receive"}[1m])
    • Query TX: rate(otel_system_network_io_bytes_total{direction="transmit"}[1m])
    • Muestra bytes/segundo recibidos y transmitidos
  4. Disk Usage (%) — bar gauge (barras horizontales)

    • Query: (otel_system_filesystem_usage_bytes{state="used"} / otel_system_filesystem_usage_bytes) * 100
    • Muestra el uso de disco por dispositivo/punto de montaje

El JSON completo del dashboard (paneles, fieldConfig, umbrales y queries) se guarda tal cual en grafana/system.json para que Grafana lo importe en el siguiente paso.

Despliegue del stack

Antes de desplegar, verificamos que todos los archivos estén en su sitio:

1cd ~/herramientas
2tree

Estructura de directorios del proyecto verificada con tree: docker-compose.yaml, grafana, opentelemetry y prometheus

Levantar servicios

Vamos a iniciar todos los contenedores:

1docker compose up -d

Para ver que se han iniciado correctamente:

1docker compose ps

Salida de docker compose ps con los cuatro contenedores: app-demo, grafana, otel-collector y prometheus

Uso del stack de monitorización

Vamos a acceder a Grafana, poniendo la IP donde tenemos alojado nuestro servidor y el puerto 3000:

Pantalla de login de Grafana en el navegador

Y ahora nos aparecerá un panel lateral a la izquierda; vamos a darle en ese panel a Dashboards → New:

Panel de Dashboards de Grafana con el botón New señalado

E importamos nuestro fichero .json que creamos anteriormente; una vez importado, nos aparecerá en el panel:

Dashboard System Monitoring Overview ya listado en el panel de Dashboards de Grafana

Entonces vemos cómo monitorizamos nuestro servidor: la CPU, la RAM, el disco y la red.

Dashboard System Monitoring Overview con los cuatro paneles en vivo: CPU Usage, Memory Usage, Network I/O y Disk Usage

Como vemos, OpenTelemetry recoge las métricas de tu servidor, Prometheus las guarda y Grafana las muestra en gráficos.