14

Monitorizando Traefik

Vistas: 0
Monitorizando Traefik

Tienes Traefik funcionando, con múltiples servicios, certificados SSL, middlewares y seguridad. Pero, ¿cómo saber si todo funciona correctamente? ¿Cómo detectar problemas antes de que los usuarios se quejen?

Para eso está el monitoring. Traefik v3 ofrece varias formas de monitorizar tu infraestructura: métricas con Prometheus, trazabilidad con OpenTelemetry, y logs de acceso detallados. Y te recomiendo que no te saltes este capítulo, porque una vez que despliegues en producción y tengas usuarios de verdad, el monitoring no es un lujo — es una necesidad. Te lo digo por experiencia: el día que algo falla a las 3 de la mañana, te alegrarás de tener dashboards y alertas configuradas.

Logs de acceso

Lo más básico que puedes hacer es activar los logs de acceso. Te dan información detallada de cada petición que pasa por Traefik. Sin ellos, estás volando a ciegas.

En tu traefik.yml,

accessLog:
  filePath: /var/log/traefik/access.log
  format: json
  bufferingSize: 100
  fields:
    defaultMode: keep
    names:
      ClientHost: keep
      ClientPort: keep
      RequestHost: keep
      RequestPath: keep
      RequestMethod: keep
      RequestProtocol: keep
      ResponseStatus: keep
      ResponseSize: keep
      Duration: keep
      OriginStatus: drop
    headers:
      defaultMode: drop
      names:
        User-Agent: keep
        Referer: keep
  • format: json: el log en JSON es más fácil de procesar con herramientas como Elasticsearch o Loki. En formato texto es legible para un humano, pero un ordenador lo parsea mucho mejor en JSON.
  • bufferingSize: buffer de escritura para no saturar el disco. Si tienes mucho tráfico, esto te puede ahorrar más de un disgusto con el I/O del servidor.
  • fields: controlas qué campos incluir. Puedes ocultar información sensible como cabeceras de autorización o direcciones IP internas. Esto es importante si manejas datos personales y te entra la GDPR.

¿Y qué haces con estos logs? Pues puedes enviarlos a un sistema centralizado como ELK, Loki o Graylog y tener dashboards de todo el tráfico. Te recomiendo que montes Promtail + Loki + Grafana, porque es el stack más ligero y se integra de puta madre con todo lo que ya tienes.

Filtrando logs por status code

A veces no quieres guardar TODO, solo lo que te interesa. Puedes filtrar:

accessLog:
  filters:
    statusCodes:
      - "200-299"
      - "400-499"
      - "500-599"
    retryAttempts: true
    minDuration: "100ms"

Con statusCodes decides qué rangos registrar. Con minDuration solo guardas peticiones que tardan más de X milisegundos — ideal para pillar cuellos de botella sin rellenar el disco con peticiones rápidas e irrelevantes.

Formato personalizado

Si JSON no te va, puedes usar un formato personalizado con variables:

accessLog:
  format: common
  # O formato personalizado:
  # format: "$request_protocol - $request_method $request_path -> $response_status en $duration ms"

Las variables disponibles incluyen $client_host, $request_host, $request_method, $request_path, $response_status, $duration, $request_protocol, $response_size, $backend_url, y muchas más. Dale caña y personalízalo a tu gusto.

Rotación de logs

Ojo, que los logs de acceso pueden crecer sin control. Traefik no hace rotación por sí mismo, así que tienes que gestionarlo externamente:

accessLog:
  filePath: /var/log/traefik/access.log
  format: json
  bufferingSize: 100
  rotation:
    maxSize: 100
    maxBackups: 5
    maxAge: 7
    localTime: true
    compress: true

O si usas Docker, puedes configurar log rotation a nivel de contenedor:

services:
  traefik:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        compress: "true"

Esto te puede ahorrar más de un disgusto cuando tu disco se llena a las 3 de la madrugada.

Métricas con Prometheus

Prometheus es el estándar de facto para métricas en el ecosistema Cloud Native. Y Traefik lo soporta de forma nativa, sin necesidad de exporters adicionales ni historias raras.

Activar Prometheus en Traefik

En tu traefik.yml,

metrics:
  prometheus:
    addEntryPointsLabels: true
    addServicesLabels: true
    manualRouting: true
    entryPoint: metrics
    buckets:
      - 0.01
      - 0.025
      - 0.05
      - 0.1
      - 0.25
      - 0.5
      - 1.0
      - 2.5
      - 5.0
      - 10.0

