Skyscanner: gestión de OpenTelemetry Collectors en 24 clústeres de producción
Por Johanna Öjeling (Grafana Labs), Juliano Costa (Datadog), Tristan Sloughter (community), Neil Fordyce (Skyscanner) | 21 de abril de 2026
Esta implementación de referencia describe cómo Skyscanner, una plataforma global de búsqueda de viajes con sede en Edimburgo, Escocia, ejecuta OpenTelemetry a gran escala.
Con 1.400 empleados en todo el mundo que operan más de 1.000 microservicios en 24 clústeres de Kubernetes de producción, el recorrido de Skyscanner con OpenTelemetry ofrece lecciones valiosas para organizaciones que operan a gran escala.
Estructura organizativa
El equipo Hubble, formado por seis ingenieros de plataforma, gestiona la mayor parte de los collectors de Skyscanner. Como parte de la organización más amplia de ingeniería de plataforma, se encargan de la plataforma de cómputo que ejecuta la arquitectura de microservicios de Skyscanner, basada principalmente en Java.
Los propios equipos de servicio permanecen abstraídos de la infraestructura de despliegue y recolección de telemetría. Para los servicios en Java, los equipos heredan una imagen base de Docker que contiene el agente Java de OpenTelemetry preconfigurado. Para los servicios en Python y Node.js, el equipo de plataforma proporciona librerías wrapper que establecen valores predeterminados razonables basados en atributos de entorno y de recursos. Estos enfoques minimizan la configuración repetitiva y proporcionan a los equipos de servicio observabilidad lista para usar sin requerir un conocimiento profundo de OpenTelemetry.
Adopción de OpenTelemetry
El recorrido de Skyscanner con OpenTelemetry comenzó en 2021. La empresa estaba migrando de un stack de código abierto construido internamente a un proveedor comercial. Sin embargo, querían evitar la dependencia de proveedor (vendor lock-in).
«Queríamos migrar a un proveedor de una forma que fuera agnóstica respecto al proveedor», explicó Neil Fordyce, ingeniero de software del equipo de plataforma Hubble de Skyscanner.
Este enfoque agnóstico respecto al proveedor los llevó a adoptar el OpenTelemetry Collector como pieza central de su infraestructura de telemetría.
Arquitectura: enrutamiento centralizado, recolección distribuida
La arquitectura de collectors de Skyscanner presenta un endpoint DNS central con enrutamiento inteligente basado en Istio. Independientemente de dónde se ejecuten los servicios a nivel global o en qué clúster se encuentren, envían la telemetría a esta única dirección. Istio se encarga de enrutar las solicitudes hacia el collector disponible más cercano.
El despliegue consta de dos patrones de collector distintos:
Gateway Collector (Replica Set): gestiona el grueso del tráfico OTLP (trazas y métricas) proveniente de la mayoría de los servicios, donde ocurre la mayor parte del procesamiento.
Agent Collector (DaemonSet): recolecta (scrapes) endpoints de Prometheus de servicios de código abierto y de plataforma que aún no admiten OTLP de forma nativa.

