Todos los artículos

Lecciones de operar sistemas en producción con equipos distribuidos

Lecciones reales de gestionar sistemas en producción a través de zonas horarias: observabilidad, gestión de incidentes, runbooks, confianza en los deploys y cómo construir una cultura de responsabilidad.

9 min de lectura
  • #distributed-teams
  • #production
  • #devops
  • #observability
  • #engineering-culture

La alerta de las 3 AM que cambió mi forma de pensar sobre los equipos

Dormía profundamente cuando llegó la alerta de PagerDuty. Un servicio de checkout de un e-commerce estaba devolviendo errores 500 a un ritmo que haría que tu dashboard de monitoreo pareciera un sismógrafo. El servicio corría en EKS, la cola se acumulaba en SQS y nuestro equipo estaba repartido en tres zonas horarias: Ecuador, Argentina y España.

Esa noche me enseñó más sobre sistemas distribuidos Y equipos distribuidos que cualquier libro de arquitectura. Porque cuando producción está en llamas, no le importa dónde vive tu equipo.

Esto es lo que he aprendido tras años operando sistemas en producción con equipos que nunca comparten una oficina.

La observabilidad es el gran igualador

Cuando tu equipo está distribuido, nadie puede darle un toque en el hombro a un colega y preguntarle “oye, ¿esto te parece raro?”. La observabilidad llena ese vacío. Se convierte en el lenguaje común que tu equipo habla entre zonas horarias.

Estandarizamos OpenTelemetry para la instrumentación y el logging estructurado en todas partes. No porque esté de moda, sino porque cuando un ingeniero en Argentina toma un incidente a las 9 AM de su hora, necesita ver el MISMO contexto que vio el ingeniero en Ecuador a las 2 AM.

Lo que realmente funciona:

  • Logging estructurado con correlation IDs. Cada request recibe un trace ID que fluye por cada servicio, cada mensaje de cola y cada llamada a la base de datos. Cuando algo se rompe, sigues un solo ID a través de todo el sistema. Sin adivinar.
  • Métricas a nivel de negocio, no solo de infraestructura. El uso de CPU está bien. “Las órdenes por minuto cayeron un 40 % en los últimos 5 minutos” es accionable.
  • Dashboards que cuentan historias. Construimos los dashboards alrededor de los recorridos del usuario, no de los servicios. El dashboard “Checkout Health” muestra todo, desde la creación del carrito hasta la confirmación del pago, en una sola vista.
// Logging estructurado que de verdad ayuda durante los incidentes
// Mal: logger.error("Payment failed")
// Bien:
logger.error({
  event: 'payment_processing_failed',
  orderId: order.id,
  paymentProvider: 'stripe',
  errorCode: err.code,
  amount: order.total,
  currency: order.currency,
  traceId: context.traceId,
  customerSegment: order.customer.tier,
  retryAttempt: attempt,
  latencyMs: Date.now() - startTime,
});

La diferencia entre esas dos líneas de log es la diferencia entre “algo se rompió” y “sé exactamente qué se rompió, para quién y cómo arreglarlo”.

Gestión de incidentes entre zonas horarias

Gestionar incidentes con un equipo distribuido requiere una estructura explícita. No puedes depender de “todos se conectan a una llamada” cuando son las 3 AM para la mitad de tu equipo.

Este es el sistema que construimos:

  • Rotación de guardia follow-the-sun. Cada zona horaria tiene un on-call primario y uno secundario. El incidente se queda con quien lo tome hasta que se resuelva O se traspase explícitamente con un resumen escrito.
  • Canales de incidente, no hilos. Cada P1/P2 tiene un canal de Slack dedicado con un bot que registra la hora de cada actualización. Cuando despierta la siguiente zona horaria, lee el canal de arriba abajo y sabe exactamente en qué punto están las cosas.
  • La regla de los 15 minutos. Si no puedes identificar la causa raíz en 15 minutos, escala. No porque seas incompetente, sino porque estás gestionando el tiempo, no el ego. Cuanto antes traigas ayuda, antes se desbloquean los usuarios.

El traspaso es la parte más difícil. Usamos una plantilla:

