OpenTelemetry
Versiones compatibles: OTEL 1.x Última actualización: July 13, 2026
Introducción
OpenTelemetry (OTel) es un framework de observabilidad para software cloud-native. Proporciona estándares neutrales respecto al proveedor para generar, recopilar y gestionar tres señales: Traces, Metrics y Logs. Como el segundo proyecto más activo de CNCF, se ha convertido en el estándar del sector.
¿Qué es OpenTelemetry?
OpenTelemetry nació de la fusión de los proyectos OpenTracing y OpenCensus:
Conceptos fundamentales
Tres señales
| Señal | Descripción | Casos de uso |
|---|---|---|
| Traces | Trazado distribuido de solicitudes | Análisis de latencia, mapeo de dependencias |
| Metrics | Mediciones numéricas | Uso de recursos, SLI/SLO |
| Logs | Registros de eventos | Depuración, auditoría |
Componentes principales
SDK de OpenTelemetry
Autoinstrumentación
Añade instrumentación automáticamente sin cambios en el código.
Autoinstrumentación de Java
# Download Java Agent
curl -L -o opentelemetry-javaagent.jar \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
containers:
- name: app
image: order-service:latest
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/opt/opentelemetry-javaagent.jar"
- name: OTEL_SERVICE_NAME
value: "order-service"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector:4317"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "grpc"
- name: OTEL_TRACES_SAMPLER
value: "parentbased_traceidratio"
- name: OTEL_TRACES_SAMPLER_ARG
value: "0.1"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.namespace=ecommerce,deployment.environment=production"
volumeMounts:
- name: otel-agent
mountPath: /opt/opentelemetry-javaagent.jar
subPath: opentelemetry-javaagent.jar
volumes:
- name: otel-agent
configMap:
name: otel-java-agentAutoinstrumentación de Python
# Installation
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
template:
spec:
containers:
- name: app
image: payment-service:latest
command:
- opentelemetry-instrument
- python
- app.py
env:
- name: OTEL_SERVICE_NAME
value: "payment-service"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector:4317"
- name: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED
value: "true"Autoinstrumentación de Node.js
// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');
const sdk = new NodeSDK({
serviceName: 'notification-service',
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4317',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://otel-collector:4317',
}),
exportIntervalMillis: 60000,
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false },
'@opentelemetry/instrumentation-http': {
ignoreIncomingRequestHook: (req) => req.url === '/health',
},
}),
],
});
sdk.start();
process.on('SIGTERM', () => {
sdk.shutdown().then(() => process.exit(0));
});Instrumentación manual
Para un control detallado, instrumenta manualmente.
Instrumentación manual de Java
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
@Service
public class OrderService {
private final Tracer tracer;
public OrderService(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer("order-service", "1.0.0");
}
public Order processOrder(OrderRequest request) {
// Create parent span
Span parentSpan = tracer.spanBuilder("processOrder")
.setSpanKind(SpanKind.SERVER)
.setAttribute("order.id", request.getOrderId())
.setAttribute("customer.id", request.getCustomerId())
.startSpan();
try (Scope scope = parentSpan.makeCurrent()) {
// Business logic
parentSpan.addEvent("Order validation started");
// Child span - inventory check
Order order = checkInventory(request);
// Child span - payment processing
processPayment(order);
parentSpan.addEvent("Order processing completed");
parentSpan.setStatus(StatusCode.OK);
return order;
} catch (Exception e) {
parentSpan.setStatus(StatusCode.ERROR, e.getMessage());
parentSpan.recordException(e);
throw e;
} finally {
parentSpan.end();
}
}
private Order checkInventory(OrderRequest request) {
Span span = tracer.spanBuilder("checkInventory")
.setSpanKind(SpanKind.INTERNAL)
.startSpan();
try (Scope scope = span.makeCurrent()) {
span.setAttribute("product.count", request.getItems().size());
// Inventory check logic
Order order = inventoryService.check(request);
span.setAttribute("inventory.available", true);
return order;
} catch (InsufficientStockException e) {
span.setAttribute("inventory.available", false);
span.setStatus(StatusCode.ERROR, "Insufficient stock");
throw e;
} finally {
span.end();
}
}
}Collector de OTEL
Arquitectura
Configuración del Collector
# otel-collector-config.yaml
receivers:
# OTLP receiver
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
max_recv_msg_size_mib: 16
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- "http://*"
- "https://*"
# Jaeger compatibility
jaeger:
protocols:
thrift_http:
endpoint: 0.0.0.0:14268
grpc:
endpoint: 0.0.0.0:14250
# Zipkin compatibility
zipkin:
endpoint: 0.0.0.0:9411
processors:
# Memory limiter
memory_limiter:
check_interval: 1s
limit_mib: 1500
spike_limit_mib: 500
# Batch processing
batch:
timeout: 5s
send_batch_size: 1000
send_batch_max_size: 1500
# Add/modify attributes
attributes:
actions:
- key: environment
value: production
action: upsert
- key: cluster
value: eks-prod-cluster
action: upsert
# Resource detection
resourcedetection:
detectors: [env, system, ec2, eks]
timeout: 5s
override: false
# Filtering (exclude health checks)
filter:
error_mode: ignore
traces:
span:
- 'attributes["http.target"] == "/health"'
- 'attributes["http.target"] == "/ready"'
- 'attributes["http.target"] == "/metrics"'
# Tail Sampling (for traces)
tail_sampling:
decision_wait: 10s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
# 100% collection for error requests
- name: error-policy
type: status_code
status_code:
status_codes: [ERROR]
# Collect slow requests
- name: latency-policy
type: latency
latency:
threshold_ms: 1000
# Priority collection for specific services
- name: service-priority
type: string_attribute
string_attribute:
key: service.name
values: [payment-service, order-service]
enabled_regex_matching: false
# Probabilistic sampling for the rest
- name: probabilistic-policy
type: probabilistic
probabilistic:
sampling_percentage: 10
exporters:
# OTLP (Tempo)
otlp/tempo:
endpoint: tempo-distributor.tempo.svc.cluster.local:4317
tls:
insecure: true
# AWS X-Ray
awsxray:
region: ap-northeast-2
index_all_attributes: true
# Prometheus Remote Write
prometheusremotewrite:
endpoint: http://prometheus:9090/api/v1/write
tls:
insecure: true
# Loki (logs)
loki:
endpoint: http://loki-gateway.loki.svc.cluster.local:3100/loki/api/v1/push
tls:
insecure: true
labels:
attributes:
service.name: "service"
service.namespace: "namespace"
k8s.pod.name: "pod"
extensions:
health_check:
endpoint: 0.0.0.0:13133
path: /health
pprof:
endpoint: 0.0.0.0:1777
service:
extensions: [health_check, pprof]
pipelines:
traces:
receivers: [otlp, jaeger, zipkin]
processors: [memory_limiter, resourcedetection, attributes, filter, tail_sampling, batch]
exporters: [otlp/tempo, awsxray]
metrics:
receivers: [otlp]
processors: [memory_limiter, resourcedetection, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [memory_limiter, resourcedetection, batch]
exporters: [loki]Patrones de despliegue en EKS
Patrón DaemonSet
Despliega el Collector en cada nodo para recopilar datos de todos los Pods de ese nodo:
# otel-collector-daemonset.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: otel-collector
namespace: otel
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/otel-collector-role
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-collector
namespace: otel
spec:
selector:
matchLabels:
app: otel-collector
template:
metadata:
labels:
app: otel-collector
spec:
serviceAccountName: otel-collector
containers:
- name: collector
image: otel/opentelemetry-collector-contrib:0.92.0
args:
- --config=/conf/otel-collector-config.yaml
ports:
- containerPort: 4317
hostPort: 4317
protocol: TCP
- containerPort: 4318
hostPort: 4318
protocol: TCP
env:
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
resources:
requests:
cpu: 200m
memory: 400Mi
limits:
cpu: 1000m
memory: 1Gi
volumeMounts:
- name: config
mountPath: /conf
volumes:
- name: config
configMap:
name: otel-collector-config
tolerations:
- effect: NoSchedule
operator: Exists
---
apiVersion: v1
kind: Service
metadata:
name: otel-collector
namespace: otel
spec:
selector:
app: otel-collector
ports:
- name: otlp-grpc
port: 4317
protocol: TCP
- name: otlp-http
port: 4318
protocol: TCP
type: ClusterIPPatrón Sidecar
Despliega el Collector como sidecar en cada Pod de aplicación:
# application-with-sidecar.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
containers:
# Application container
- name: app
image: order-service:latest
ports:
- containerPort: 8080
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://localhost:4317"
- name: OTEL_SERVICE_NAME
value: "order-service"
# OTEL Collector sidecar
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.92.0
args:
- --config=/conf/otel-collector-config.yaml
ports:
- containerPort: 4317
resources:
requests:
cpu: 50m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
volumeMounts:
- name: otel-config
mountPath: /conf
volumes:
- name: otel-config
configMap:
name: otel-sidecar-configKubernetes Operator
Autoinstrumentación mediante OpenTelemetry Operator:
Instalación del Operator
# Install cert-manager (required)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
# Install OpenTelemetry Operator
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yamlInstrumentation CR
# instrumentation.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: otel-instrumentation
namespace: default
spec:
exporter:
endpoint: http://otel-collector.otel.svc.cluster.local:4317
propagators:
- tracecontext
- baggage
- b3
sampler:
type: parentbased_traceidratio
argument: "0.1"
# Java configuration
java:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
env:
- name: OTEL_JAVAAGENT_DEBUG
value: "false"
# Python configuration
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
env:
- name: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED
value: "true"
# Node.js configuration
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latestInyección de autoinstrumentación
# Enable auto-instrumentation on namespace
apiVersion: v1
kind: Namespace
metadata:
name: ecommerce
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
instrumentation.opentelemetry.io/inject-python: "true"
instrumentation.opentelemetry.io/inject-nodejs: "true"
---
# Or apply to individual Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: ecommerce
annotations:
instrumentation.opentelemetry.io/inject-java: "otel-instrumentation"
spec:
template:
metadata:
annotations:
# Pod-level annotation
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: app
image: order-service:latestConfiguración de múltiples backends
Envía datos a varios backends desde un único Collector:
exporters:
# Grafana Tempo
otlp/tempo:
endpoint: tempo-distributor:4317
tls:
insecure: true
# AWS X-Ray
awsxray:
region: ap-northeast-2
# Datadog
datadog:
api:
key: ${DD_API_KEY}
site: datadoghq.com
traces:
span_name_as_resource_name: true
# Jaeger
jaeger:
endpoint: jaeger-collector:14250
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/tempo, awsxray, datadog, jaeger]Actualización de julio de 2026: Observación de un límite de red para el tráfico de agentes de IA
Una publicación del blog de CNCF describe un patrón para crear un límite de red para agentes de IA mediante NGINX y OpenTelemetry. El tráfico saliente de los agentes de IA se fuerza a pasar por un proxy de reenvío (NGINX), y el módulo nativo de OpenTelemetry de NGINX emite un span de OTel para cada solicitud. Esos spans fluyen a través de un Collector de OTel igual que los pipelines descritos anteriormente —se almacenan en un registro de auditoría o se reenvían a Jaeger, Grafana o un SIEM—, lo que permite correlacionar las interacciones de los usuarios con las llamadas externas que un agente realizó en su nombre. Si ejecutas cargas de trabajo de agentes en tu cluster, este es un patrón de observabilidad útil que reutiliza tu pipeline de OTel existente sin cambios.
Prácticas recomendadas
1. Estandariza los atributos de recursos
# Follow Semantic Conventions
resource:
attributes:
# Service information
service.name: order-service
service.version: 1.2.3
service.namespace: ecommerce
# Deployment environment
deployment.environment: production
# Cloud information
cloud.provider: aws
cloud.region: ap-northeast-2
cloud.availability_zone: ap-northeast-2a
# Kubernetes information
k8s.cluster.name: eks-prod
k8s.namespace.name: ecommerce
k8s.pod.name: order-service-abc123
k8s.deployment.name: order-service2. Estrategia de muestreo
# Hierarchical sampling
processors:
tail_sampling:
policies:
# Priority 1: 100% errors
- name: errors
type: status_code
status_code:
status_codes: [ERROR]
# Priority 2: 100% slow requests
- name: slow
type: latency
latency:
threshold_ms: 2000
# Priority 3: 50% critical services
- name: critical-services
type: and
and:
and_sub_policy:
- name: service-name
type: string_attribute
string_attribute:
key: service.name
values: [payment-service, order-service]
- name: probabilistic
type: probabilistic
probabilistic:
sampling_percentage: 50
# Priority 4: 5% for the rest
- name: default
type: probabilistic
probabilistic:
sampling_percentage: 53. Consideraciones de seguridad
# Enable TLS
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
tls:
cert_file: /certs/server.crt
key_file: /certs/server.key
exporters:
otlp:
endpoint: tempo:4317
tls:
ca_file: /certs/ca.crt
cert_file: /certs/client.crt
key_file: /certs/client.key
# Filter sensitive information
processors:
attributes:
actions:
- key: http.request.header.authorization
action: delete
- key: db.statement
action: hash # HashingCuestionario
Pon a prueba tus conocimientos con el Cuestionario de OpenTelemetry.