Fíjate en buckets: son los intervalos de tiempo que usa Prometheus para el histograma de latencia. Por defecto Traefik usa los buckets estándar de Prometheus, pero te recomiendo que los ajustes según tu caso. Si tu API responde en milisegundos, tener un bucket de 10 segundos no te sirve de nada. Si sirves ficheros grandes, igual sí.

Y creas un entrypoint para las métricas:

entryPoints:
  metrics:
    address: ":8082"

Pero ojo: este entrypoint no debe ser accesible desde internet. Lo configuras para que solo escuche en localhost o en tu red interna:

entryPoints:
  metrics:
    address: "127.0.0.1:8082"

Si usas Docker, otra opción es no mapear el puerto y acceder por la red interna de Docker:

services:
  traefik:
    ports:
      - "80:80"
      - "443:443"
    # No mapees el 8082, solo expónlo en la red interna

Prometheus se conecta usando el nombre del servicio de Docker y el puerto interno.

Todas las métricas que obtienes

Una vez activado, Traefik expone métricas en http://localhost:8082/metrics. Vale la pena que conozcas todas, no solo las típicas. Aquí tienes la lista completa:

Métricas de peticiones:

  • traefik_requests_total — número total de peticiones. Con labels code, method, protocol, service, entrypoint.
  • traefik_request_duration_seconds — histograma de tiempos de respuesta. El santo grial para medir rendimiento.
  • traefik_request_duration_seconds_bucket — los buckets individuales del histograma, con el conteo de requests en cada rango.
  • traefik_request_duration_seconds_sum — suma total de tiempo de todas las peticiones.
  • traefik_request_duration_seconds_count — número total de peticiones medidas.
  • traefik_requests_bytes_total — volumen total de datos transferidos en requests (body).
  • traefik_responses_bytes_total — volumen total de datos transferidos en responses.
  • traefik_backend_requests_total — peticiones por servicio backend.
  • traefik_backend_request_duration_seconds — histograma de latencia por backend.
  • traefik_backend_responses_bytes_total — bytes de respuesta por backend.
  • traefik_backend_response_status — códigos de respuesta por servicio.
  • traefik_entrypoint_requests_total — peticiones por entrypoint.
  • traefik_entrypoint_request_duration_seconds — latencia por entrypoint.
  • traefik_entrypoint_requests_bytes_total — bytes por entrypoint.
  • traefik_entrypoint_responses_bytes_total — bytes de respuesta por entrypoint.

Métricas de conexiones:

  • traefik_open_connections — conexiones activas actualmente. Con label method. Si este número se dispara, tienes un problema.
  • traefik_tcp_connections_total — conexiones TCP totales (para entrypoints TCP).
  • traefik_tcp_open_connections — conexiones TCP abiertas.
  • traefik_tcp_write_bytes_total — bytes escritos en conexiones TCP.
  • traefik_tcp_read_bytes_total — bytes leídos en conexiones TCP.

Métricas de configuración:

  • traefik_config_reloads_total — recargas de configuración. Si ves muchas, algo está cambiando constantemente.
  • traefik_config_reloads_failures_total — recargas fallidas. Si esto sube, tienes un error en la configuración.
  • traefik_config_last_reload_success — 1 si la última recarga fue exitosa, 0 si falló. Una métrica golang típica que es oro puro para alertas.
  • traefik_config_last_reload_failure — timestamp de la última recarga fallida.

Métricas de TLS y certificados:

  • traefik_tls_certificates_not_after — timestamp de expiración de cada certificado. Con label cn (common name).
  • traefik_tls_certificates_not_before — timestamp de inicio de validez.
  • traefik_tls_certificates_secrets_names — nombres de los secretos o rutas de los certificados.
  • traefik_tls_certificate_validation_failures_total — fallos de validación de certificados.

Métricas internas de Traefik:

  • traefik_health_info — información general de salud del router.
  • traefik_build_info — versión de Traefik que estás ejecutando. Con labels version, goversion, goarch.

Con todas estas métricas puedes construir dashboards realmente completos. ¿Ves por qué te digo que no es solo poner Prometheus y ya?

Configuración avanzada de Prometheus

Vale, ya tienes el scrape_configs básico. Pero si quieres hacerlo bien, te recomiendo una configuración más completa:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  scrape_timeout: 10s

