Introducción
En este minilab aprenderás a implementar trazabilidad distribuida (distributed tracing) usando OpenTelemetry con Dynatrace como backend de observabilidad. Crearemos una aplicación PHP instrumentada que envía trazas personalizadas a través del OpenTelemetry Collector hacia Dynatrace.
¿Qué es OpenTelemetry?
OpenTelemetry (OTel) es un estándar abierto y vendor-neutral para la observabilidad de aplicaciones. Permite instrumentar, generar, recolectar y exportar datos de telemetría (trazas, métricas y logs) sin depender de un proveedor específico.
¿Por qué usar OpenTelemetry con Dynatrace?
- Portabilidad: Código independiente del vendor
- Flexibilidad: Puedes cambiar de backend sin modificar tu aplicación
- Estándar de industria: Soportado por CNCF (Cloud Native Computing Foundation)
- Integración nativa: Dynatrace soporta OTLP (OpenTelemetry Protocol) de forma nativa
Arquitectura del minilab
┌─────────────────┐
│ Navegador │
│ (Cliente) │
└────────┬────────┘
│ HTTP
▼
┌─────────────────┐
│ Nginx (Puerto │
│ 80) │
└────────┬────────┘
│
▼
┌─────────────────┐ OTLP/HTTP ┌──────────────────┐
│ PHP 8.2-FPM │─────────────────────│ OpenTelemetry │
│ + OpenTelemetry│ Port 4318 │ Collector │
└─────────────────┘ └─────────┬────────┘
│
│ OTLP + API Token
▼
┌──────────────────┐
│ Dynatrace │
│ (SaaS Tenant) │
└──────────────────┘
Requisitos previos
Antes de comenzar, asegúrate de tener:
✅ VirtualBox con Ubuntu Server 24.04 instalado
✅ Docker y Docker Compose instalados
✅ Acceso SSH a tu servidor Ubuntu
✅ Cuenta de Dynatrace (trial de 15 días gratuito)
✅ Conexión a internet desde el servidor
Fase 1: Preparar Dynatrace
Paso 1.1: Crear Access Token
- Accede a tu tenant de Dynatrace
- Ve a Apps → Manage → Access tokens
- Click en Generate new token
- Configura el token:
- Nombre:
opentelemetry-lab - Permisos necesarios:
- ✅
metrics.ingest– Ingest metrics - ✅
logs.ingest– Ingest logs - ✅
openTelemetryTrace.ingest– Ingest OpenTelemetry traces
- ✅
- Nombre:
- Genera el token y cópialo (solo se muestra una vez)
Paso 1.2: Anotar el endpoint OTLP
Tu endpoint OTLP de Dynatrace tiene este formato:
https://{tu-environment-id}.live.dynatrace.com/api/v2/otlp
Por ejemplo: https://abc12345.live.dynatrace.com/api/v2/otlp
Anota tu environment ID y el token para usarlos más adelante.
Fase 2: Instalar OpenTelemetry Collector
El OpenTelemetry Collector es un componente que recibe, procesa y exporta datos de telemetría. Lo instalaremos directamente en el host (fuera de Docker).
Paso 2.1: Conectar al servidor
ssh usuario@ip-de-tu-servidor
Paso 2.2: Descargar e instalar el Collector
# Crear directorio para el Collector
sudo mkdir -p /opt/otelcol
# Descargar el OpenTelemetry Collector Contrib
cd /opt/otelcol
sudo wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.115.1/otelcol-contrib_0.115.1_linux_amd64.tar.gz
# Descomprimir
sudo tar -xvzf otelcol-contrib_0.115.1_linux_amd64.tar.gz
# Dar permisos de ejecución
sudo chmod +x otelcol-contrib
Paso 2.3: Crear archivo de configuración
Crea el archivo de configuración del Collector:
sudo nano /opt/otelcol/config.yaml
Pega este contenido (reemplaza {TU_ENVIRONMENT_ID} y {TU_TOKEN}):
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 10s
send_batch_size: 1024
exporters:
otlphttp:
endpoint: https://{TU_ENVIRONMENT_ID}.live.dynatrace.com/api/v2/otlp
headers:
Authorization: "Api-Token {TU_TOKEN}"
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp, debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp, debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp, debug]
Ejemplo real:
exporters:
otlphttp:
endpoint: https://jdn3490734.live.dynatrace.com/api/v2/otlp
headers:
Authorization: "Api-Token dt0c01.BCY2IAH34T7VMZF2OLJN7NBT.UJNENUKNO5SEUHNDCBZMPBUP2HTOXOS3QHAA3DGEEELC5EDUHN"
Guarda el archivo: Ctrl + O, Enter, Ctrl + X
Paso 2.4: Ejecutar el Collector
Inicia el Collector en una terminal:
cd /opt/otelcol
sudo ./otelcol-contrib --config=config.yaml
Deberías ver:
2025-12-25T15:13:59.398Z info service@v0.115.0/service.go:166 Setting up own telemetry...
2025-12-25T15:13:59.404Z info service@v0.115.0/service.go:238 Starting otelcol-contrib...
2025-12-25T15:13:59.404Z info extensions/extensions.go:39 Starting extensions...
2025-12-25T15:13:59.406Z info service@v0.115.0/service.go:261 Everything is ready. Begin running and processing data.
Deja esta terminal abierta con el Collector corriendo. Abre una nueva terminal SSH para continuar con los siguientes pasos.
Fase 3: Configurar la aplicación PHP con OpenTelemetry
Paso 3.1: Crear la estructura del proyecto
En la nueva terminal:
# Crear directorio del proyecto
mkdir -p ~/lab-dynatrace/html
cd ~/lab-dynatrace
Paso 3.2: Crear Dockerfile para PHP
nano Dockerfile.php
Contenido:
FROM php:8.2-fpm
# Instalar dependencias necesarias
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
&& docker-php-ext-install zip
# Instalar la extensión OpenTelemetry para PHP
RUN pecl install opentelemetry-1.1.0 \
&& docker-php-ext-enable opentelemetry
# Instalar Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Directorio de trabajo
WORKDIR /var/www/html
CMD ["php-fpm"]
Paso 3.3: Crear composer.json
nano composer.json
Contenido:
{
"require": {
"open-telemetry/sdk": "^1.0",
"open-telemetry/exporter-otlp": "^1.0",
"open-telemetry/opentelemetry-auto-slim": "^1.0"
}
}
Paso 3.4: Crear docker-compose.yml
nano docker-compose.yml
Contenido:
version: '3'
services:
web:
image: nginx:latest
container_name: lab-dynatrace_web_1
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./html:/usr/share/nginx/html
depends_on:
- app
networks:
- dynatrace-net
app:
build:
context: .
dockerfile: Dockerfile.php
container_name: lab-dynatrace_app_1
volumes:
- ./html:/var/www/html
environment:
- OTEL_PHP_AUTOLOAD_ENABLED=true
- OTEL_SERVICE_NAME=lab-dynatrace-php
- OTEL_TRACES_EXPORTER=otlp
- OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
- OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4318
- OTEL_PROPAGATORS=tracecontext,baggage
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- dynatrace-net
networks:
dynatrace-net:
driver: bridge
Paso 3.5: Crear configuración de Nginx
nano nginx.conf
Contenido:
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Paso 3.6: Crear la aplicación PHP con instrumentación OpenTelemetry
nano html/index.php
Contenido completo:
<?php
require_once '/var/www/html/vendor/autoload.php';
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
// Obtener el tracer
$tracer = Globals::tracerProvider()->getTracer('lab-dynatrace-php');
// Crear un span para la petición principal
$rootSpan = $tracer->spanBuilder('http_request')
->setSpanKind(SpanKind::KIND_SERVER)
->setAttribute('http.method', $_SERVER['REQUEST_METHOD'])
->setAttribute('http.url', $_SERVER['REQUEST_URI'])
->setAttribute('http.host', $_SERVER['HTTP_HOST'])
->startSpan();
$scope = $rootSpan->activate();
try {
// Simular una operación de base de datos
$dbSpan = $tracer->spanBuilder('database_query')
->setSpanKind(SpanKind::KIND_CLIENT)
->setAttribute('db.system', 'mysql')
->setAttribute('db.operation', 'SELECT')
->startSpan();
usleep(50000); // Simular 50ms de latencia
$serverTime = date('Y-m-d H:i:s');
$dbSpan->setStatus(StatusCode::STATUS_OK);
$dbSpan->end();
// Simular procesamiento de negocio
$businessSpan = $tracer->spanBuilder('business_logic')
->setAttribute('operation', 'calculate_server_status')
->startSpan();
usleep(30000); // Simular 30ms de procesamiento
$phpVersion = phpversion();
$businessSpan->addEvent('calculation_complete', [
'result' => 'success',
'php_version' => $phpVersion
]);
$businessSpan->end();
$rootSpan->setStatus(StatusCode::STATUS_OK);
} catch (Exception $e) {
$rootSpan->recordException($e);
$rootSpan->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
} finally {
$scope->detach();
$rootSpan->end();
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laboratorio Dynatrace + OpenTelemetry</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #1496ff;
}
.badge {
display: inline-block;
background: #4caf50;
color: white;
padding: 5px 10px;
border-radius: 3px;
font-size: 12px;
margin-left: 10px;
}
button {
background-color: #1496ff;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
margin: 10px 5px;
}
button:hover {
background-color: #0d7cd6;
}
.status {
margin-top: 20px;
padding: 15px;
background-color: #e8f5e9;
border-left: 4px solid #4caf50;
border-radius: 4px;
}
.otel-info {
margin-top: 20px;
padding: 15px;
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Laboratorio Dynatrace - Sistema Activo<span class="badge">OpenTelemetry</span></h1>
<div class="status">
<strong>✅ Estado del Backend:</strong> PHP <?php echo $phpVersion; ?> funcionando correctamente
</div>
<div class="otel-info">
<strong>📊 OpenTelemetry Activo:</strong> Esta página está enviando trazas distribuidas a Dynatrace
</div>
<p>Este es un laboratorio de pruebas para explorar las capacidades de monitorización Full-Stack de Dynatrace.</p>
<p><strong>Tiempo del servidor:</strong> <?php echo $serverTime; ?></p>
<h3>Prueba de Real User Monitoring (RUM)</h3>
<p>Haz clic en el botón para generar un error JavaScript que será capturado por Dynatrace:</p>
<button id="btnError">⚠️ Generar Error JavaScript</button>
<h3>Prueba de OpenTelemetry</h3>
<p>Genera tráfico para ver las trazas en Dynatrace:</p>
<button id="btnTrace">🔍 Generar Trazas OTLP</button>
<script>
document.getElementById('btnError').addEventListener('click', function() {
console.error("Error forzado para pruebas de RUM en Dynatrace");
throw new Error("Error de prueba generado desde el laboratorio Dynatrace");
});
document.getElementById('btnTrace').addEventListener('click', function() {
// Recargar la página para generar nuevas trazas
for(let i = 0; i < 5; i++) {
fetch(window.location.href)
.then(response => console.log('Traza generada ' + (i+1)))
.catch(error => console.error('Error:', error));
}
alert('5 peticiones enviadas. Revisa Dynatrace en unos minutos para ver las trazas distribuidas.');
});
</script>
</div>
</body>
</html>
Paso 3.7: Construir y levantar los contenedores
# Construir la imagen de PHP con OpenTelemetry
docker-compose build
# Levantar los servicios
docker-compose up -d
Este proceso puede tardar unos minutos la primera vez.
Paso 3.8: Instalar dependencias de Composer dentro del contenedor
# Entrar al contenedor
docker exec -it lab-dynatrace_app_1 bash
# Dentro del contenedor, crear composer.json
cat > /var/www/html/composer.json << 'EOF'
{
"require": {
"open-telemetry/sdk": "^1.0",
"open-telemetry/exporter-otlp": "^1.0",
"open-telemetry/opentelemetry-auto-slim": "^1.0"
}
}
EOF
# Instalar dependencias
cd /var/www/html
composer install --no-dev --optimize-autoloader
# Salir del contenedor
exit
Paso 3.9: Reiniciar el contenedor
docker-compose restart app
Fase 4: Verificar y validar
Paso 4.1: Verificar que los contenedores están corriendo
docker-compose ps
Deberías ver ambos contenedores en estado «Up»:
Name Command State Ports
----------------------------------------------------------------------------------
lab-dynatrace_app_1 docker-php-entrypoint php-fpm Up 9000/tcp
lab-dynatrace_web_1 /docker-entrypoint.sh ngin... Up 0.0.0.0:80->80/tcp
Paso 4.2: Verificar logs de PHP
docker-compose logs -f app
Deberías ver:
app_1 | [25-Dec-2025 15:50:38] NOTICE: fpm is running, pid 1
app_1 | [25-Dec-2025 15:50:38] NOTICE: ready to handle connections
Sin errores de OpenTelemetry. Presiona Ctrl + C para salir.
Paso 4.3: Acceder a la aplicación
Abre tu navegador y accede a:
http://localhost:8080
Deberías ver la página del laboratorio con el badge «OpenTelemetry».

Paso 4.4: Generar tráfico
Haz clic varias veces en el botón «🔍 Generar Trazas OTLP» para generar peticiones.
Paso 4.5: Verificar el Collector
Ve a la terminal donde está corriendo el OpenTelemetry Collector. Deberías empezar a ver logs detallados mostrando las trazas que está recibiendo y exportando:
ScopeSpans #0
ScopeSpans SchemaURL:
InstrumentationScope lab-dynatrace-php
Span #0
Trace ID : f96608e0b9c26fd2b5c747b7e2556b81
Parent ID : c9e90f32737efcce
ID : 35c5c642bcecfd00
Name : database_query
Kind : Client
Start time : 2025-12-25 15:54:27.137092923 +0000 UTC
End time : 2025-12-25 15:54:27.187654329 +0000 UTC
Status code : Ok
Attributes:
-> db.system: Str(mysql)
-> db.operation: Str(SELECT)
Span #1
Trace ID : f96608e0b9c26fd2b5c747b7e2556b81
Parent ID : c9e90f32737efcce
ID : 2234cf58edd677094
Name : business_logic
Kind : Internal
...
Esto confirma que: ✅ La aplicación PHP está generando trazas ✅ El Collector las está recibiendo ✅ El Collector las está exportando a Dynatrace
Paso 4.6: Verificar en Dynatrace
- Accede a tu tenant de Dynatrace
- Ve a Services → Services Classic (en el menú de Application Observability)
- Busca el servicio
lab-dynatrace-php - Haz clic en él

Deberías ver:
Service Metrics:
- Response time: ~81-92 ms
- Throughput: peticiones por minuto
- Failed requests: 0%
Distributed Traces:
- Lista de trazas
http_request - Haz clic en cualquier traza


Trace Detail: Verás la traza distribuida completa con los 3 spans:
http_request– El span principal (servidor)database_query– La consulta simulada a MySQLbusiness_logic– El procesamiento de negocio

Cada span muestra:
- Duración
- Atributos personalizados (db.system, db.operation, etc.)
- Eventos (calculation_complete)
- Timeline visual
Explicación de los conceptos
¿Qué es un Span?
Un span representa una unidad de trabajo en un sistema distribuido. Contiene:
- Nombre de la operación
- Timestamp de inicio y fin
- Atributos (metadata)
- Eventos
- Relación con otros spans (parent/child)
¿Qué es un Trace?
Un trace (traza) es una colección de spans que representan el flujo completo de una petición a través de un sistema distribuido.
Tipos de Spans que creamos
// 1. Span de tipo SERVER (entrada HTTP)
$rootSpan = $tracer->spanBuilder('http_request')
->setSpanKind(SpanKind::KIND_SERVER)
->setAttribute('http.method', 'GET')
->startSpan();
// 2. Span de tipo CLIENT (llamada externa - BD)
$dbSpan = $tracer->spanBuilder('database_query')
->setSpanKind(SpanKind::KIND_CLIENT)
->setAttribute('db.system', 'mysql')
->startSpan();
// 3. Span INTERNAL (procesamiento interno)
$businessSpan = $tracer->spanBuilder('business_logic')
->setAttribute('operation', 'calculate_server_status')
->startSpan();
Flujo de datos
┌──────────────┐
│ PHP App │ 1. Genera spans con OpenTelemetry SDK
└──────┬───────┘
│ OTLP/HTTP (puerto 4318)
▼
┌──────────────┐
│ Collector │ 2. Recibe, procesa (batch), exporta
└──────┬───────┘
│ OTLP/HTTP + API Token
▼
┌──────────────┐
│ Dynatrace │ 3. Almacena y visualiza
└──────────────┘
Conceptos clave de OpenTelemetry
1. Tracer Provider
Es la fábrica que crea tracers. En nuestro código:
$tracer = Globals::tracerProvider()->getTracer('lab-dynatrace-php');
2. Context Propagation
OpenTelemetry propaga el contexto (trace ID, span ID) automáticamente entre componentes para mantener la trazabilidad.
3. Exporters
Componentes que envían la telemetría a backends. Usamos:
- OTLP HTTP Exporter: Para enviar a Dynatrace
- Debug Exporter: Para ver logs en consola
4. Processors
Transforman o filtran los datos antes de exportarlos. Usamos:
- Batch Processor: Agrupa múltiples spans antes de enviarlos (más eficiente)
Ventajas de este enfoque
✅ Vendor Neutral
El código de instrumentación es estándar OpenTelemetry. Puedes cambiar de Dynatrace a Jaeger, Zipkin, o cualquier otro backend compatible simplemente modificando la configuración del Collector.
✅ Separation of Concerns
- Aplicación: Solo se preocupa de generar telemetría estándar
- Collector: Maneja el enrutamiento, procesamiento y exportación
- Backend: Visualiza y analiza
✅ Flexibilidad
El Collector puede:
- Filtrar datos sensibles
- Enriquecer con metadata adicional
- Enviar a múltiples backends simultáneamente
- Aplicar sampling (muestreo)
✅ Control de costes
Puedes controlar qué datos envías y a dónde, optimizando costes de ingesta.
Troubleshooting
Problema: Errores «Transport factory not defined for protocol: grpc»
Solución: Asegúrate de usar http/protobuf en lugar de grpc:
- OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
- OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4318
Problema: No veo trazas en Dynatrace
Verificar:
- El Collector está corriendo y no muestra errores
- El token tiene los permisos correctos
- El endpoint de Dynatrace es correcto
- Hay tráfico generado (recargar la página varias veces)
# Ver logs del Collector
# Deberías ver "ScopeSpans" en los logs
# Ver logs de PHP
docker-compose logs app
# No debe haber errores de OpenTelemetry
Problema: vendor/autoload.php not found
Solución: Instalar dependencias de Composer dentro del contenedor:
docker exec -it lab-dynatrace_app_1 bash
cd /var/www/html
composer install
exit
docker-compose restart app
Limpieza (Cleanup)
Para detener y eliminar todos los recursos:
# Detener contenedores
docker-compose down
# Eliminar imágenes (opcional)
docker-compose down --rmi all
# Detener el Collector
# En la terminal del Collector: Ctrl + C
Recursos adicionales
- Documentación oficial de OpenTelemetry
- Dynatrace OpenTelemetry
- OpenTelemetry PHP
- Collector Configuration
Conclusiones
En este minilab has aprendido a:
✅ Instalar y configurar el OpenTelemetry Collector
✅ Instrumentar una aplicación PHP con OpenTelemetry SDK
✅ Crear trazas personalizadas (custom spans) con atributos y eventos
✅ Enviar telemetría a Dynatrace usando OTLP
✅ Visualizar trazas distribuidas en Dynatrace
✅ Entender la arquitectura de observabilidad con OpenTelemetry
Este conocimiento es fundamental para:
- Examen Dynatrace Associate: OpenTelemetry es parte del temario oficial
- Observabilidad moderna: Estándar de industria adoptado por empresas cloud-native
- Troubleshooting: Identificar cuellos de botella y problemas de rendimiento en sistemas distribuidos
¡Felicidades por completar el minilab!
Autor: Observa Sistemas
Fecha: Diciembre 2025
Versión: 1.0
Tags: #Dynatrace #OpenTelemetry #ObservabilityAsCode #DistributedTracing #PHP #Docker