Mastodon: ejecución de Collectors de OpenTelemetry en producción con un equipo pequeño
Por Juliano Costa (Datadog), Tristan Sloughter (community), Johanna Öjeling (Grafana Labs), Damien Mathieu (Elastic), Tim Campbell (Mastodon) | 18 de marzo de 2026
Esta implementación de referencia describe cómo Mastodon, una organización sin ánimo de lucro que opera a escala global con un equipo notablemente pequeño, ejecuta el OpenTelemetry Collector en producción.
Mastodon de un vistazo
Mastodon es una plataforma de redes sociales descentralizada, gratuita y de código abierto, operada por una organización sin ánimo de lucro.
La descentralización no es aquí un término de marketing, sino un principio arquitectónico central. Cualquiera puede ejecutar su propio servidor de Mastodon, y esos servidores operados de forma independiente interoperan mediante protocolos abiertos como parte de lo que se conoce como el fediverso: una red federada de plataformas sociales independientes que se comunican entre sí usando protocolos estandarizados como ActivityPub. Al igual que ocurre con el correo electrónico, los usuarios pueden comunicarse entre instancias independientemente de quién las opere.
Esta filosofía no solo condiciona las decisiones de producto de Mastodon, sino también su enfoque de la observabilidad.
Estructura organizativa
Toda la organización Mastodon está formada por unas 20 personas, y la infraestructura de observabilidad (incluido el OpenTelemetry Collector) la gestiona un único ingeniero.
A pesar del reducido tamaño del equipo, Mastodon opera dos grandes instancias de Mastodon en producción:
Se ejecuta en Kubernetes con autoescalado entre 9 y 15 nodos (16 núcleos, 64 GB de RAM cada uno). El frontend web escala entre 5 y 20 pods, mientras que los distintos pools de workers de Sidekiq escalan entre 10 y 40 pods. De media, mastodon.social tiene entre 70 y 80 pods en ejecución en un momento dado. Esta plataforma gestiona hasta 300 000 usuarios activos al día y aproximadamente 10 millones de solicitudes por minuto.
Se ejecuta en Kubernetes con autoescalado entre 3 y 6 nodos (8 núcleos, 32 GB de RAM cada uno). El frontend web escala entre 3 y 10 pods, y los pools de Sidekiq escalan entre 5 y 15 pods, lo que da un promedio total de 20 a 30 pods. Esta instancia opera a una escala menor, pero aun así considerable.
Con un ancho de banda operativo tan limitado, la simplicidad y la fiabilidad no son negociables.
Adopción de OpenTelemetry: la libertad de elección por diseño
Dado que Mastodon es de código abierto y está diseñado para que otros lo ejecuten, el equipo quería una solución de telemetría que preservara la libertad del operador.
OpenTelemetry se convirtió en la opción predeterminada porque permite que cada operador de un servidor Mastodon decida cómo —o si— se recopila la telemetría.
Mediante una sencilla configuración por variables de entorno, los operadores pueden elegir:
- Enviar la telemetría directamente a un backend de observabilidad (usando únicamente la configuración del SDK de Ruby)
- Enrutar la telemetría a través de un OpenTelemetry Collector
- Deshabilitar la telemetría por completo
La organización central de Mastodon no realiza seguimiento de cómo gestionan la observabilidad las instancias externas. Lo que importa es que la telemetría emitida se ajuste estrictamente a las convenciones semánticas de OpenTelemetry, lo que la hace utilizable en cualquier lugar.
Este enfoque evita los modelos de datos específicos de proveedor y garantiza la compatibilidad con el ecosistema más amplio de OpenTelemetry, sin que Mastodon tenga que mantener sus propias convenciones.
Arquitectura del Collector: uno solo por namespace
La arquitectura de Collector de Mastodon es intencionadamente minimalista.
Un único OpenTelemetry Collector por namespace de Kubernetes gestiona todas las señales de telemetría: trazas, métricas y logs. No hay niveles separados de gateway y agente, ni capas de enrutamiento complejas, ni herramientas de despliegue personalizadas.