scrape_configs:
  - job_name: 'traefik'
    scrape_interval: 10s
    scrape_timeout: 5s
    metrics_path: /metrics
    scheme: http
    static_configs:
      - targets: ['traefik:8082']
        labels:
          service: 'traefik'
          env: 'production'

  - job_name: 'traefik-ping'
    scrape_interval: 30s
    metrics_path: /ping
    scheme: http
    static_configs:
      - targets: ['traefik:8082']

  - job_name: 'node'
    scrape_interval: 15s
    static_configs:
      - targets: ['node-exporter:9100']
        labels:
          host: 'docker-host'
          env: 'production'

  - job_name: 'docker'
    scrape_interval: 15s
    static_configs:
      - targets: ['cadvisor:8080']
        labels:
          env: 'production'

Fíjate en los detalles:

  • scrape_interval más frecuente para Traefik (10s) que para el resto (15s). Las métricas de tráfico cambian rápido, quieres capturarlas con granularidad fina.
  • scrape_timeout de 5s para no bloquear el scrape si Traefik está saturado. Mejor perder una muestra que tener Prometheus esperando.
  • Labels adicionales como service y env para filtrar en Grafana después.
  • node_exporter y cAdvisor para métricas del sistema y de los contenedores. Así cuando veas que la latencia sube, puedes mirar si es porque la CPU está al 100%.

Usando service discovery de Docker

Si no quieres configurar targets a mano, Prometheus puede descubrir contenedores automáticamente:

scrape_configs:
  - job_name: 'docker'
    docker_sd_configs:
      - host: "unix:///var/run/docker.sock"
        refresh_interval: 15s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        regex: '/(.*)'
        target_label: 'container'
      - source_labels: [__meta_docker_container_label_prometheus_job]
        regex: '(.+)'
        target_label: 'job'

Esto te permite etiquetar tus contenedores con labels de Docker tipo prometheus.job=traefik y Prometheus los descubre solito. Muy útil cuando tienes muchos servicios y no quieres mantener una lista estática.

Almacenamiento y retención

Prometheus por defecto guarda 15 días de métricas. Si quieres cambiarlo:

# prometheus.yml
global:
  scrape_interval: 15s

storage:
  tsdb:
    retention:
      time: 30d
      size: 50GB

Puedes configurar retención por tiempo (30d) o por tamaño máximo (50GB). Si tienes mucho tráfico, te recomiendo limitar por tamaño para no reventar el disco.

Consultas útiles en PromQL

Te dejo algunas queries que uso a menudo. Dales caña:

Tasa de peticiones por segundo:

rate(traefik_requests_total[5m])

Tasa de errores HTTP 5xx:

rate(traefik_requests_total{code=~"5.."}[5m]) / rate(traefik_requests_total[5m]) * 100

Percentil 99 de latencia (p99):

histogram_quantile(0.99, sum(rate(traefik_request_duration_seconds_bucket[5m])) by (le))

Peticiones lentas (>1s):

rate(traefik_request_duration_seconds_bucket{le="1.0"}[5m]) / ignoring(le) rate(traefik_request_duration_seconds_count[5m])

Conexiones abiertas ahora mismo:

traefik_open_connections

Certificados que expiran en menos de 30 días:

traefik_tls_certificates_not_after - time() < 2592000

Recargas de configuración fallidas:

increase(traefik_config_reloads_failures_total[1h]) > 0

Con estas queries puedes construir alertas en Prometheus o dashboards en Grafana. Te recomiendo que empieces con la tasa de errores y la latencia p99 — son las dos métricas que más problemas te van a destapar.

Dashboards de Grafana

Tener las métricas en Prometheus está muy bien, pero si no las visualizas, no te sirven para nada. Grafana es el mejor compañero de Prometheus y juntos forman un equipo imbatible.

Importar dashboards predefinidos

Lo más rápido es importar un dashboard ya hecho. Hay varios específicos para Traefik:

  • ID 17378: dashboard oficial de Traefik para Prometheus. Tiene paneles de peticiones, latencia, códigos de estado, conexiones activas, recargas de configuración, TLS, y más. Es el que te recomiendo si quieres empezar rápido.
  • ID 4475: dashboard más simple pero muy completo. Buena opción si prefieres algo menos abrumador.
  • ID 18638: dashboard centrado en rendimiento con percentiles de latencia y throughput.
  • ID 15758: dashboard de Traefik con panel de topología de servicios.

Solo tienes que ir a Grafana → Import → pegar el ID → seleccionar la fuente de datos Prometheus → y listo.

Configurar la fuente de datos en Grafana

Si usas el docker-compose que te pongo más abajo, necesitas configurar Grafana para que sepa dónde está Prometheus:

# grafana-datasources.yml
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false
    jsonData:
      timeInterval: "15s"
      queryTimeout: "30s"
      httpMethod: "POST"

Y lo montas como volumen en Grafana:

services:
  grafana:
    image: grafana/grafana:latest
    volumes:
      - ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml:ro
      - ./grafana-dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yml:ro
      - ./grafana-dashboards:/var/lib/grafana/dashboards

Dashboards provisionados automáticamente

Si no quieres andar importando a mano cada vez que reinicies, puedes provisionar dashboards:

# grafana-dashboards.yml
apiVersion: 1

providers:
  - name: 'Traefik'
    orgId: 1
    folder: 'Traefik'
    type: file
    disableDeletion: true
    editable: true
    updateIntervalSeconds: 30
    options:
      path: /var/lib/grafana/dashboards

Y descargas el JSON del dashboard 17378, lo guardas en ./grafana-dashboards/traefik-official.json, y Grafana lo carga automáticamente al arrancar.

Qué paneles no pueden faltar en tu dashboard

Si prefieres crear tu propio dashboard desde cero, te recomiendo estos paneles mínimos:

  1. RPS (Requests Per Second) — gráfica de área con la tasa de peticiones. Filtrable por entrypoint y servicio.
  2. Latencia p50, p95 y p99 — tres líneas superpuestas. Cuando el p99 se separa del p50, tienes un problema de rendimiento.
  3. Códigos de estado HTTP — stacked bar chart con 2xx, 3xx, 4xx, 5xx. Los 5xx deben ser cero o casi cero.
  4. Throughput (bytes) — tráfico de entrada y salida.
  5. Conexiones activas — gauge o time series. Si ves una meseta que no baja, hay clientes colgados.
  6. Errores TLS — contador de errores de handshake TLS y validación de certificados.
  7. Estado de recargas — 1/0 según si la última recarga fue exitosa. Si ves un 0, algo está mal en tu configuración.
  8. Certificados próximos a expirar — tabla con los CN y días restantes.

Te recomiendo que añadas también un panel de health check con las métricas del sistema (CPU, RAM, disco) del host donde corre Traefik. A veces el problema no es Traefik, sino que el servidor se está quedando sin recursos.

Alertas en Prometheus

Las métricas están muy bien, pero si tienes que mirar el dashboard cada 5 minutos, algo estás haciendo mal. Las alertas te avisan cuando algo va mal, y te recomiendo que configures al menos estas:

# alertas.yml
groups:
  - name: traefik
    rules:
      - alert: HighErrorRate
        expr: |
          rate(traefik_requests_total{code=~"5.."}[5m]) /
          rate(traefik_requests_total[5m]) * 100 > 5
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Tasa de error alta en Traefik"
          description: "La tasa de errores 5xx es del {{ $value | humanizePercentage }} en los últimos 5 minutos."

      - alert: HighLatency
        expr: |
          histogram_quantile(0.99,
            sum(rate(traefik_request_duration_seconds_bucket[5m])) by (le)
          ) > 3
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Latencia alta en Traefik"
          description: "El p99 de latencia es de {{ $value | humanizeDuration }}."

      - alert: CertificateExpiringSoon
        expr: |
          traefik_tls_certificates_not_after - time() < 604800
        labels:
          severity: critical
        annotations:
          summary: "Certificado TLS próximo a expirar"
          description: "El certificado {{ $labels.cn }} expira en menos de 7 días."

      - alert: ConfigReloadFailed
        expr: |
          increase(traefik_config_reloads_failures_total[15m]) > 0
        labels:
          severity: critical
        annotations:
          summary: "Error en recarga de configuración"
          description: "Traefik ha fallado al recargar la configuración."

      - alert: NoTraffic
        expr: |
          rate(traefik_requests_total[5m]) == 0
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Sin tráfico en Traefik"
          description: "No se detectan peticiones en los últimos 10 minutos."

Y en prometheus.yml añades:

rule_files:
  - "alertas.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

Esto te puede ahorrar más de un disgusto cuando un certificado caduca un domingo por la noche.

OpenTelemetry (OTLP)

Traefik v3 introduce soporte nativo para OpenTelemetry, el estándar moderno para observabilidad. Con OTLP puedes enviar trazas (traces) y métricas a sistemas como Jaeger, Tempo, Grafana, o cualquier backend que soporte OTLP.

Activar OpenTelemetry en Traefik

experimental:
  openTelemetry:
    grpc:
      address: "otel-collector:4317"
      insecure: true