## Incident Handoff — [INC-2024-0847]
**Status:** Investigating / Mitigated / Monitoring
**Impact:** ~12% of checkout attempts failing in US-East
**What we know:** SQS consumer lag spiking on payment-processor queue
**What we've tried:** Scaled consumers to 8 replicas (no improvement)
**Current hypothesis:** Stripe webhook endpoint is rate-limiting us
**Next steps:** Check Stripe dashboard, consider circuit breaker activation
**Key links:** [Dashboard](link) | [Logs query](link) | [Trace](link)

Esa plantilla nos ha ahorrado horas de “espera, ¿qué pasó mientras dormía?”.

Runbooks que realmente se usan

La mayoría de los runbooks son documentos de cementerio. Se escriben una vez, nunca se actualizan y nunca se consultan durante un incidente real. Yo también he sido culpable de esto. Esto es lo que cambió.

Los runbooks funcionan cuando están:

  • Enlazados directamente desde las alertas. Cada alerta de PagerDuty incluye un enlace al runbook. No lo buscas: está ahí, en la notificación.
  • Escritos como árboles de decisión, no como novelas. “Si la métrica X supera Y, haz Z.” No tres párrafos de contexto.
  • Probados con regularidad. Hacemos “simulacros de runbook” cada mes. Eliges un escenario, sigues el runbook y compruebas si todavía funciona. Si no, lo actualizas en el momento.
  • A cargo del equipo de guardia. Quienes usan los runbooks son quienes los mantienen. No un equipo de documentación aparte.
# runbook: payment-queue-lag
trigger: sqs_queue_depth > 10000 for 5m
steps:
  - check: "Are consumers running?"
    command: "kubectl get pods -l app=payment-consumer -n production"
    if_no: "kubectl rollout restart deployment/payment-consumer -n production"
    if_yes: "continue"
  - check: "Is Stripe responding?"
    command: "curl -o /dev/null -s -w '%{http_code}' https://api.stripe.com/v1/health"
    if_5xx: "Activate circuit breaker: kubectl set env deployment/payment-consumer STRIPE_CIRCUIT=open"
    if_200: "continue"
  - check: "Consumer error logs"
    command: "kubectl logs -l app=payment-consumer --tail=100 | grep ERROR"
    action: "Analyze error pattern and escalate if unknown"

“You Build It, You Run It” en la práctica

Esta filosofía suena genial en las charlas de conferencias. En la práctica, con un equipo distribuido, requiere infraestructura.

No puedes esperar que los desarrolladores “operen” lo que construyen si:

  • No tienen acceso a los logs de producción
  • No pueden hacer deploy sin abrir un ticket
  • Las alertas llegan a un equipo de operaciones aparte en lugar de al equipo que construye
  • No existe una forma segura de revertir sus cambios

Lo que implementamos en realidad:

  • Cada equipo es dueño de las alertas, dashboards y runbooks de su servicio. No ops. No SRE. El equipo que escribe el código.
  • Pipelines de deploy con análisis canary automatizado. Los ingenieros despliegan su propio código. El pipeline vigila las tasas de error durante 10 minutos. Si se disparan, hace rollback automáticamente. Esto le da a los desarrolladores confianza al desplegar sin necesidad de un guardián de operaciones.
  • Feature flags para todo lo que ve el usuario. Despliega el código cuando quieras. Activa las funcionalidades en horario laboral, cuando el equipo está despierto y atento.
# Estrategia de deploy canary en EKS
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: checkout-service
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout-service
  progressDeadlineSeconds: 600
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m

Patrones de comunicación asíncrona que funcionan

Los equipos distribuidos viven y mueren por su comunicación asíncrona. Las reuniones entre zonas horarias son caras y a menudo innecesarias. Esto es lo que reemplazó a la mayoría de las nuestras:

  • ADRs (Architecture Decision Records) para cada decisión importante. No porque amemos la documentación, sino porque cuando tu compañero en España despierta, necesita entender POR QUÉ elegimos SQS en lugar de Kafka, no solo que lo hicimos.
  • Videos en Loom en lugar de reuniones. Un recorrido en video de cinco minutos por un PR o un documento de diseño. Quien lo recibe lo ve a 1.5x en su horario. La respuesta, por escrito.
  • Documentos RFC con fechas límite explícitas. “Comentarios hasta el jueves EOD UTC.” Sin ambigüedad.
  • Standups asíncronos diarios en Slack. Tres líneas: qué hice, qué estoy haciendo y si tengo bloqueos. Se publican cuando empieza tu día. Se leen cuando empieza el de ellos.