Dada la escala y el tráfico, esto ha demostrado ser más que suficiente.
Tim Campbell, ingeniero de software en Mastodon, comentó que en los aproximadamente 2 años que llevan ejecutando el Collector, nunca han tenido un solo problema con él.
«Para mi sorpresa, mi muy grata sorpresa, no he tenido ni un solo problema. Como usamos un operador de Kubernetes para esto, si alguna vez surge algún problema, simplemente se reinicia automáticamente. Al menos en lo que respecta a las trazas y los logs reales que llegan a Datadog, no he visto ninguna interrupción. En cuanto a memoria y procesos, se ha mantenido perfectamente estable dentro de los límites que hemos establecido.»
Despliegue y gestión del ciclo de vida
Para mantener la sobrecarga operativa lo más baja posible, Mastodon se apoya en:
- El OpenTelemetry Operator para Kubernetes
- Argo CD para despliegues y promociones basados en Git
Cada Collector se define como un recurso personalizado OpenTelemetryCollector.
A partir de ahí, Kubernetes se encarga automáticamente de la reconciliación, los
reinicios y la gestión del ciclo de vida.
«Básicamente, solo necesitamos crear un archivo yaml para cada objeto
OpenTelemetryCollectorque necesitemos crear, y Argo se encarga de desplegar/actualizar automáticamente lo que necesitamos.»
Este modelo proporciona:
- Configuración declarativa
- Recuperación automática ante fallos
- Auditabilidad clara a través del historial de Git
Cabe destacar que Mastodon no impone límites estrictos de CPU o memoria a los pods del Collector. En la práctica, el consumo de recursos se ha mantenido insignificante en comparación con el resto de la plataforma.
Gestión del tráfico mediante el muestreo
En lugar de basarse en límites de recursos, Mastodon controla la sobrecarga de observabilidad principalmente mediante el muestreo basado en cola (tail-based sampling).
- En mastodon.social, las trazas exitosas se muestrean aproximadamente al 0,1 %, lo que da como resultado solo unas pocas docenas de trazas por minuto a pesar del tráfico extremadamente alto.
- En mastodon.online, el muestreo es ligeramente más permisivo, pero sigue los mismos principios.
- Todas las trazas de error se recopilan siempre, lo que garantiza una visibilidad completa de los fallos.
Este enfoque mantiene el volumen de datos predecible al tiempo que conserva los datos de diagnóstico de alto valor.
Configuración: con criterio propio, pero mínima
Mastodon usa la distribución OpenTelemetry Collector Contrib, principalmente por conveniencia: incluye todo lo que necesitan sin requerir compilaciones personalizadas.
La configuración se centra en:
- Ingesta OTLP para todas las señales
- Enriquecimiento de metadatos de Kubernetes
- Detección de recursos
- Muestreo basado en la cola (tail-based sampling)
- Transformación para la compatibilidad con el backend
A continuación se incluye una configuración completa de producción, a modo de referencia (también puedes verla en otelbin):
Configuración del Collector de Mastodon
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: mastodon-social
namespace: mastodon-social
spec:
nodeSelector:
joinmastodon.org/property: mastodon.social
env:
- name: DD_API_KEY
valueFrom:
secretKeyRef:
name: datadog-secret
key: api-key
- name: DD_SITE
valueFrom:
secretKeyRef:
name: datadog-secret
key: site
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- 'http://*'
- 'https://*'
processors:
batch: {}
resource:
attributes:
- key: deployment.environment.name
value: 'production'
action: upsert
- key: property
value: 'mastodon.social'
action: upsert
- key: git.commit.sha
from_attribute: vcs.repository.ref.revision
action: insert
- key: git.repository_url
from_attribute: vcs.repository.url.full
action: insert
k8sattributes:
auth_type: 'serviceAccount'
passthrough: false
extract:
metadata:
- k8s.namespace.name
- k8s.pod.name
- k8s.pod.start_time
- k8s.pod.uid
- k8s.deployment.name
- k8s.node.name
labels:
- tag_name: app.label.component
key: app.kubernetes.io/component
from: pod
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.ip
- sources:
- from: resource_attribute
name: k8s.pod.uid
- sources:
- from: connection
resourcedetection:
detectors: [system]
system:
resource_attributes:
os.description:
enabled: true
host.arch:
enabled: true
host.cpu.vendor.id:
enabled: true
host.cpu.family:
enabled: true
host.cpu.model.id:
enabled: true
host.cpu.model.name:
enabled: true
host.cpu.stepping:
enabled: true
host.cpu.cache.l2.size:
enabled: true
transform:
error_mode: ignore
# Nomenclatura correcta de la función de código
trace_statements:
- context: span
conditions:
- attributes["code.namespace"] != nil
statements:
- set(attributes["resource.name"],
Concat([attributes["code.namespace"],
attributes["code.function"]], "#"))
# Nombre de host de Kubernetes correcto
- context: resource
conditions:
- attributes["k8s.node.name"] != nil
statements:
- set (attributes["k8s.node.name"],
Concat([attributes["k8s.node.name"], "k8s-1"], "-"))
metric_statements:
- context: resource
conditions:
- attributes["k8s.node.name"] != nil
statements:
- set (attributes["k8s.node.name"],
Concat([attributes["k8s.node.name"], "k8s-1"], "-"))
log_statements:
- context: resource
conditions:
- attributes["k8s.node.name"] != nil
statements:
- set (attributes["k8s.node.name"],
Concat([attributes["k8s.node.name"], "k8s-1"], "-"))
attributes/sidekiq:
include:
match_type: strict
attributes:
- key: messaging.sidekiq.job_class
actions:
- key: resource.name
from_attribute: messaging.sidekiq.job_class
action: upsert
tail_sampling:
policies:
[
{
name: errors-policy,
type: status_code,
status_code: { status_codes: [ERROR] },
},
{
name: randomized-policy,
type: probabilistic,
probabilistic: { sampling_percentage: 0.1 },
},
]
connectors:
datadog/connector:
traces:
compute_stats_by_span_kind: true
exporters:
datadog:
api:
site: ${DD_SITE}
key: ${DD_API_KEY}
traces:
compute_stats_by_span_kind: true
trace_buffer: 500
service:
pipelines:
traces/all:
receivers: [otlp]
processors:
[
resource,
k8sattributes,
resourcedetection,
transform,
attributes/sidekiq,
batch,
]
exporters: [datadog/connector]
traces/sample:
receivers: [datadog/connector]
processors: [tail_sampling, batch]
exporters: [datadog]
metrics:
receivers: [datadog/connector, otlp]
processors:
[resource, k8sattributes, resourcedetection, transform, batch]
exporters: [datadog]
logs:
receivers: [otlp]
processors:
[
resource,
k8sattributes,
resourcedetection,
transform,
attributes/sidekiq,
batch,
]
exporters: [datadog]
Mantenerse actualizado
Mastodon normalmente actualiza el OpenTelemetry Collector en el plazo de uno o dos días tras cada versión.
«Todo está documentado, y todos los cambios incompatibles están correctamente detallados», señaló Tim, elogiando la claridad de las notas de la versión.
Aunque las versiones frecuentes a veces introducen cambios incompatibles, el equipo lo considera una señal de un desarrollo saludable y activo, siempre que te mantengas al día.
Lecciones y puntos de dolor
La parte más difícil del recorrido fue, simplemente, empezar. Entender cómo encajan entre sí los componentes del Collector llevó tiempo, especialmente para un equipo sin especialistas dedicados a la observabilidad. Más recientemente, la mayor complejidad ha surgido del uso avanzado del procesador transform, en particular al adaptar los atributos de span a los requisitos de nomenclatura específicos del backend.
transform:
error_mode: ignore
# Nomenclatura correcta de la función de código
trace_statements:
- context: span
conditions:
- attributes["code.namespace"] != nil
statements:
- set(attributes["resource.name"], Concat([attributes["code.namespace"],
attributes["code.function"]], "#"))
En la regla del procesador transform anterior, han configurado una condición
para establecer resource.name (un atributo específico de Datadog) con el valor
de code.namespace#code.function. Con esa configuración, cada vez que el span
llegaba al backend, podía asignarse al nombre que habían definido. A pesar de
esa curva de aprendizaje, la experiencia general ha superado las expectativas.
«Básicamente puedes hacer lo que quieras. Superó mis expectativas. Todo funciona bastante bien.»
Esa fiabilidad y flexibilidad son las razones por las que Mastodon sigue usando el OpenTelemetry Collector en producción.
Consejos para equipos pequeños
A partir de la experiencia de Mastodon, destacan algunas lecciones:
- Mantén la arquitectura simple: un único Collector puede llegar muy lejos
- Confía en los operadores de Kubernetes para la gestión del ciclo de vida
- Usa el muestreo para controlar los costes
- Cíñete a las convenciones semánticas para evitar la dependencia de proveedor (lock-in) a largo plazo
- Actualiza con frecuencia para reducir el impacto de los cambios incompatibles
Conclusiones
La historia de Mastodon demuestra que incluso un equipo muy pequeño puede operar con éxito OpenTelemetry Collectors en producción —a escala global— sin una carga operativa significativa.
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!