O usando HTTP (el puerto 4318 es el estándar HTTP para OTLP):

experimental:
  openTelemetry:
    http:
      address: "otel-collector:4318"
      insecure: true

¿La diferencia? gRPC es más eficiente en términos de rendimiento y usa protocol buffers. HTTP es más fácil de depurar y funciona mejor si hay firewalls que bloquean gRPC. Te recomiendo gRPC para producción y HTTP si estás haciendo pruebas.

Configuración completa de OpenTelemetry en Traefik

Puedes ajustar varios parámetros:

experimental:
  openTelemetry:
    grpc:
      address: "otel-collector:4317"
      insecure: true
      # timeout: 5s
      # headers:
      #   x-api-key: "tu-api-key"
    # También puedes usar headers de propagación personalizados
    # propagation: "tracecontext,baggage"

El campo propagation define qué formatos de contexto de tracing usar. Por defecto usa W3C Trace Context (tracecontext), que es el estándar moderno. Si tienes servicios legacy que usan Zipkin o Jaeger, puedes añadirlos:

experimental:
  openTelemetry:
    grpc:
      address: "otel-collector:4317"
      insecure: true
    propagation: "tracecontext,baggage,jaeger"

¿Para qué sirven las trazas?

Cada petición que entra en Traefik genera una traza (trace) que puedes seguir a través de todos los servicios. Una traza está compuesta de spans, cada uno representando una operación: el span de Traefik recibiendo la petición, el span del middleware de autenticación, el span del balanceador pasando la petición al backend, el span del backend procesándola…

Esto es oro puro para depurar problemas de rendimiento. Por ejemplo, si un usuario se queja de que la página va lenta, puedes abrir la traza completa en Jaeger o Tempo y ver:

  • Cuánto tardó Traefik en enrutar (normalmente <1ms).
  • Cuánto tardó el middleware de rate limiting.
  • Cuánto tardó el middleware de autenticación (¿está llamando a un servicio externo?).
  • Cuánto tardó el servicio en responder.
  • Cuánto tardó la respuesta en volver al cliente.

Sin trazas, solo ves que algo es lento. Con trazas, sabes exactamente qué parte es la culpable.

OpenTelemetry Collector: configuración avanzada

Para aprovechar OpenTelemetry, necesitas un collector que reciba las trazas y las envíe a un backend. El collector es el cerebro de la operación: recibe datos por un lado, los procesa, y los envía a uno o varios destinos.

# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    container_name: otel-collector
    command: ["--config=/etc/otelcol-contrib/config.yaml"]
    volumes:
      - ./otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro
    networks:
      - traefik-net

Y su configuración. Aquí te pongo un ejemplo completo con exportadores a varios backends:

# otel-collector.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  attributes:
    actions:
      - key: environment
        value: production
        action: upsert
      - key: datacenter
        value: mad01
        action: upsert
  filter:
    error_mode: ignore
    traces:
      span:
        - 'attributes["http.status_code"] >= 200 and attributes["http.status_code"] < 400'

exporters:
  logging:
    verbosity: detailed
  otlp:
    endpoint: "tempo:4317"
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: traefik_otlp
    const_labels:
      source: otel-collector

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, attributes, batch]
      exporters: [logging, otlp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, attributes, batch]
      exporters: [logging, prometheus]

Vamos por partes:

  • receivers: escucha en gRPC (4317) y HTTP (4318) para recibir trazas y métricas de Traefik.
  • processors: aquí está la magia. batch agrupa trazas para mejorar rendimiento, memory_limiter evita que el collector se coma toda la RAM, attributes añade metadatos como entorno y datacenter, y filter descarta trazas que no nos interesan (por ejemplo, solo guardar errores).
  • exporters: logging muestra las trazas en la consola del collector (ideal para debug), otlp las envía a Tempo o Jaeger, y prometheus expone métricas del propio collector.

Enviando trazas a Grafana Tempo

Grafana Tempo es un backend de trazas escalable y se integra perfectamente con Grafana. Para usarlo:

services:
  tempo:
    image: grafana/tempo:latest
    container_name: tempo
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yml:/etc/tempo.yaml:ro
      - ./tempo-data:/tmp/tempo
    networks:
      - traefik-net

  # Y el collector apunta a tempo

Configuración de Tempo:

# tempo.yml
server:
  http_listen_port: 3200

distributor:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317

storage:
  trace:
    backend: local
    local:
      path: /tmp/tempo/blocks
    wal:
      path: /tmp/tempo/wal

