12. Deployment en producción

Por: Artiko
jaegerkubernetesproduccionoperatorhelmkafka

12. Deployment en producción

Pasar de “todo en un container” a producción real implica decisiones de:


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

strategyComponentes
allInOneSingle pod, dev/staging
productionCollector + Query separados
streamingCollector → 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:

  1. Ingress con OAuth2 proxy (Google, GitHub, OIDC).
  2. NGINX con basic auth (rápido para staging).
  3. 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:

ComponenteRéplicasCPURAM
Collector3-5500m-21-4 GiB
Ingester (si Kafka)3500m-11-2 GiB
Query2-3250m-1512 MiB-2 GiB
Cassandra cluster3 nodos2-48-16 GiB
Elasticsearch cluster3 nodos2-48-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

Elasticsearch

ClickHouse

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:

  1. ¿Cuántos incidentes por mes resuelve más rápido gracias a tener trazas?
  2. ¿Cuánto tiempo de ingeniería ahorra ese diagnóstico más rápido?
  3. ¿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.