--- title: "Cómo instalar Jaeger v2 en Ubuntu 24.04 con Docker" description: "Despliega Jaeger v2 con Docker Compose en Ubuntu 24.04, guarda las trazas en disco con Badger e instrumenta una app Python con OpenTelemetry para ver sus spans." category: "Stack de Observabilidad" language: es source: https://cubepath.com/es/docs/stack-de-observabilidad/instalacion-de-jaeger-para-rastreo-distribuido updated: 2026-09-25 --- # Cómo instalar Jaeger v2 en Ubuntu 24.04 con Docker Jaeger es una plataforma de rastreo distribuido: recibe las trazas que generan tus aplicaciones y te permite seguir una petición a través de todos los servicios que atraviesa, con el tiempo que pasa en cada uno. Desde la versión 2, Jaeger está construido sobre OpenTelemetry Collector y recibe los datos directamente por OTLP, el protocolo estándar de OpenTelemetry. En este tutorial desplegarás Jaeger v2 con Docker Compose en Ubuntu 24.04, con almacenamiento persistente en disco, e instrumentarás una pequeña aplicación Python para ver sus trazas en la interfaz web. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM. - Un usuario no root con privilegios `sudo`. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker (consulta la guía "Cómo instalar Docker en Ubuntu 24.04, Debian 12 y Rocky 9"). - Acceso SSH desde tu equipo, para abrir la interfaz de Jaeger mediante un túnel. ## Conceptos básicos Antes de empezar conviene tener claros tres términos: - **Span**: una operación con nombre, inicio y duración, por ejemplo "GET /pedidos" o "SELECT en la tabla users". Puede llevar atributos (método HTTP, código de estado, errores). - **Traza**: el conjunto de spans de una misma petición, enlazados por un identificador común y organizados en árbol. - **OTLP**: el protocolo con el que los SDK de OpenTelemetry envían las trazas. Jaeger lo acepta por gRPC en el puerto `4317` y por HTTP en el `4318`. Jaeger v2 sustituye a los antiguos componentes separados de la versión 1 (agent, collector, query). Un único binario hace de receptor OTLP, almacenamiento e interfaz web. El antiguo `jaeger-agent` ya no existe: las aplicaciones envían las trazas por OTLP directamente a Jaeger o a un OpenTelemetry Collector. ## Paso 1: Preparar el directorio del proyecto Crea un directorio para Jaeger con un subdirectorio `data` donde se guardarán las trazas: ```bash mkdir -p ~/jaeger/data cd ~/jaeger ``` La imagen oficial de Jaeger se ejecuta con el usuario sin privilegios de UID `10001`, así que ese usuario debe poder escribir en `data`: ```bash sudo chown -R 10001:10001 ~/jaeger/data ``` ## Paso 2: Configurar Jaeger con almacenamiento Badger Sin configuración, la imagen de Jaeger guarda las trazas en memoria y se pierden al reiniciar el contenedor. Para un servidor de un solo nodo, **Badger** es la opción más sencilla: una base de datos embebida que escribe en disco sin servicios adicionales. Para volúmenes grandes o varios nodos, Jaeger admite Elasticsearch, OpenSearch o Cassandra. La configuración de Jaeger v2 sigue el formato de OpenTelemetry Collector: receptores, procesadores, exportadores y extensiones, unidos en un pipeline. Crea el archivo: ```bash nano config.yaml ``` ```yaml service: extensions: [jaeger_storage, jaeger_query] pipelines: traces: receivers: [otlp] processors: [batch] exporters: [jaeger_storage_exporter] extensions: jaeger_query: storage: traces: badger_store http: endpoint: 0.0.0.0:16686 jaeger_storage: backends: badger_store: badger: directories: keys: /badger/keys values: /badger/values ephemeral: false receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: exporters: jaeger_storage_exporter: trace_storage: badger_store ``` Qué hace cada bloque: - `receivers.otlp`: acepta trazas OTLP por gRPC y HTTP. Escucha en `0.0.0.0` dentro del contenedor; la exposición real hacia fuera la controla Docker en el paso siguiente. - `processors.batch`: agrupa los spans antes de escribirlos, lo que reduce la carga sobre el almacenamiento. - `extensions.jaeger_storage`: define un backend llamado `badger_store` que escribe en `/badger` dentro del contenedor. - `extensions.jaeger_query`: sirve la interfaz web y la API de consulta en el puerto `16686`, leyendo de ese mismo backend. - `exporters.jaeger_storage_exporter`: escribe en `badger_store` las trazas que llegan por el pipeline. ## Paso 3: Arrancar Jaeger con Docker Compose Crea el archivo `compose.yaml`: ```bash nano compose.yaml ``` ```yaml services: jaeger: image: jaegertracing/jaeger:latest container_name: jaeger restart: unless-stopped command: ["--config", "/jaeger/config.yaml"] volumes: - ./config.yaml:/jaeger/config.yaml:ro - ./data:/badger ports: - "127.0.0.1:16686:16686" - "127.0.0.1:4317:4317" - "127.0.0.1:4318:4318" ``` Todos los puertos se publican solo en `127.0.0.1`. Docker gestiona sus propias reglas de iptables y los puertos publicados en todas las interfaces se saltan UFW, así que de este modo ni la interfaz (que no tiene autenticación) ni los receptores OTLP quedan expuestos a Internet. > Consejo: `latest` apunta a la última versión 2.x. En producción, fija una versión concreta (por ejemplo `jaegertracing/jaeger:2.x.y`, consulta las etiquetas en Docker Hub) para que una actualización no llegue sin que la hayas probado. Arranca el contenedor: ```bash docker compose up -d ``` Comprueba que está en marcha y revisa los logs: ```bash docker compose ps docker compose logs jaeger | tail -n 20 ``` En los logs no debe haber líneas de nivel `error`, y deberías ver que se inician los receptores OTLP en `0.0.0.0:4317` y `0.0.0.0:4318` y el servidor HTTP de consulta en `0.0.0.0:16686`. Comprueba además que Badger ha creado sus archivos: ```bash sudo ls ~/jaeger/data ``` ```output keys values ``` Por último, verifica que la API de consulta responde: ```bash curl -s http://127.0.0.1:16686/api/services ``` ```output {"data":[],"total":0,"limit":0,"offset":0,"errors":null} ``` La lista de servicios aún está vacía porque ninguna aplicación ha enviado trazas. ## Paso 4: Instrumentar una aplicación Python con OpenTelemetry Para generar trazas reales vas a crear una pequeña aplicación Flask y a instrumentarla con la autoinstrumentación de OpenTelemetry, que crea spans para cada petición HTTP sin tocar el código. Añadirás además un span manual para una operación interna. Instala el soporte de entornos virtuales de Python: ```bash sudo apt update sudo apt install python3-venv ``` Crea el proyecto y un entorno virtual: ```bash mkdir -p ~/trace-demo cd ~/trace-demo python3 -m venv venv source venv/bin/activate ``` Instala Flask, la distribución de OpenTelemetry y el exportador OTLP: ```bash pip install flask opentelemetry-distro opentelemetry-exporter-otlp ``` `opentelemetry-bootstrap` detecta las librerías instaladas (en este caso Flask) e instala sus paquetes de instrumentación: ```bash opentelemetry-bootstrap -a install ``` Crea la aplicación: ```bash nano app.py ``` ```python import random import time from flask import Flask from opentelemetry import trace app = Flask(__name__) tracer = trace.get_tracer(__name__) def calcular_precio(producto_id): # Span manual para una operación interna with tracer.start_as_current_span("calcular_precio") as span: span.set_attribute("producto.id", producto_id) time.sleep(random.uniform(0.02, 0.2)) return round(random.uniform(5, 50), 2) @app.route("/producto/") def producto(producto_id): precio = calcular_precio(producto_id) return {"id": producto_id, "precio": precio} if __name__ == "__main__": app.run(host="127.0.0.1", port=5000) ``` Ejecuta la aplicación con `opentelemetry-instrument`. Las variables de entorno indican el nombre del servicio, que se exporten solo trazas y que se envíen por OTLP/HTTP al puerto `4318` de Jaeger: ```bash export OTEL_SERVICE_NAME=tienda-demo export OTEL_TRACES_EXPORTER=otlp export OTEL_METRICS_EXPORTER=none export OTEL_LOGS_EXPORTER=none export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 opentelemetry-instrument python app.py ``` ```output * Serving Flask app 'app' * Running on http://127.0.0.1:5000 Press CTRL+C to quit ``` Deja la aplicación en marcha. En otra sesión SSH, genera unas cuantas peticiones: ```bash for i in $(seq 1 20); do curl -s "http://127.0.0.1:5000/producto/$i"; echo; done ``` Tras unos segundos (el procesador `batch` y el SDK envían los spans por lotes), comprueba que Jaeger ha registrado el servicio: ```bash curl -s http://127.0.0.1:16686/api/services ``` ```output {"data":["tienda-demo"],"total":1,"limit":0,"offset":0,"errors":null} ``` ## Paso 5: Explorar las trazas en la interfaz web La interfaz de Jaeger solo escucha en localhost. Desde tu equipo local, abre un túnel SSH hacia el puerto `16686` del servidor, sustituyendo `your_user` y `your_server_ip`: ```bash ssh -N -L 16686:127.0.0.1:16686 your_user@your_server_ip ``` Con el túnel abierto, visita `http://localhost:16686` en el navegador: 1. En **Service**, elige `tienda-demo` y pulsa **Find Traces**. 2. El gráfico superior muestra la duración de cada traza en el tiempo. Los puntos más altos son las peticiones más lentas. 3. Abre una traza. Verás un span raíz `GET /producto/` creado por la instrumentación de Flask y, dentro de él, el span `calcular_precio` con el atributo `producto.id`. 4. Pulsa sobre cada span para ver sus atributos: método HTTP, ruta, código de estado y la duración exacta. Con varias aplicaciones instrumentadas que se llaman entre sí, la misma vista muestra el recorrido completo de una petición, y la pestaña **System Architecture** dibuja el grafo de dependencias entre servicios. ## Paso 6: Controlar el volumen con muestreo En producción no suele ser necesario guardar el 100 % de las trazas. El muestreo se configura en el SDK de cada aplicación, también con variables de entorno. Esta configuración guarda el 10 % de las trazas nuevas y respeta la decisión del servicio que inició la petición, para no romper trazas a medias: ```bash export OTEL_TRACES_SAMPLER=parentbased_traceidratio export OTEL_TRACES_SAMPLER_ARG=0.1 ``` Reinicia la aplicación con estas variables y lanza de nuevo las 20 peticiones: en Jaeger aparecerán aproximadamente dos trazas nuevas en lugar de veinte. ## Enviar trazas desde otros servidores Si tus aplicaciones se ejecutan en otros servidores, tienen que alcanzar los puertos OTLP de Jaeger. Las opciones más seguras son: - Publicar `4317` y `4318` en la IP privada del servidor (por ejemplo `"10.0.0.5:4318:4318"` en `compose.yaml`) y usar esa red privada entre servidores. - Ejecutar un OpenTelemetry Collector en cada servidor que reenvíe las trazas a Jaeger por una conexión cifrada. Evita publicar los puertos OTLP en la IP pública: no tienen autenticación y cualquiera podría enviar datos a tu Jaeger. ## Solución de problemas **El contenedor se reinicia en bucle con un error de permisos en `/badger`**: el directorio `data` no pertenece al UID del contenedor. Ejecuta `sudo chown -R 10001:10001 ~/jaeger/data` y `docker compose up -d`. **El contenedor no arranca y el log indica un error en la configuración**: Jaeger valida `config.yaml` al iniciar y rechaza claves desconocidas o mal indentadas. Revisa la línea que indica `docker compose logs jaeger` y recuerda que YAML no admite tabuladores. **`tienda-demo` no aparece en Jaeger**: comprueba que las variables `OTEL_*` están exportadas en la misma sesión donde ejecutas `opentelemetry-instrument`, que el endpoint es `http://127.0.0.1:4318` sin ruta adicional y que Jaeger está en marcha (`docker compose ps` en `~/jaeger`). **`channel 2: open failed` al abrir el túnel SSH**: Jaeger no está escuchando en `127.0.0.1:16686` del servidor. Comprueba los puertos publicados con `docker compose ps`. ## Conclusión Has desplegado Jaeger v2 con Docker Compose en Ubuntu 24.04, con las trazas persistidas en disco mediante Badger y los puertos limitados a localhost, y has instrumentado una aplicación Python con OpenTelemetry para ver sus spans automáticos y manuales. Como OpenTelemetry es un estándar, el mismo Jaeger sirve para aplicaciones en Go, Java, Node.js o .NET cambiando solo el SDK. Como siguientes pasos puedes: - Instrumentar el resto de tus servicios con el SDK de OpenTelemetry de su lenguaje y enviar las trazas al mismo Jaeger. - Migrar el almacenamiento a OpenSearch o Elasticsearch cuando el volumen de trazas crezca o necesites alta disponibilidad. - Correlacionar trazas con métricas y logs añadiendo Prometheus y Grafana a tu stack de observabilidad.