compactor:
  compaction:
    block_retention: 48h

Enviando trazas a Jaeger

Si prefieres Jaeger en lugar de Tempo:

services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    container_name: jaeger
    ports:
      - "16686:16686"  # UI
    networks:
      - traefik-net

Y en el collector cambias el exportador:

exporters:
  otlp:
    endpoint: "jaeger:4317"
    tls:
      insecure: true

Luego abres http://localhost:16686 y ves todas las trazas en la UI de Jaeger. Te recomiendo que pruebes esto aunque luego uses Tempo — Jaeger es más sencillo de configurar para empezar.

Headers de propagación en tus servicios

Para que las trazas atraviesen todos tus servicios, necesitas propagar los headers de OpenTelemetry. Cuando Traefik recibe una petición, añade estos headers antes de pasarla al backend:

traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: traefik=1
baggage: user-id=123,session-id=abc

Tus servicios backend deben leer estos headers y continuar la traza. Cada lenguaje tiene su SDK de OpenTelemetry:

  • Python: opentelemetry-api + opentelemetry-sdk
  • Node.js: @opentelemetry/api + @opentelemetry/node
  • Go: go.opentelemetry.io/otel
  • Java: io.opentelemetry:opentelemetry-api
  • PHP: open-telemetry/opentelemetry

Te pongo un ejemplo rápido en Python con FastAPI:

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Configurar el tracer
provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="otel-collector:4317", insecure=True))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

# Instrumentar FastAPI automáticamente
app = FastAPI()
FastAPIInstrumentor.instrument_app(app)

Así de sencillo. Tu servicio FastAPI ya genera trazas que continúan las que empezó Traefik.

Logs de Traefik

Además de los logs de acceso, Traefik tiene sus propios logs de sistema. Por defecto son stdout (la salida estándar del contenedor), pero puedes configurarlos:

log:
  level: INFO
  filePath: /var/log/traefik/traefik.log
  format: json

Niveles disponibles: DEBUG, INFO, WARN, ERROR. En producción usa INFO. DEBUG es muy verboso y solo para depurar. Te recomiendo que no actives DEBUG en producción a menos que estés investigando un problema específico — llenará tus logs en cuestión de minutos.

Diferencias entre logs de acceso y logs de sistema

Esto es importante y mucha gente lo confunde:

  • Logs de acceso (accessLog): registran cada petición HTTP que pasa por Traefik. Quién pide qué, cuándo, y cómo responde Traefik.
  • Logs de sistema (log): registran eventos internos de Traefik. Recargas de configuración, errores internos, warnings, conexiones establecidas con proveedores.

Los logs de sistema son los que miras cuando Traefik no arranca o se comporta de forma extraña. Los logs de acceso son los que miras para auditar tráfico o analizar rendimiento.

Integración con Loki

Loki es un sistema de logs de Grafana, diseñado específicamente para logs que ya están estructurados. A diferencia de Elasticsearch, Loki no indexa el contenido del log, solo los metadatos (labels). Esto lo hace mucho más ligero y barato de operar.

services:
  loki:
    image: grafana/loki:latest
    container_name: loki
    ports:
      - "3100:3100"
    volumes:
      - ./loki.yml:/etc/loki/loki.yml:ro
      - ./loki-data:/loki
    networks:
      - traefik-net

Configuración de Loki:

# loki.yml
auth_enabled: false

server:
  http_listen_port: 3100

ingester:
  lifecycler:
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
  chunk_idle_period: 5m
  chunk_retain_period: 30s

schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /loki/index
    cache_location: /loki/cache
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks

limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h

Y necesitas Promtail para leer los logs de Traefik y enviarlos a Loki:

services:
  promtail:
    image: grafana/promtail:latest
    container_name: promtail
    volumes:
      - ./promtail.yml:/etc/promtail/promtail.yml:ro
      - /var/log/traefik:/var/log/traefik:ro
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - traefik-net

Configuración de Promtail:

# promtail.yml
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: traefik
    static_configs:
      - targets: [localhost]
        labels:
          job: traefik
          __path__: /var/log/traefik/access.log
    pipeline_stages:
      - json:
          expressions:
            ClientHost: ClientHost
            RequestHost: RequestHost
            RequestPath: RequestPath
            RequestMethod: RequestMethod
            ResponseStatus: ResponseStatus
            Duration: Duration
            ResponseSize: ResponseSize
      - labels:
          ResponseStatus:
          RequestMethod:
      - timestamp:
          source: Time
          format: RFC3339Nano