La regla general: si la información tiene que sobrevivir más tiempo que la reunión, nunca debió ser una reunión.

El problema de la confianza al desplegar

El momento más aterrador en la operación de un equipo distribuido es desplegar un viernes por la tarde cuando el equipo que construyó la funcionalidad está a punto de desconectarse por el fin de semana.

Lo resolvimos con capas de confianza:

  • Feature flags: despliega el código apagado y actívalo cuando el equipo esté en línea
  • Canary releases: desplazamiento automático de tráfico con rollback automático
  • Ventanas de deploy: desplegamos en cualquier momento, pero las activaciones ocurren durante las horas de solapamiento (nuestra “ventana dorada” es de 10 AM a 1 PM UTC, cuando todas las zonas horarias están despiertas)
  • Notificaciones SNS: cada deploy dispara una notificación al canal del equipo con el resumen del commit, el autor y las instrucciones de rollback

El objetivo no es frenar los deploys. Es hacer que cada deploy sea aburrido. Los deploys aburridos significan que tu sistema está sano.

Historia de guerra: la cola silenciosa

Un sábado, nuestra cola de procesamiento de órdenes (SQS) dejó de consumir mensajes. Sin errores. Sin alertas. Solo… silencio. Las órdenes se acumulaban, pero todo se veía verde en nuestros dashboards.

¿La causa raíz? Un nodo de Kubernetes había sido acordonado (cordoned) durante el mantenimiento, los pods consumidores fueron desalojados y el Horizontal Pod Autoscaler tenía un mínimo de réplicas igual a cero. Así que cuando los pods fueron desalojados, el HPA estaba perfectamente conforme con cero réplicas procesando cero mensajes.

Lo que cambiamos después:

  • El mínimo de réplicas para los consumidores críticos ahora es 2. Siempre.
  • Agregamos una alerta de “cero consumidores”: si cualquier cola SQS crítica tiene cero consumidores activos por más de 2 minutos, se avisa de inmediato.
  • Runbook actualizado el mismo día.
  • Post-mortem compartido con todos los equipos, no solo con el afectado.

Sin culpas. Solo aprendizaje.

Construir una cultura de responsabilidad, no de culpa

Los post-mortems sin culpa no son solo una buena idea. Son un mecanismo de supervivencia para los equipos distribuidos. Y la razón es esta: si la gente teme que le echen la culpa, esconde los problemas. Si esconde los problemas, los problemas crecen. Y si los problemas crecen entre zonas horarias, te enteras cuando ya es un desastre.

Nuestra plantilla de post-mortem tiene una sola regla: ningún nombre de persona en la sección de causa raíz. Falló el sistema. No una persona.

Nos enfocamos en:

  • ¿Qué controles faltaban que permitieron que esto ocurriera?
  • ¿Qué señales pasamos por alto y por qué?
  • ¿Qué automatización lo habría detectado antes de que los usuarios lo notaran?

¿El resultado? Los ingenieros reportan de forma proactiva los casi-incidentes. Avisan “esto se siente frágil” sin miedo. Y todo el equipo mejora porque la información fluye libremente.

La documentación es una dependencia de producción

Lo diré sin rodeos: si tu sistema no puede ser operado por alguien que no estuvo en la sala cuando se construyó, tu documentación está fallando. Y con equipos distribuidos, SIEMPRE hay alguien que no estuvo en la sala.

Tratamos la documentación como tratamos las pruebas. No es opcional. No es “deseable”. Es una dependencia de producción. Si entregas un servicio nuevo sin un README actualizado, un runbook operativo y un diagrama de arquitectura, tu PR no se mergea.

¿Es trabajo extra? Sí. ¿Se paga solo la primera vez que alguien en otra zona horaria tiene que depurar tu servicio a las 3 AM? Totalmente.

La verdad honesta

Operar sistemas en producción con equipos distribuidos es más difícil que hacerlo en una oficina compartida. Quien te diga lo contrario te está vendiendo algo. Pero también es posible, sostenible y, cuando lo haces bien, increíblemente gratificante.

La clave no son las herramientas sofisticadas ni los procesos complejos. Es construir sistemas (tanto técnicos como humanos) que asuman que siempre hay alguien durmiendo, siempre hay alguien depurando y la documentación es lo primero que va a leer.

Haz que esa experiencia sea excelente y todo lo demás vendrá solo.