12. Deployment en producción
12. Deployment en producción
Pasar de “todo en un container” a producción real implica decisiones de:
- Separación de roles (collector ≠ query ≠ ingester).
- Alta disponibilidad y autoscaling.
- Storage replicado y monitoreado.
- Buffer (Kafka) si el volumen lo justifica.
- TLS, autenticación de la UI.
- Observabilidad del propio Jaeger.
Arquitectura de producción
flowchart LR
APP[Apps con OTel] -->|OTLP| LB1[Service IP\nCollector]
LB1 --> C1[Collector pod 1]
LB1 --> C2[Collector pod 2]
LB1 --> C3[Collector pod 3]
C1 --> K[(Kafka)]
C2 --> K
C3 --> K
K --> I1[Ingester pod 1]
K --> I2[Ingester pod 2]
I1 --> S[(Cassandra / ES /\nClickHouse cluster)]
I2 --> S
S --> Q1[Query pod 1]
S --> Q2[Query pod 2]
LB2[Ingress UI] --> Q1
LB2 --> Q2
USR[Usuario] --> LB2
Con buffer Kafka el flujo gana resiliencia: si el storage falla, los spans quedan en Kafka. Sin Kafka, fallo de storage = pérdida de spans.
Opción 1: Jaeger Operator (recomendado)
jaeger-operator es un operator de Kubernetes que gestiona instancias de Jaeger declarativamente.
Instalación
kubectl create namespace observability
kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.62.0/jaeger-operator.yaml -n observability
Definir una instancia productiva
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger-prod
namespace: observability
spec:
strategy: production
collector:
replicas: 3
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: 2, memory: 4Gi }
autoscale: true
minReplicas: 3
maxReplicas: 10
query:
replicas: 2
ingester:
replicas: 3
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch.observability.svc:9200
num-shards: 5
num-replicas: 1
index-prefix: jaeger
ingress:
enabled: true
hosts: ["jaeger.empresa.com"]
tls:
- secretName: jaeger-tls
hosts: [jaeger.empresa.com]
Estrategias del operator
strategy | Componentes |
|---|---|
allInOne | Single pod, dev/staging |
production | Collector + Query separados |
streaming | Collector → Kafka → Ingester → Storage |
streaming es la más robusta para volúmenes altos.
Configurar Kafka buffer
spec:
strategy: streaming
collector:
replicas: 3
ingester:
replicas: 3
options:
kafka:
consumer:
topic: jaeger-spans
brokers: kafka.kafka.svc:9092
storage:
options:
kafka:
producer:
topic: jaeger-spans
brokers: kafka.kafka.svc:9092
es:
server-urls: http://elasticsearch.observability.svc:9200
Opción 2: Helm chart (sin Operator)
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm repo update
helm install jaeger jaegertracing/jaeger \
--namespace observability \
--create-namespace \
--values values.yaml
values.yaml mínimo productivo:
allInOne:
enabled: false
provisionDataStore:
cassandra: false
elasticsearch: false
storage:
type: elasticsearch
elasticsearch:
host: elasticsearch.observability.svc
port: 9200
agent:
enabled: false # deprecated en v2
collector:
replicaCount: 3
service:
otlp:
grpc:
port: 4317
http:
port: 4318
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPU: 70
query:
replicaCount: 2
ingress:
enabled: true
hosts:
- host: jaeger.empresa.com
paths:
- path: /
pathType: Prefix
Autoscaling
El collector es CPU-bound: parsing y validación de spans. HPA basado en CPU funciona bien:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: jaeger-collector
spec:
scaleTargetRef: { kind: Deployment, name: jaeger-collector }
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Para ingester puede tener sentido escalar por lag de Kafka:
- type: External
external:
metric: { name: kafka_consumergroup_lag }
target: { type: AverageValue, averageValue: 10000 }
TLS y autenticación
TLS entre apps y collector
collector:
resources: ...
cmdlineParams:
collector.otlp.grpc.tls.enabled: "true"
collector.otlp.grpc.tls.cert: /tls/tls.crt
collector.otlp.grpc.tls.key: /tls/tls.key
En las apps:
export OTEL_EXPORTER_OTLP_ENDPOINT=https://jaeger-collector.observability.svc:4317
export OTEL_EXPORTER_OTLP_CERTIFICATE=/etc/ssl/ca.crt
Autenticación de la UI
Jaeger no tiene auth nativa. Tres opciones comunes:
- Ingress con OAuth2 proxy (Google, GitHub, OIDC).
- NGINX con basic auth (rápido para staging).
- VPN privada (la UI no expuesta a internet).
# Ejemplo con oauth2-proxy delante de Jaeger Query
Resource sizing inicial
Punto de partida para 5.000 spans/s sostenidos:
| Componente | Réplicas | CPU | RAM |
|---|---|---|---|
| Collector | 3-5 | 500m-2 | 1-4 GiB |
| Ingester (si Kafka) | 3 | 500m-1 | 1-2 GiB |
| Query | 2-3 | 250m-1 | 512 MiB-2 GiB |
| Cassandra cluster | 3 nodos | 2-4 | 8-16 GiB |
| Elasticsearch cluster | 3 nodos | 2-4 | 8-16 GiB |
Ajustá midiendo. Las recetas de la documentación oficial son orientativas.
Observabilidad de Jaeger mismo
Jaeger expone métricas Prometheus en cada pod (/metrics). Scrap éstas mínimo:
# ServiceMonitor para Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: jaeger-collector
spec:
selector:
matchLabels: { app: jaeger, app.kubernetes.io/component: collector }
endpoints:
- port: admin-http
interval: 30s
Alertas críticas
groups:
- name: jaeger
rules:
- alert: JaegerSpansDropped
expr: rate(jaeger_collector_spans_dropped_total[5m]) > 10
for: 5m
annotations:
summary: "Jaeger collector descartando spans"
- alert: JaegerStorageError
expr: rate(jaeger_storage_operations_failures_total[5m]) > 0
for: 5m
- alert: JaegerKafkaLag
expr: kafka_consumergroup_lag{consumergroup="jaeger-ingester"} > 100000
for: 10m
Si Jaeger pierde spans y nadie se entera, no estás operando observabilidad — la estás simulando.
Backup y disaster recovery
Cassandra
- Snapshots regulares (
nodetool snapshot). - Replicación factor ≥ 3 en cluster multi-DC si necesitás cross-region DR.
- Test de restore al menos trimestral.
Elasticsearch
- Snapshot lifecycle policies apuntando a S3/GCS.
- Verificá que el snapshot incluye todos los índices
jaeger-*.
ClickHouse
BACKUP TABLEa S3 o disk separado.- Replicación con ZooKeeper / Keeper si necesitás HA.
Aclaración crítica: las trazas de Jaeger no son datos críticos como tu base transaccional. Si perdés 1 hora de trazas en un disaster, sufrís pero el negocio sigue. Dimensioná tu DR acordemente — no gastés en HA cross-region para Jaeger lo que no gastás para tu DB principal.
Coste vs valor
Jaeger productivo cuesta. Las preguntas honestas a tu equipo:
- ¿Cuántos incidentes por mes resuelve más rápido gracias a tener trazas?
- ¿Cuánto tiempo de ingeniería ahorra ese diagnóstico más rápido?
- ¿Cuánto cuesta (infra + ops) la setup actual?
Si las trazas no son la primera herramienta que abre el equipo en un incidente, no estás capitalizando. La inversión solo se justifica con cambio cultural acompañando.
¿Qué viene?
En el próximo capítulo cubrimos Service Performance Monitoring (SPM): cómo derivar métricas RED de los spans usando el spanmetrics processor y Prometheus, para que el Monitor de Jaeger funcione.