Con Promtail parseas el JSON de los logs de acceso, extraes campos como labels de Loki, y puedes filtrar y buscar en Grafana como si fuera una base de datos.

Ejemplo completo

Te dejo un docker-compose.yml completo con Traefik, Prometheus, Grafana, Loki, Promtail, OpenTelemetry Collector y Tempo para monitorización completa. Esto es lo que te recomiendo para producción:

services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
      - ./logs:/var/log/traefik
    networks:
      - monitoring-net

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./prometheus/alertas.yml:/etc/prometheus/alertas.yml:ro
      - prometheus-data:/prometheus
    networks:
      - monitoring-net

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - ./grafana/grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml:ro
      - ./grafana/grafana-dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yml:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards
      - grafana-data:/var/lib/grafana
    networks:
      - monitoring-net

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    restart: unless-stopped
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
    networks:
      - monitoring-net

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    container_name: otel-collector
    restart: unless-stopped
    command: ["--config=/etc/otelcol-contrib/config.yaml"]
    volumes:
      - ./otel/otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro
    networks:
      - monitoring-net

  tempo:
    image: grafana/tempo:latest
    container_name: tempo
    restart: unless-stopped
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yml:/etc/tempo.yaml:ro
      - tempo-data:/tmp/tempo
    networks:
      - monitoring-net

  loki:
    image: grafana/loki:latest
    container_name: loki
    restart: unless-stopped
    volumes:
      - ./loki.yml:/etc/loki/loki.yml:ro
      - loki-data:/loki
    networks:
      - monitoring-net

  promtail:
    image: grafana/promtail:latest
    container_name: promtail
    restart: unless-stopped
    volumes:
      - ./promtail.yml:/etc/promtail/promtail.yml:ro
      - ./logs:/var/log/traefik:ro
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - monitoring-net

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    networks:
      - monitoring-net
    pid: host

networks:
  monitoring-net:
    name: monitoring-net
    driver: bridge

volumes:
  prometheus-data:
  grafana-data:
  tempo-data:
  loki-data:

Fíjate en que he añadido node-exporter para métricas del sistema. No solo monitorices Traefik, monitoriza también el servidor donde corre.

Troubleshooting

No todo va a funcionar a la primera. Aquí te dejo los problemas más comunes y cómo solucionarlos.

Prometheus no scrapea métricas

