> La lista completa de artículos está en [llms.txt](https://docs.f3cloud.com/llms.txt). Cada página tiene versión en Markdown, basta con añadir .md a su dirección. Esta página en Markdown: [https://docs.f3cloud.com/es/docker/opentelemetry-collector.md](https://docs.f3cloud.com/es/docker/opentelemetry-collector.md).

# OpenTelemetry en un VPS – recogida de trazas y métricas con OpenTelemetry Collector

Instalación de OpenTelemetry Collector en un VPS con Docker Compose. Qué son las trazas, las métricas y los registros, el protocolo OTLP, receptores, procesadores y exportadores, envío de datos de prueba con la herramienta telemetrygen, conexión de una aplicación en Python sin tocar su código, vista de una traza en Jaeger a través de un túnel SSH y métricas del servidor con el receptor host_metrics. Probado en Debian 12 con OpenTelemetry Collector 0.163.1.

**OpenTelemetry** te ayuda a ver qué ocurre dentro de una aplicación y en el servidor. El programa envía a un mismo lugar las trazas de las peticiones, las métricas y las entradas del registro, y desde ahí puedes pasarlas a cualquier almacén y examinarlas en líneas de tiempo y gráficos. El intermediario principal de este esquema se llama **OpenTelemetry Collector**, y es el que levantaremos en el VPS. En este artículo iniciaremos Collector y Jaeger con Docker Compose, enviaremos datos de prueba, conectaremos una aplicación en Python sin tocar su código, abriremos una traza en el navegador y añadiremos las métricas del propio servidor. Hemos repetido todos los pasos en un servidor de pruebas con Debian 12, la versión de Collector en el momento de la prueba era la **0.163.1** y la de Jaeger, la **2.22.0**.

***

## Cómo funciona OpenTelemetry

Mientras una aplicación consta de un solo proceso, para entender su comportamiento suele bastar el registro. Pero cuando una petición pasa por varios servicios, por ejemplo por una aplicación web, una cola y una base de datos, el registro se queda corto. Quieres ver el recorrido de una petición concreta y saber cuánto tiempo pasó en cada paso, y de paso comprobar si el propio servidor se está quedando sin memoria o sin disco. Los datos que los programas y los servidores dan sobre su propio trabajo se llaman **telemetría**. OpenTelemetry, u OTel para abreviar, es un conjunto abierto de especificaciones, interfaces de programación y herramientas que describe cómo crear, transmitir y recoger la telemetría. No está atado a un proveedor concreto, así que los mismos datos se pueden enviar a Jaeger, a Prometheus o a un servicio comercial. Aun así, conviene ver enseguida el límite. Según la definición de la documentación oficial, OpenTelemetry no es un sistema de observabilidad, es decir, no guarda los datos ni dibuja gráficos, sino que deja el almacenamiento y la visualización a otros programas.

La telemetría se divide en tres tipos principales de **señales**. La **traza** (trace en inglés) describe el recorrido de una sola petición por la aplicación. Se compone de **spans**, y cada span representa una unidad de trabajo dentro de la petición, por ejemplo el procesamiento de una petición HTTP o una consulta a la base de datos. Un span tiene nombre, hora de inicio y de fin, atributos e identificadores. Todos los spans de una misma traza llevan un identificador de traza común, y un span anidado indica el identificador de su padre. Precisamente por estos vínculos Jaeger dibuja después el árbol de llamadas que veremos en el paso 5. La **métrica** es una medición tomada durante el funcionamiento, por ejemplo la memoria ocupada o el número de peticiones. A diferencia de la traza, describe el comportamiento del sistema en conjunto y no de una sola petición, por eso encaja bien en gráficos y alertas. El **registro** (log) es una anotación sobre un evento. Para no obligarte a abandonar las bibliotecas de registro habituales, OpenTelemetry se conecta a ellas, y cuando en la aplicación está activado el SDK o la instrumentación automática, a las entradas del registro se añaden por sí solos los identificadores de la traza y del span activos. Gracias a eso puedes vincular una línea del registro con la petición durante la cual apareció.

Veamos ahora cómo salen los datos de la aplicación. En el propio código se usa la **API**, que define cómo crear la telemetría, y de su implementación en cada lenguaje se encarga el **SDK**. El SDK recoge los spans y las métricas creados, los procesa y los exporta. No hace falta escribir la trazabilidad a mano para cada framework, porque existen bibliotecas de instrumentación que crean por sí solas los spans de las peticiones de las bibliotecas populares, y en los lenguajes que lo admiten se conectan sin tocar el código en absoluto. Así haremos con la aplicación en Flask. El SDK envía los datos mediante el protocolo **OTLP**, que tiene dos variantes de transmisión. **OTLP/gRPC** usa por defecto el puerto 4317 y **OTLP/HTTP**, el puerto 4318, y por HTTP los datos van a las direcciones `/v1/traces`, `/v1/metrics` y `/v1/logs`. Son dos protocolos distintos en dos puertos distintos, y confundirlos es muy fácil. En la sección de preguntas frecuentes hemos reproducido los errores que aparecen en ese caso.

La aplicación se puede configurar para que envíe los datos directamente al almacén, y para familiarizarse con OpenTelemetry o para un banco de pruebas pequeño es admisible. Sin embargo, en la mayoría de los casos junto a los servicios se ejecuta un **Collector**, un programa intermediario aparte que recibe la telemetría, la procesa y la reenvía. Así es más fácil para la aplicación, porque entrega los datos rápido y se olvida de ellos, mientras que los reintentos de envío, la agrupación en lotes, el cifrado y el filtrado de datos sensibles los asume Collector. Por dentro está organizado como un conjunto de canalizaciones, una para cada señal. La canalización empieza con los **receptores** (receivers), que reciben los datos, por ejemplo por OTLP o de las métricas del servidor. Después vienen los **procesadores** (processors), que modifican los datos por el camino, por ejemplo los agrupan en lotes. La canalización termina con los **exportadores** (exporters), que envían el resultado a uno o varios almacenes. Hay un detalle con el que tropiezan los principiantes. No basta con describir un componente en su sección de la configuración, además hay que enumerarlo en la sección `service`, o no se activará. Lo veremos en la configuración del paso 2. Collector se publica en varias compilaciones, y nosotros usaremos **contrib**, que tiene muchos más receptores que la básica. En las imágenes de la versión 0.163.1 hay 108 receptores frente a 7.

> **El papel de Jaeger en el esquema.**
>
> Jaeger es un sistema abierto para guardar y ver trazas. En nuestro esquema hace de almacén e interfaz web, y no forma parte del propio OpenTelemetry. Jaeger 2.22.0 recibe datos por OTLP, en su registro se ven las líneas Starting GRPC server y Starting HTTP server, así que Collector le pasará las trazas con el mismo protocolo.

***

## Qué necesitas

* **Un servidor** con Debian 12 y **al menos 2 GB de memoria**, por ejemplo el plan **DE-RZ9-1**. En nuestro servidor, Collector ocupó de 45 a 70 MB de memoria, Jaeger de 20 a 60 MB y la aplicación de demostración en Flask, unos 120 MB. Las imágenes de Docker ocuparon en el disco unos 750 MB, y el entorno virtual de Python, otros 67 MB. Si todavía no tienes servidor, contrátalo siguiendo la [guía](https://docs.f3cloud.com/es/client-area/order-vps.md).
* **Conexión por SSH**, por ejemplo con [Termius](https://docs.f3cloud.com/es/vps/termius-connection.md). Los comandos se ejecutan como `root`.
* **Docker y Docker Compose**. Cómo instalarlos se cuenta en un [artículo aparte](https://docs.f3cloud.com/es/docker/docker-installation.md). En nuestro caso estaban instalados Docker 29.9.2 y Compose 5.6.1.
* **Python 3** para la aplicación de demostración. En Debian 12 ya viene incluido, y nuestra versión era la 3.11.2.
* **Un terminal y un navegador en tu ordenador**, para abrir Jaeger a través de un túnel SSH.

***

## Paso 1. Prepara la carpeta y el archivo Compose

Crea la carpeta del proyecto y entra en ella.

```bash
mkdir -p /opt/otel
cd /opt/otel
```

Ahora crea el archivo `compose.yaml`. Copia el bloque de abajo completo, con la línea `cat` al principio y `EOF` al final, y pégalo en la ventana del terminal.

```bash
cat > compose.yaml <<'EOF'
# Jaeger guarda y muestra las trazas, Collector recibe la telemetría
services:
  jaeger:
    image: cr.jaegertracing.io/jaegertracing/jaeger:2.22.0
    container_name: otel-jaeger
    restart: unless-stopped
    ports:
      - "127.0.0.1:16686:16686"   # interfaz web de Jaeger, solo desde el propio servidor

  collector:
    image: otel/opentelemetry-collector-contrib:0.163.1
    container_name: otel-collector
    restart: unless-stopped
    volumes:
      - ./otel-config.yaml:/etc/otelcol-contrib/config.yaml:ro
    ports:
      - "127.0.0.1:4317:4317"     # OTLP gRPC
      - "127.0.0.1:4318:4318"     # OTLP HTTP
    depends_on:
      - jaeger
EOF
```

En el archivo se describen dos contenedores.

* **jaeger** guarda las trazas y las muestra en la interfaz web. Su puerto 16686 está publicado solo en la dirección interna del servidor `127.0.0.1`.
* **collector** recibe la telemetría. Sus puertos 4317 (OTLP/gRPC) y 4318 (OTLP/HTTP) también están publicados solo en `127.0.0.1`, así que las aplicaciones del propio servidor llegarán hasta él, pero desde internet no es accesible.

El archivo de ajustes de Collector se monta dentro del contenedor solo en modo lectura, y lo crearemos en el paso siguiente. Las versiones de las imágenes están indicadas de forma explícita para que, después de reiniciar, obtengas la misma versión y no una nueva.

> **Por qué los puertos solo en 127.0.0.1.**
>
> El Collector de este artículo no tiene contraseñas, y recibe datos de cualquiera que consiga conectarse al puerto. La documentación oficial de OpenTelemetry aconseja publicar el puerto de Collector en Docker solo en `127.0.0.1` si lo usan programas del mismo servidor. Además, el cortafuegos UFW no cierra por sí solo los puertos de Docker, de eso se habla en detalle en el [artículo sobre Docker](https://docs.f3cloud.com/es/docker/docker-installation.md). Hemos comprobado desde un ordenador doméstico que las conexiones a los puertos 4317, 4318 y 16686 en la dirección externa del servidor no se establecen y terminan por tiempo de espera agotado.

***

## Paso 2. Escribe la configuración de Collector

Crea el archivo `otel-config.yaml` del mismo modo, copiando el bloque completo.

```bash
cat > otel-config.yaml <<'EOF'
receivers:
  # Recepción de telemetría de las aplicaciones mediante el protocolo OTLP
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  # Agrupa los datos en lotes, así el envío es más eficiente
  batch:

exporters:
  # Las trazas van a Jaeger
  otlp_grpc/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  # Todo lo que recibe Collector se imprime en su registro
  debug:
    verbosity: basic

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp_grpc/jaeger, debug]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]
EOF
```

Repasemos el archivo por secciones.

* **receivers** describe los receptores. El receptor `otlp` espera datos en los puertos 4317 (gRPC) y 4318 (HTTP). La dirección `0.0.0.0` significa que la recepción se hace en todas las direcciones dentro del contenedor, y el acceso desde fuera queda limitado por cómo hemos publicado los puertos en `compose.yaml`.
* **processors** describe los procesadores. El procesador `batch` agrupa los datos en lotes antes de enviarlos.
* **exporters** describe los exportadores. El nombre `otlp_grpc/jaeger` se compone del tipo y de un nombre propio después de la barra, así se pueden tener varios exportadores del mismo tipo. La dirección `jaeger:4317` es el nombre del servicio de `compose.yaml`, y Docker encuentra por sí solo el contenedor con ese nombre. La línea `insecure: true` desactiva el cifrado, porque el intercambio se hace dentro de la red de Docker en un mismo servidor. El exportador `debug` imprime todo lo recibido en el registro de Collector, y el modo `basic` muestra una línea por cada lote de datos.
* **service** reúne las canalizaciones. Para cada señal se indica de dónde tomar los datos, cómo procesarlos y adónde enviarlos. Las trazas van tanto a Jaeger como al registro, y las métricas y los registros, por ahora, solo al registro.

> **No quites la dirección 0.0.0.0.**
>
> Si en el receptor no indicas `endpoint`, Collector escucha solo en la dirección interna del contenedor `127.0.0.1`, y los puertos publicados dejan de funcionar. Lo hemos comprobado en el servidor de pruebas, los detalles están en la sección de preguntas frecuentes.

> **Nombres de componentes en guías antiguas.**
>
> En la versión 0.163.1 el exportador de OTLP por gRPC se llama `otlp_grpc`, y el receptor de métricas del servidor, `host_metrics`. En las guías antiguas se encuentran los nombres `otlp` y `hostmetrics`. La comprobación de la configuración también los acepta, pero nosotros usamos los nombres nuevos.

***

## Paso 3. Comprueba la configuración e inicia

Antes de iniciar Collector, comprueba el archivo de ajustes. El comando de abajo ejecuta un contenedor de Collector de un solo uso con tu archivo y simplemente informa de si la configuración es correcta.

```bash
docker compose run --rm --no-deps collector validate --config=/etc/otelcol-contrib/config.yaml
echo $?
```

Si todo está en orden, el comando no imprimirá nada y `echo $?` mostrará `0`. Si hay un error en el archivo, verás su texto, y hay ejemplos en la sección de preguntas frecuentes. Ahora inicia los contenedores.

```bash
docker compose up -d
```

En el primer arranque, Docker descargará primero las imágenes. Pasados unos segundos, mira la última línea del registro de Collector.

```bash
docker logs otel-collector 2>&1 | tail -n 1
```

Debe aparecer la línea **Everything is ready. Begin running and processing data.**, que significa que los receptores están en marcha.

<figure><img src="https://docs.f3cloud.com/img/otel-compose-up.png" alt="Comprobación de la configuración de Collector, arranque con docker compose up y la línea Everything is ready en el registro"><figcaption><p>La configuración es correcta, Collector está en marcha y listo para recibir datos</p></figcaption></figure>

Las líneas del registro de Collector son largas, porque al final de cada una hay un bloque de servicio con la descripción del propio Collector. Lo que nos importa es el principio de la línea y los números del final.

***

## Paso 4. Envía trazas de prueba

Para comprobar Collector antes de conectar una aplicación real, usaremos la herramienta **telemetrygen**. Forma parte del proyecto OpenTelemetry y sabe generar trazas, métricas y registros de prueba. La ejecutaremos en un contenedor en la red del servidor y enviaremos tres trazas.

```bash
docker run --rm --network host \
  ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \
  traces --otlp-insecure --traces 3 2>&1 | grep -E "generated|export:"
```

* `--network host` da al contenedor la red del propio servidor, por eso la herramienta ve el puerto `127.0.0.1:4317`.
* `--otlp-insecure` desactiva el cifrado. Nuestro Collector no lo tiene, y sin este parámetro la herramienta intentará conectarse por TLS y recibirá un error.
* `--traces 3` fija el número de trazas. La herramienta crea una traza por segundo, así que tres trazas tardan unos 5 segundos en enviarse.
* El filtro `grep` deja solo la línea final y las líneas con errores de envío. La herramienta es muy habladora.

La línea **traces generated** solo significa que la herramienta ha creado los datos. Si han llegado hasta Collector, lo mostrará su registro.

```bash
docker logs otel-collector 2>&1 | grep Traces | tail -n 2
```

<figure><img src="https://docs.f3cloud.com/img/otel-telemetrygen.png" alt="telemetrygen ha enviado tres trazas, en el registro de Collector hay líneas Traces con dos spans cada una"><figcaption><p>Collector ha recibido las trazas y las ha escrito en el registro</p></figcaption></figure>

En cada línea **Traces** aparece `"spans": 2`. La herramienta crea una traza de dos spans, y Collector recibe las trazas a medida que aparecen e imprime una línea por cada lote.

La misma prueba se puede hacer también por HTTP. Para ello, añade el parámetro `--otlp-http` e indica el puerto 4318.

```bash
docker run --rm --network host \
  ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \
  traces --otlp-http --otlp-insecure --otlp-endpoint localhost:4318 --service telemetrygen-http --traces 2
```

El parámetro `--service` fija el nombre del servicio con el que las trazas aparecerán en Jaeger, así es fácil distinguirlas de las primeras.

***

## Paso 5. Abre Jaeger a través de un túnel SSH

La interfaz web de Jaeger está cerrada a internet, por eso hay que conectarse a ella mediante un túnel SSH.

1. En tu ordenador, abre una ventana nueva del terminal y crea el túnel. Sustituye `IP-del-servidor` por la dirección de tu VPS.

   ```bash
   ssh -L 16686:127.0.0.1:16686 root@IP-del-servidor
   ```

2. Sin cerrar esa ventana, abre en el navegador de tu ordenador la dirección `http://127.0.0.1:16686`.
3. En la lista **Service** elige `telemetrygen` y pulsa <kbd>Find Traces</kbd>.

Se abrirá una página con las trazas encontradas. Hemos enviado tres trazas y las tres se ven en la tabla. Cada una tiene dos spans, y la duración es de unos 123 microsegundos, porque telemetrygen crea por defecto spans muy cortos.

<figure><img src="https://docs.f3cloud.com/img/otel-jaeger-telemetrygen.png" alt="Jaeger muestra tres trazas del servicio telemetrygen, con dos spans cada una"><figcaption><p>Trazas de prueba de telemetrygen en Jaeger</p></figcaption></figure>

Haz clic en el nombre de cualquier traza y se abrirá su línea de tiempo con los spans dentro.

> **Jaeger guarda las trazas solo en memoria.**
>
> En el modo de nuestro archivo Compose, Jaeger no escribe los datos en el disco. Según su documentación, es un almacenamiento temporal en memoria. Lo hemos comprobado reiniciando el contenedor, y después las trazas anteriores desaparecieron. El almacenamiento permanente de trazas no se configura en este artículo, para familiarizarse y depurar basta con la memoria.

***

## Paso 6. Conecta una aplicación en Python

Los datos de prueba sirven para comprobar, pero la utilidad real empieza cuando es tu aplicación la que envía la telemetría. Tomaremos una pequeña aplicación web en Flask y le conectaremos OpenTelemetry sin cambios en la lógica. Como base hemos usado el ejemplo de la guía oficial de OpenTelemetry para Python, al que hemos añadido un span propio y una pequeña pausa para que la traza resulte más clara.

1. Instala el paquete para entornos virtuales de Python. En Debian 12 no viene incluido en la instalación estándar.

   ```bash
   apt install -y python3-venv
   ```

2. Crea la carpeta de la aplicación y el entorno virtual, y después actívalo.

   ```bash
   mkdir -p /opt/otel/app
   cd /opt/otel/app
   python3 -m venv venv
   source venv/bin/activate
   ```

   Al principio de la línea del terminal aparecerá la marca `(venv)`.

3. Instala Flask, OpenTelemetry y el exportador de OTLP. Después ejecuta `opentelemetry-bootstrap`, que encuentra en el entorno las bibliotecas ya instaladas, por ejemplo Flask, y les instala los paquetes de instrumentación.

   ```bash
   pip install flask opentelemetry-distro opentelemetry-exporter-otlp
   opentelemetry-bootstrap -a install
   ```

   En nuestro servidor se instalaron Flask 3.1.3, OpenTelemetry SDK 1.45.1 y opentelemetry-distro 0.66b1.

4. Crea el archivo de la aplicación `app.py`.

   ```bash
   cat > app.py <<'EOF'
   import logging
   import time
   from random import randint

   from flask import Flask, request
   from opentelemetry import trace

   app = Flask(__name__)
   logger = logging.getLogger(__name__)
   tracer = trace.get_tracer("diceroller.tracer")

   @app.route("/rolldice")
   def roll_dice():
       player = request.args.get("player", default=None, type=str)
       result = str(roll())
       if player:
           logger.warning("%s is rolling the dice: %s", player, result)
       else:
           logger.warning("Anonymous player is rolling the dice: %s", result)
       return result

   def roll():
       # Tramo propio de la traza dentro de la petición
       with tracer.start_as_current_span("roll") as span:
           time.sleep(0.3)  # simulación de trabajo
           value = randint(1, 6)
           span.set_attribute("roll.value", value)
           return value
   EOF
   ```

   La aplicación responde en la dirección `/rolldice` con un número aleatorio del 1 al 6. La única referencia a OpenTelemetry que contiene es el span `roll`, que hemos creado nosotros para ver un tramo anidado. Todo lo demás lo hará la instrumentación automática.

5. Inicia la aplicación mediante `opentelemetry-instrument`.

   ```bash
   opentelemetry-instrument --service_name dice-server --logs_exporter otlp flask run -p 8000
   ```

   * `--service_name dice-server` fija el nombre del servicio en Jaeger.
   * `--logs_exporter otlp` activa el envío de registros. Las trazas y las métricas, según la documentación, van por OTLP/gRPC a `localhost:4317` incluso sin este parámetro.
   * `flask run -p 8000` es el comando habitual para iniciar la aplicación. Si el puerto 8000 lo tienes ocupado, elige otro.

   La ventana quedará casi vacía, y es lo normal. Las entradas del registro de la aplicación ahora las recoge OpenTelemetry y las envía a Collector, y en el terminal no se muestran.

6. Abre una segunda ventana de SSH y haz varias peticiones a la aplicación.

   ```bash
   for p in Anna Boris Clara; do curl "http://127.0.0.1:8000/rolldice?player=$p"; echo; done
   ```

   Cada respuesta se retrasa aproximadamente un tercio de segundo por la pausa del código. Pasados unos segundos, comprueba el registro de Collector.

   ```bash
   docker logs otel-collector 2>&1 | grep -E "Traces|Logs" | tail -n 2
   ```

<figure><img src="https://docs.f3cloud.com/img/otel-python-requests.png" alt="Tres peticiones curl a la aplicación y líneas Logs y Traces en el registro de Collector, seis spans"><figcaption><p>Tres peticiones dieron seis spans y entradas del registro</p></figcaption></figure>

Las tres peticiones dieron **seis spans**, es decir, dos por cada una, y las entradas del registro de la aplicación. Collector ha recibido las tres señales.

Ahora vuelve a Jaeger, actualiza la página, en la lista **Service** elige `dice-server` y pulsa <kbd>Find Traces</kbd>. Abre cualquier traza `GET /rolldice` y haz clic en el span anidado `roll`.

<figure><img src="https://docs.f3cloud.com/img/otel-jaeger-dice-trace.png" alt="Traza GET /rolldice en Jaeger con el span anidado roll, el atributo roll.value es igual a 5"><figcaption><p>La petición y el span anidado roll, abajo están sus atributos</p></figcaption></figure>

La barra superior es toda la petición `GET /rolldice`, la ha creado la biblioteca de instrumentación de Flask. Debajo está el span anidado `roll`, que hemos creado a mano. Ocupa casi todo el tiempo de la petición, unos 300 de 301 milisegundos, porque es justo ahí donde está nuestra pausa. En los atributos del span se ve `roll.value` con el número obtenido. Así, en la traza se ve enseguida en qué parte de la petición se va el tiempo.

Detén la aplicación con la combinación <kbd>Ctrl</kbd> + <kbd>C</kbd> en la primera ventana.

> **Envío por HTTP en lugar de gRPC.**
>
> La aplicación se puede cambiar a OTLP/HTTP con una variable de entorno. Añade antes del comando de inicio `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`, y los datos irán al puerto 4318. Si necesitas otra dirección de Collector, la fija la variable `OTEL_EXPORTER_OTLP_ENDPOINT`. Hemos probado las dos formas, y con HTTP el servicio apareció en Jaeger igual que con gRPC.

***

## Paso 7. Añade las métricas del servidor

Por ahora Collector recibe datos de las aplicaciones, pero él mismo no mide nada. Vamos a enseñarle a recoger las métricas del servidor con el receptor **host_metrics**. Sabe tomar la carga del procesador, la memoria, los discos y la red.

Vuelve a la carpeta del proyecto.

```bash
cd /opt/otel
```

1. Abre el archivo de ajustes de Collector.

   ```bash
   nano otel-config.yaml
   ```

   Guarda los cambios con la combinación <kbd>Ctrl</kbd> + <kbd>O</kbd>, luego <kbd>Enter</kbd>, y sal del editor con la combinación <kbd>Ctrl</kbd> + <kbd>X</kbd>.

2. Añade en la sección `receivers` un receptor nuevo justo después de `otlp`, con la misma sangría de dos espacios.

   ```yaml
     # Métricas del propio servidor
     host_metrics:
       root_path: /hostfs
       collection_interval: 10s
       scrapers:
         cpu:
         memory:
         load:
         filesystem:
         network:
   ```

   * `scrapers` enumera qué se va a medir exactamente. Hemos tomado el procesador, la memoria, la carga media, el sistema de archivos y la red. En la documentación del receptor hay otros, por ejemplo discos y procesos.
   * `collection_interval` fija la frecuencia de las mediciones. Por defecto es de un minuto, y nosotros hemos puesto 10 segundos para ver antes el resultado.
   * `root_path` hace falta porque Collector funciona en un contenedor. Para que vea el servidor y no su propio contenedor, la raíz del servidor se monta en la carpeta `/hostfs`, y en `root_path` se indica esa carpeta.

3. En el mismo archivo, añade el nuevo receptor a la canalización de métricas.

   ```yaml
       metrics:
         receivers: [otlp, host_metrics]
   ```

4. Cambia el modo del exportador `debug` para que en el registro aparezcan no solo los contadores, sino también los valores. Sustituye en la sección `exporters` la línea `verbosity: basic` por `verbosity: detailed` y guarda el archivo.

5. Abre el archivo `compose.yaml` y monta la raíz del servidor en modo lectura en la sección `volumes` del servicio `collector`.

   ```bash
   nano compose.yaml
   ```

   ```yaml
       volumes:
         - ./otel-config.yaml:/etc/otelcol-contrib/config.yaml:ro
         - /:/hostfs:ro                 # sistema de archivos del servidor para las métricas del host
   ```

6. Comprueba la configuración y vuelve a crear el contenedor de Collector.

   ```bash
   docker compose run --rm --no-deps collector validate --config=/etc/otelcol-contrib/config.yaml
   docker compose up -d
   ```

Espera unos 20 segundos a que Collector haga las primeras mediciones y mira qué métricas ha recogido.

```bash
docker logs otel-collector --since 12s 2>&1 | grep "Name: system" | sort -u
```

Para asegurarnos de que las cifras son reales, tomaremos del registro el valor de la memoria ocupada y lo compararemos con el comando `free`.

```bash
docker logs otel-collector --since 12s 2>&1 | grep -A12 system.memory.usage | grep -E "state|Value" | head -2
free -b | head -2
```

<figure><img src="https://docs.f3cloud.com/img/otel-hostmetrics.png" alt="Métricas system.cpu, system.memory, system.filesystem y system.network en el registro de Collector y la salida de free para comparar"><figcaption><p>Collector recoge las métricas del servidor, y la memoria ocupada coincide con la salida de free</p></figcaption></figure>

Collector ha mostrado 13 métricas con nombres que empiezan por `system.`. Por ejemplo, `system.cpu.load_average.1m` es la carga media del último minuto, `system.memory.usage` es la memoria en bytes por estados, `system.filesystem.usage` es el espacio ocupado en los discos y `system.network.io` es el volumen de datos transmitidos. La memoria ocupada de la métrica se acerca a la columna `used` de la salida de `free`, y la pequeña diferencia se debe a que los valores se tomaron en momentos distintos.

> **Vuelve al modo basic.**
>
> El modo detallado escribe en el registro unos 90 KB cada minuto, es decir, unos 130 MB al día. Cuando hayas mirado lo suficiente, devuelve en `otel-config.yaml` la línea `verbosity: basic` y reinicia Collector con el comando `docker compose restart collector`.

Por ahora las métricas solo llegan al registro. Para guardarlas y dibujar gráficos hay que enviarlas a un almacén de métricas, y Collector tiene para ello exportadores, por ejemplo `prometheus`. Es tema de un artículo aparte.

***

## Comandos útiles

```bash
docker compose ps                        # estado de los contenedores y puertos
docker compose logs -f collector         # registro de Collector en tiempo real
docker compose restart collector         # volver a leer el archivo de ajustes
docker compose run --rm --no-deps collector validate --config=/etc/otelcol-contrib/config.yaml   # comprobar los ajustes
docker compose down                      # detener y eliminar los contenedores
```

Después de detenerlo con el comando `docker compose down`, las trazas de Jaeger desaparecerán, ya que se guardan solo en memoria.

***

## Preguntas frecuentes

<details>

<summary>¿Hay que abrir los puertos 4317, 4318 y 16686 en UFW?</summary>

No. Las aplicaciones de este mismo servidor se conectan a `127.0.0.1`, y Jaeger lo abrimos a través de un túnel SSH. Los puertos en Compose están publicados solo en `127.0.0.1`, y hemos comprobado desde otro ordenador que en la dirección externa del servidor la conexión con ellos no se establece. No sustituyas `127.0.0.1:4317:4317` por `4317:4317` sin necesidad. El cortafuegos UFW no cierra los puertos de Docker, y Collector sin contraseñas quedaría abierto a todo internet.

</details>

<details>

<summary>La aplicación funciona, pero no hay nada en Collector ni en Jaeger, y en el registro aparece Failed to auto initialize OpenTelemetry</summary>

No está instalado el exportador de OTLP. Ejecutamos la aplicación solo con `opentelemetry-distro` y obtuvimos estas líneas.

```text
RuntimeError: Requested component 'otlp_proto_grpc' not found in entry point 'opentelemetry_traces_exporter'
Failed to auto initialize OpenTelemetry
```

La propia aplicación sigue respondiendo a las peticiones, por eso el error pasa fácilmente desapercibido. Detén la aplicación, ejecuta `pip install opentelemetry-exporter-otlp` e inicia de nuevo.

</details>

<details>

<summary>Jaeger está vacío y en el terminal de la aplicación no hay ningún error</summary>

Los errores de envío no se muestran en el terminal, porque las entradas del registro, incluidos los mensajes del propio OpenTelemetry, las recoge la instrumentación automática. Hemos comprobado que con el parámetro `--logs_exporter console` esas mismas entradas, junto con los errores de exportación, aparecen en la pantalla. Inicia la aplicación así.

```bash
opentelemetry-instrument --service_name dice-server --logs_exporter console flask run -p 8000
```

En la consola empezarán a mostrarse tanto las entradas de la propia aplicación como los mensajes de errores de exportación, de los que se ven ejemplos en las dos preguntas siguientes.

</details>

<details>

<summary>Failed to export traces to localhost:4318 … Trying to connect an http1.x server</summary>

La aplicación envía los datos por gRPC, pero la dirección apunta al puerto HTTP. Reprodujimos el error iniciando la aplicación con la dirección de Collector `http://localhost:4318` mediante la variable `OTEL_EXPORTER_OTLP_ENDPOINT`.

```text
Failed to export traces to localhost:4318, error code: StatusCode.UNAVAILABLE, error details: failed to connect to all addresses; last error: INTERNAL: ipv4:127.0.0.1:4318: Failed parsing HTTP/2 (Expected SETTINGS frame as the first frame, got frame type 80) (Trying to connect an http1.x server (HTTP status 400))
```

Hay dos salidas. O devuelves el puerto de gRPC `4317`, o cambias la aplicación a HTTP con la variable `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`. En la herramienta telemetrygen, el mismo error se ve como `http2: frame too large, note that the frame header looked like an HTTP/1.1 header`.

</details>

<details>

<summary>BadStatusLine … encountered while exporting spans batch, malformed HTTP response</summary>

Es la situación inversa. La aplicación o la herramienta envían los datos por HTTP, pero la dirección apunta al puerto de gRPC. Iniciamos la aplicación con las variables `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf` y `OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317`.

```text
Transient error ('Connection aborted.', BadStatusLine('\x00\x00\x06\x04\x00\x00\x00\x00\x00\x00\x05\x00\x00@\x00')) encountered while exporting spans batch, retrying in 0.82s.
Failed to export spans batch due to timeout, max retries or shutdown.
```

Recuerda la correspondencia. El puerto 4317 acepta solo gRPC y el puerto 4318, solo HTTP. Para HTTP no olvides tampoco el `--otlp-http` de telemetrygen.

</details>

<details>

<summary>transport: Error while dialing … connect: connection refused</summary>

Collector no está en marcha o la dirección está mal indicada. Obtuvimos este error al enviar trazas al puerto 4319, en el que no escucha nadie.

```text
traces export: exporter export timeout: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 127.0.0.1:4319: connect: connection refused"
```

Comprueba con el comando `docker compose ps` que el contenedor funciona, y que los puertos están a la escucha en el servidor.

```bash
ss -tln | grep -E ":(4317|4318|16686) "
```

En la salida deben aparecer tres líneas con la dirección `127.0.0.1`.

</details>

<details>

<summary>tls: first record does not look like a TLS handshake</summary>

La herramienta telemetrygen se conecta por defecto mediante TLS, y nuestro Collector recibe los datos sin cifrado. Obtuvimos este error al ejecutar la herramienta sin el parámetro `--otlp-insecure`.

```text
transport: authentication handshake failed: tls: first record does not look like a TLS handshake
```

Añade el parámetro `--otlp-insecure`.

</details>

<details>

<summary>error reading server preface: connection reset by peer</summary>

El puerto está publicado, pero Collector dentro del contenedor escucha en una dirección que no es la correcta. Pasa si en el receptor `otlp` no está indicado `endpoint`. Comprobamos la configuración con `grpc:` y `http:` vacíos, y en el registro de Collector aparecieron estas direcciones.

```text
"endpoint": "127.0.0.1:4317"
"endpoint": "127.0.0.1:4318"
```

En ese caso, telemetrygen responde con un error.

```text
traces export: exporter export timeout: rpc error: code = Unavailable desc = connection error: desc = "error reading server preface: connection reset by peer"
```

Indica en la configuración `endpoint: 0.0.0.0:4317` para gRPC y `endpoint: 0.0.0.0:4318` para HTTP, como en el paso 2, y reinicia Collector.

</details>

<details>

<summary>references exporter "jaeger" which is not configured</summary>

En la canalización se indica un componente que no existe en su sección de la configuración. Obtuvimos este error al escribir en la canalización de trazas `jaeger` en lugar de `otlp_grpc/jaeger`.

```text
Error: service::pipelines::traces: references exporter "jaeger" which is not configured
```

El nombre en la sección `service` debe coincidir con el nombre en `exporters`, incluida la parte después de la barra. Collector también se queja del mismo modo de los receptores y procesadores sin describir.

</details>

<details>

<summary>receivers::host_metrics: invalid root_path: stat /hostfs: no such file or directory</summary>

En `compose.yaml` no está montada la raíz del servidor en la carpeta `/hostfs`, y en los ajustes del receptor `host_metrics` esa carpeta sí está indicada.

```text
Error: receivers::host_metrics: invalid root_path: stat /hostfs: no such file or directory
```

Añade en la sección `volumes` la línea `- /:/hostfs:ro`, como en el paso 7.

</details>

***

## Nuestros recursos

**Canal de Telegram:** [https://t.me/f3cloud_news](https://t.me/f3cloud_news)\
**Web del hosting:** [https://f3cloud.com](https://f3cloud.com/es)