Configuración: comenzar con lo simple, evolucionar gradualmente
Cuando Skyscanner desplegó los collectors por primera vez en 2021, su configuración era mínima: memory limiter, batch processor y un exportador OTLP para trazas.
Con el tiempo, la configuración evolucionó de forma orgánica: se añadieron pipelines de métricas, se integró la ingesta de spans de Istio, se implementó la transformación de spans a métricas y se añadieron filter processors para reducir el ruido y controlar los costes.
Convertir los spans del service mesh de Istio en métricas de plataforma
Uno de los usos más innovadores que Skyscanner hace del collector consiste en generar métricas a partir de los spans del service mesh de Istio.
Las métricas nativas de Istio sufrían problemas de explosión de cardinalidad que llegaban a saturar su despliegue de Prometheus. Además, Skyscanner opera muchos servicios listos para usar (off-the-shelf) sobre los que no tienen control del código, pero para los que aún necesitan métricas consistentes.
Su solución: configurar Istio para emitir spans (originalmente en formato Zipkin, aunque Istio ahora admite OTLP), ingerirlos a través del collector con el receptor Zipkin, transformarlos para que cumplan con las convenciones semánticas, y usar el span metrics connector para generar métricas consistentes sin ninguna instrumentación de la aplicación.
«Podemos hacer eso a nivel de plataforma sin que los propietarios de las aplicaciones tengan que instrumentar su código en absoluto», señaló Neil.
La configuración del span metrics connector extrae dimensiones clave de los spans:
connectors:
spanmetrics:
aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA
dimensions:
- name: http.status_code
- name: grpc.status_code
- name: rpc.service
- name: rpc.method
- name: prot
- name: flag
- name: k8s.deployment.name
- name: k8s.replicaset.name
- name: destination_subset
dimensions_cache_size: 15000000
histogram:
exponential:
max_size: 160
unit: ms
metrics_flush_interval: 30s
El collector transforma después estas métricas para usar nombres de convenciones
semánticas como http.client.duration y http.server.duration, agregándolas
por clúster, nombre de servicio y código de estado HTTP. Esto proporciona
métricas HTTP a nivel de plataforma para cada servicio sin cambios de código,
con una nomenclatura consistente que respeta las convenciones semánticas y con
menor cardinalidad que las métricas nativas de Istio.
El desafío de los errores 404
Un desafío notable con la configuración del collector involucró a servicios de caché que devolvían HTTP 404 para indicar que una entrada no existía en la caché. El collector trataba estos 404 como errores, lo que activaba un muestreo de trazas del 100% para lo que en realidad era un comportamiento normal y de alto volumen.
La solución fue añadir un filter processor para desactivar (unset) el estado de error en estas respuestas 404 específicas:
processors:
span/unset_cache_client_404:
include:
attributes:
- key: http.response.status_code
value: ^404$
- key: server.address
value: ^(service-x\.skyscanner\.net|service-y\.skyscanner\.net|service-z\.skyscanner\.net|service-z-\w{2}-\w+-\d\.int\.\w{2}-\w+-\d\.skyscanner\.com)$
match_type: regexp
regexp:
cacheenabled: true
cachemaxnumentries: 1000
status:
code: Unset
Este processor identifica los spans con códigos de estado 404 provenientes de servicios de caché específicos y desactiva su estado de error, evitando que activen un muestreo basado en errores.
«Habríamos tenido trazas de mayor calidad y más fáciles de usar si hubiéramos contado con ese filter processor desde el principio», reflexionó Neil.
Sin embargo, Neil señala que, con la reciente introducción de la configuración declarativa del SDK de OpenTelemetry, este tipo de filtrado ahora podría configurarse de forma descentralizada por los propios equipos de servicio, en lugar de requerir cambios en la configuración central del collector.
Análisis detallado de la configuración
Skyscanner ha compartido sus configuraciones de collector de producción para ayudar a otros a entender estos patrones en la práctica:
Gateway collector
El gateway collector gestiona la mayor parte del procesamiento:
- Recibe métricas y trazas OTLP de los servicios, y spans Zipkin de Istio
- Usa el span metrics connector para generar métricas a partir de los spans de Istio
- Emplea numerosos transform processors para mapear los atributos de Istio a las convenciones semánticas
- Implementa la lógica de filtrado de 404 para los servicios de caché
- Exporta métricas y trazas al proveedor de observabilidad vía OTLP
El diagrama ilustra cómo las métricas y trazas OTLP, así como los spans de Istio, llegan a estos gateway collectors:

Agent collector
El agent collector se centra en recolectar métricas de infraestructura y de plataforma de cada nodo:
- Recolecta (scrapes) endpoints de Prometheus de varias fuentes (node exporter, kube-state-metrics, kubelet)
- Realiza un procesamiento mínimo (limitación de memoria, agrupación en lotes, limpieza de atributos)
- Exporta métricas al proveedor de observabilidad vía OTLP
Estrategia de instrumentación
El entorno de Skyscanner, con un fuerte peso de Java, se beneficia significativamente de las capacidades de auto-instrumentación de OpenTelemetry. El agente Java, preconfigurado en las imágenes base de Docker, proporciona generación de spans HTTP y gRPC lista para usar.
Auto-instrumentación con criterio propio
El equipo adopta un enfoque deliberadamente selectivo respecto a la auto-instrumentación. En lugar de habilitarlo todo por defecto, parten en la dirección opuesta: todas las instrumentaciones están deshabilitadas en una imagen base de Docker compartida, y solo se habilita explícitamente un conjunto seleccionado.
«Es más bien al revés. Deshabilitamos todo y luego habilitamos lo que necesitamos», explicó Neil.
Usando variables de entorno en la imagen base, Skyscanner habilita por defecto un conjunto reducido de instrumentaciones relacionadas con el runtime, HTTP y gRPC. Esto incluye JAX-RS, gRPC, Jetty, clientes HTTP habituales, instrumentación de executors y propagación del contexto de logging. Los equipos de servicio heredan estos valores predeterminados automáticamente, pero siguen siendo libres de sobrescribirlos o de habilitar instrumentaciones adicionales en las definiciones de su propio servicio si lo necesitan.
Este modelo garantiza consistencia entre cientos de servicios sin dejar de ofrecer flexibilidad en los extremos.
Configuración del agente Java
El siguiente fragmento ilustra la imagen base de Java compartida. Incorpora el agente Java de OpenTelemetry en la imagen, establece valores predeterminados para toda la organización e instala un script de lanzamiento común:
# Imagen usada como fuente del agente Java de OpenTelemetry
FROM ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.25.0 AS otel
# Define una imagen base común que todos los microservicios Java deben extender
FROM image/registry/public-java-image:x.y.z
# Copia el agente Java de OpenTelemetry desde la imagen de OTel
COPY --from=otel /javaagent.jar $OPEN_TELEMETRY_DIRECTORY/opentelemetry-javaagent.jar
ENV OTEL_AGENT=$OPEN_TELEMETRY_DIRECTORY/opentelemetry-javaagent.jar
# Elige valores predeterminados razonables para todos en la organización
ENV OTEL_METRICS_EXPORTER="otlp"
ENV OTEL_TRACES_EXPORTER="otlp"
ENV OTEL_LOGS_EXPORTER="none"
ENV OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="DELTA"
ENV OTEL_EXPERIMENTAL_METRICS_VIEW_CONFIG="otel-view.yaml"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="http://otel.skyscanner.net"
ENV OTEL_INSTRUMENTATION_COMMON_DEFAULT_ENABLED="false"
ENV OTEL_INSTRUMENTATION_RUNTIME_TELEMETRY_ENABLED="true"
ENV OTEL_INSTRUMENTATION_ASYNC_HTTP_CLIENT_ENABLED="true"
ENV OTEL_INSTRUMENTATION_APACHE_HTTPCLIENT_ENABLED="true"
COPY run.sh /usr/bin/run.sh
Ese script de lanzamiento, run.sh, construye los parámetros -javaagent y
otel.resource.attributes a partir de las variables de entorno que proporciona
el sistema de despliegue:
# Usamos esto para configurar los atributos de recurso de OTel
# para aquello que podemos detectar desde variables de entorno al iniciar el servicio
# Estas variables las establece automáticamente nuestro sistema de despliegue
# Se han omitido algunas variables de entorno para evitar repeticiones
setup_otel_agent() {
if [[ -n "$AWS_REGION" ]]; then CLOUD_REGION="cloud.region=${AWS_REGION},"; else CLOUD_REGION=""; fi
if [[ -n "$AWS_ACCOUNT" ]]; then CLOUD_ACCOUNT_ID="cloud.account.id=${AWS_ACCOUNT},"; else CLOUD_ACCOUNT_ID=""; fi
if [[ -n "$CLUSTER_NAME" ]]; then K8S_CLUSTER_NAME="k8s.cluster.name=${CLUSTER_NAME},"; else K8S_CLUSTER_NAME=""; fi
if [[ -n "$SERVICE" ]]; then SERVICE_NAME="service.name=${SERVICE}"; else SERVICE_NAME=""; fi
echo -n "-javaagent:$OTEL_AGENT" \
"-Dotel.resource.attributes=${CLOUD_REGION}${CLOUD_ACCOUNT_ID}${K8S_CLUSTER_NAME}${SERVICE_NAME}"
}
JAVA_OPTS="-D64 -server -showversion $(setup_otel_agent) ${ADDITIONAL_JAVA_OPTS:-}"
exec java $JAVA_OPTS "$@"
Por último, el Dockerfile de cada servicio individual extiende la misma base y solo añade las instrumentaciones adicionales que ese servicio necesita:
FROM image/registry/skyscanner-java-base:x.y.z
COPY my-service.jar
# Es fácil de extender si my-service quiere habilitar alguna otra instrumentación no predeterminada
ENV OTEL_INSTRUMENTATION_OPENAI_ENABLED=true
ENV OTEL_INSTRUMENTATION_OKHTTP_ENABLED=true
CMD exec /usr/bin/run.sh -jar my-service.jar server
Spans sí, métricas no (por defecto)
Un aspecto particularmente interesante de la estrategia de Skyscanner es cómo tratan las métricas frente a las trazas. Aunque las instrumentaciones HTTP y gRPC están habilitadas, el equipo descarta deliberadamente la mayoría de las métricas HTTP y RPC generadas por el SDK. Esto se debe a que ya obtienen métricas de plataforma consistentes y de menor cardinalidad a partir de los spans del service mesh de Istio, como se describió anteriormente.
En lugar de deshabilitar por completo las instrumentaciones —lo cual también eliminaría los spans—, usan vistas del SDK de OpenTelemetry para descartar las agregaciones de métricas mientras preservan el trazado:
- Las métricas HTTP y RPC se descartan globalmente
- Los spans se siguen emitiendo con normalidad
- Los equipos de servicio pueden reactivar selectivamente métricas específicas (por ejemplo, la latencia del lado del servidor) si necesitan una granularidad adicional más allá de la que proporciona Istio
Cuando los equipos vuelven a activar métricas del SDK, a menudo las renombran para evitar colisiones o el doble conteo con las métricas ya existentes derivadas de Istio.
En la imagen base de Java mostrada anteriormente,
OTEL_EXPERIMENTAL_METRICS_VIEW_CONFIG apunta al archivo otel-view.yaml
predeterminado de Skyscanner, utilizando la
configuración de archivo de vistas:
# Configuración de vista de métricas predeterminada de Skyscanner
# Almacenada en un archivo al que apunta OTEL_EXPERIMENTAL_METRICS_VIEW_CONFIG
# Descarta las métricas de http y rpc, porque ya tenemos métricas de Istio
# Aun así queremos que el trazado funcione, así que no desactivaríamos la instrumentación sin más
- selector:
instrument_name: http.*
view:
aggregation: drop
- selector:
instrument_name: rpc.*
view:
aggregation: drop
El mismo archivo se puede extender cuando un servicio necesita conservar
métricas específicas. Un caso de uso típico es desglosar las solicitudes por
http.route:
# Este comportamiento de descarte se puede modificar ampliando la lista para
# añadir más vistas que seleccionen explícitamente las métricas a conservar.
# p. ej. para conservar las métricas http.server.request.duration,
# pero seguir descartando las métricas http.client.*
- selector:
instrument_name: http.server.request.duration
view:
# renombrada porque ya tenemos métricas de Istio llamadas http.server.request.duration,
# así que no queremos que colisionen ni se cuenten dos veces
name: app.http.server.request.duration
attribute_keys:
- http.request.method
- http.route
- http.response.status_code
Este enfoque permite a Skyscanner conservar trazas distribuidas de alto valor, evitar la duplicación de métricas, controlar la cardinalidad y reducir los costes de ingesta, todo ello sin exigir a los propietarios de los servicios un conocimiento profundo del funcionamiento interno de OpenTelemetry.
En general, la estrategia refleja una mentalidad de plataforma sólida: proporcionar valores predeterminados razonables que funcionen a gran escala, minimizar el ruido y hacer que “lo correcto” sea también “lo fácil”, dejando a la vez margen para que los equipos con necesidades avanzadas puedan ir más allá.
Despliegue y gestión de versiones
Skyscanner usa la distribución Contrib del OpenTelemetry Collector, adoptada porque incluía todo lo que necesitaban. El equipo aprendió que Contrib no se recomienda para uso en producción, y planean explorar la construcción de imágenes de collector personalizadas que incluyan solo los componentes que necesitan.
Skyscanner actualiza los collectors aproximadamente cada seis meses, aunque actualizan con mayor frecuencia si están siguiendo funcionalidades específicas o correcciones críticas. Siguen feeds RSS y canales de Slack de la CNCF para mantenerse informados sobre las novedades.
Su estrategia de despliegue usa una promoción progresiva entre niveles de clúster: clústeres de Dev, después tres clústeres de producción Alpha, seguidos de ocho clústeres de producción Beta y, finalmente, los 13 clústeres de producción restantes. Usando Argo CD para el despliegue, los cambios se promueven entre niveles mediante pull requests.
«Sin duda hemos causado problemas en los clústeres de pruebas de desarrollo y luego los hemos solucionado antes de seguir promoviendo el cambio», dijo Neil.
Este enfoque gradual ha permitido detectar problemas de configuración antes de que lleguen a producción. Aunque todavía no cuentan con capacidades automatizadas de pruebas y rollback para sus despliegues del OpenTelemetry Collector, estas mejoras están en el horizonte.
Lo que funciona bien
Desde que adoptaron OpenTelemetry en producción, la experiencia del equipo ha sido muy positiva.
«Realmente ha sido bastante indoloro», reflexionó Neil.
La flexibilidad destaca como la mayor fortaleza del collector.
«Todo lo que nos hemos propuesto hacer, hemos podido llevarlo a cabo. Creo que eso dice mucho de lo flexible que es», explicó Neil.
Otros aspectos destacados incluyen que el protocolo OTLP proporciona independencia de proveedor mediante una configuración sencilla, notas de versión claras y bien organizadas, y la capacidad de respuesta de la comunidad cuando miembros del equipo descubrieron y contribuyeron correcciones para una fuga de memoria en un componente del collector.
Lecciones y puntos de dolor
Skyscanner todavía usa convenciones semánticas HTTP antiguas e inestables en algunos pipelines. Actualizarlas requiere modificar múltiples reglas de transform processors que mapean los atributos de Istio a los nombres de las convenciones semánticas, lo que implica cotejar manualmente la documentación y completar cadenas de configuración.
El equipo conoce Weaver para la gestión de convenciones semánticas, pero todavía no lo ha integrado en su flujo de trabajo.
Actualizar cada seis meses implica enfrentarse a múltiples cambios disruptivos (breaking changes) a la vez. Aunque las notas de versión están bien escritas y documentan los cambios con claridad, revisar seis meses de actualizaciones de una sola vez añade más fricción que mantener el ritmo de las versiones.
Consejos para otros
Basándose en su experiencia en producción, el equipo de Skyscanner ofrece estos consejos:
- Empieza de forma simple: comienza solo con el memory limiter, el batch processor y exportadores básicos. Añade complejidad únicamente cuando surja la necesidad.
- Memory limiter desde el primer día: configúralo de inmediato para prevenir problemas de memoria a medida que escalas.
- Considera los filter processors desde el principio: comprende la semántica de los códigos de estado de tu aplicación y filtra los “falsos positivos” de alto volumen para controlar los costes.
- No sobrediseñes la resiliencia: para los datos de telemetría, un procesamiento por lotes simple en memoria suele ser suficiente.
- Los despliegues graduales detectan problemas: la promoción progresiva entre niveles de entorno proporciona una validación valiosa.
Conclusiones
La historia de Skyscanner demuestra que equipos de ingeniería de plataforma de tamaño modesto pueden gestionar con éxito una infraestructura de OpenTelemetry Collector a gran escala con una sobrecarga operativa relativamente baja.
Comentarios
¿Fue útil esta página?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!