Síntoma: Prometheus marca el target como DOWN en la página de targets (http://prometheus:9090/targets).

Posibles causas:

  1. El entrypoint de métricas no es accesible desde Prometheus. Verifica que el entrypoint metrics esté escuchando en 0.0.0.0:8082 o en la IP de la red de Docker. Si pusiste 127.0.0.1:8082, solo es accesible desde el propio contenedor de Traefik.
# Verifica desde dentro del contenedor de Prometheus
docker exec prometheus wget -qO- http://traefik:8082/metrics | head -20
  1. Redes de Docker separadas. Prometheus y Traefik deben estar en la misma red Docker.
  2. Firewall bloqueando el puerto. Si Traefik y Prometheus están en hosts diferentes, asegúrate de que el puerto 8082 esté abierto.

Grafana no ve datos

Síntoma: Los paneles de Grafana muestran «No data» o «N/A».

Posibles causas:

  1. La fuente de datos apunta a la URL incorrecta. En Grafana, la URL debe ser http://prometheus:9090 (nombre del servicio Docker, no localhost).
  2. El intervalo de tiempo es demasiado corto. Las métricas de Prometheus tardan en acumularse. Pon un rango de al menos 5-15 minutos.
  3. Las queries de PromQL son incorrectas para tu versión. Algunos nombres de métricas cambiaron entre versiones de Traefik. Verifica los nombres exactos en http://traefik:8082/metrics.

OpenTelemetry no envía trazas

Síntoma: No aparecen trazas en Jaeger/Tempo.

Posibles causas:

  1. El collector no está accesible. Verifica que el contenedor otel-collector esté ejecutándose y escuchando en los puertos correctos:
docker logs otel-collector
docker exec otel-collector netstat -tlnp
  1. La configuración de Traefik no tiene experimental habilitado. Sin esa sección, OpenTelemetry no funciona.
  2. El puerto gRPC o HTTP no coincide. Asegúrate de que el puerto en la configuración de Traefik coincida con el del collector.
  3. Timeout de conexión. Si el collector tarda en responder, Traefik puede descartar la traza. Ajusta el timeout:
experimental:
  openTelemetry:
    grpc:
      address: "otel-collector:4317"
      insecure: true
      timeout: 10s

Logs de acceso vacíos

Síntoma: El archivo de log existe pero está vacío, o no se crea.

Posibles causas:

  1. El directorio de logs no existe. Traefik no crea directorios automáticamente. Asegúrate de que la ruta exista:
mkdir -p ./logs
chmod 755 ./logs
  1. Permisos del volumen. Si usas Docker, el usuario de Traefik (normalmente uid 1000 o 1001) debe poder escribir en el directorio montado.
  2. Formato incorrecto. Si usas format: json y el archivo no se genera, prueba primero con format: common para descartar problemas de serialización.

Certificados Let’s Encrypt no se renuevan

Síntoma: Los certificados caducan y Traefik no los renueva automáticamente.

Métricas para detectarlo:

traefik_tls_certificates_not_after - time() < 604800

Si esta alerta se dispara, revisa los logs de sistema de Traefik para ver si hay errores de ACME:

log:
  level: DEBUG
  filePath: /var/log/traefik/traefik.log
  format: json

Las causas más comunes son límites de rate de Let’s Encrypt (5 renovaciones por dominio a la semana) o problemas de conectividad con el servidor ACME.

Alto consumo de memoria

Síntoma: Traefik consume cada vez más RAM con el tiempo (memory leak aparente).

Posibles causas:

  1. Buffering de logs demasiado grande. Reduce bufferingSize en el accessLog.
  2. Demasiadas métricas con alta cardinalidad. Si tienes muchos servicios, cada uno con múltiples labels, Prometheus puede generar muchas series temporales. Limita las labels:
metrics:
  prometheus:
    addEntryPointsLabels: true
    addServicesLabels: false  # Si tienes muchos servicios, desactiva esto
    manualRouting: true
  1. OpenTelemetry sin límite de memoria en el collector. Añade el processor memory_limiter en el collector:
processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128

Buenas prácticas

  1. Activa los logs de acceso en formato JSON. Son más fáciles de procesar que el formato texto y cualquier sistema de logs los entiende.
  2. Usa Prometheus + Grafana para métricas. Es el stack más maduro y con mejores dashboards. Lleva años en producción en empresas de todos los tamaños.
  3. No expongas el endpoint de métricas a internet. Ponlo en localhost o en tu red interna. Si alguien accede a tus métricas, sabe exactamente qué servicios tienes y cómo se comportan.
  4. Configura alertas cuanto antes. No sirve de nada tener métricas si no te avisan cuando algo va mal. Empieza con las alertas críticas (errores 5xx, certificados próximos a expirar, latencia alta) y luego añade más.
  5. OpenTelemetry es para sistemas complejos. Si tienes 2-3 servicios, con Prometheus y logs tienes suficiente. Pero si tienes microservicios que se llaman entre sí, las trazas son la única forma de entender qué está pasando.
  6. Retención de datos sensata. No guardes logs y métricas para siempre. Define una política de retención: 30 días para métricas, 90 días para logs, 7 días para trazas. Ajusta según tus necesidades y espacio disponible.
  7. Monitoriza también el monitoring. Asegúrate de que Prometheus, Grafana y Loki tienen recursos suficientes y están funcionando. No hay nada más triste que un sistema de monitorización caído.
  8. Prueba las alertas. Simula un fallo: para un servicio, cambia la configuración a propósito, deja caducar un certificado de prueba. Verifica que las alertas se disparan y te llegan por el canal correcto (email, Slack, Telegram…).

Conclusión

La monitorización es lo que separa un sistema amateur de uno profesional. Con Prometheus, Grafana, logs de acceso, Loki y OpenTelemetry, tienes visibilidad completa de lo que pasa en tu infraestructura Traefik. Cuando algo vaya mal, lo sabrás antes de que los usuarios te lo digan — y eso, créeme, no tiene precio.

Te recomiendo que montes todo el stack que te he mostrado, aunque sea en un entorno de pruebas primero. Configura las alertas, juega con los dashboards, aprende a leer las trazas. Cuando llegue el momento de pasar a producción, ya sabrás exactamente qué mirar y dónde.

Y recuerda: el monitoring no es un proyecto de un día. Es algo que vas ajustando con el tiempo. Añade métricas nuevas, refina las alertas, mejora los dashboards. Dale caña y no dejes de iterar.

En el próximo y último capítulo veremos HTTP/3 y rendimiento, cómo optimizar Traefik para sacarle el máximo partido.


Más información,

Deja una respuesta