DEV Community

Alex Carter
Alex Carter

Posted on

Kubernetes: un contrato de correo para CI

Un pipeline puede tener todos sus pods en Running y terminar en rojo porque nunca encontró el correo de verificación. La primera reacción suele ser reiniciar el worker o aumentar el timeout. A veces ayuda, pero mas frecuente solo oculta la causa.

En equipos que mantienen pruebas de registro, recuperación de cuenta o invitaciones, el correo debe tener un contrato parecido al de una base de datos de prueba. Cada job necesita saber qué buzón puede usar, durante cuánto tiempo, qué mensaje espera y qué evidencia debe dejar si falla. Sin ese contrato, una prueba de email se convierte en una espera larga y poco explicable.

El problema no es solo el correo

La entrega de un mensaje cruza varias capas: el test crea un usuario, la aplicación publica un evento, un worker procesa la tarea, el proveedor acepta el mensaje y el inbox lo expone a la prueba. Kubernetes puede estar sano mientras cualquiera de las otras capas está detenida.

Los síntomas se parecen bastante:

  • el mensaje no se creó porque falló el evento de signup;
  • el worker lo procesó con credenciales del entorno equivocado;
  • el correo llegó a otro buzón porque dos jobs compartieron dirección;
  • el mensaje existe, pero la consulta empezó antes de la hora correcta;
  • el inbox respondió con un error temporal y el test lo tomó como “no existe”.

Por eso conviene hacer visibles los estados pequeños del inbox: “esperando”, “recibido”, “procesando” y “expirado” son más útiles que un único spinner.

Define un contrato por job

Antes de crear el recurso de Kubernetes, genera un run_id único. Ese identificador debe viajar por el usuario sintético, el evento de email, los logs y la consulta al buzón. La dirección de prueba puede incorporar el ID o estar asociada a él en una tabla de leases.

El contrato mínimo tiene cinco campos:

  1. Propietario: qué job y qué namespace pueden leer el buzón.
  2. Vigencia: cuándo empieza y cuándo caduca la lease.
  3. Mensaje esperado: destinatario lógico, tipo de evento y versión de plantilla.
  4. Reloj: hora desde la que el test acepta mensajes, no solo el último mensaje visible.
  5. Limpieza: quién libera la dirección y qué ocurre si el pod muere.

Esto evita el fallo clasico de “el test pasó porque encontró un correo viejo”. También permite distinguir un problema de aplicación de un problema del proveedor.

Qué debe guardar el job

No hace falta copiar el contenido completo del email a los logs. Para investigar basta con guardar datos seguros y correlacionables:

  • run_id y nombre del job;
  • namespace, commit y entorno;
  • timestamp de solicitud y timestamp de primera consulta;
  • identificador del mensaje, si el proveedor lo ofrece;
  • estado observado: waiting, received, consumed o expired;
  • contador y razón de cada reintento.

El cuerpo del mensaje puede quedar fuera del log, sobre todo si contiene tokens. En su lugar, el test puede verificar el asunto, el host esperado del enlace y un hash o identificador de la plantilla. En una guardia, esa evidencia es mas accionable que cien líneas de HTML.

Investiga por capas

Cuando el pipeline falla, sigue el camino del evento y no el orden de los recursos en kubectl.

Primero confirma que la aplicación creó el evento para el run_id correcto. Luego revisa si el worker recibió la tarea, si la reintentó y con qué configuración. Después comprueba la respuesta del proveedor. Solo al final consulta el inbox. Si el mensaje fue aceptado por el proveedor pero no aparece, el problema ya no está en el deployment que generó el evento.

Una métrica sencilla ayuda mucho: registra por separado el tiempo de creación del evento, el tiempo hasta la aceptación del proveedor y el tiempo hasta la lectura por el test. Así se ve si el retraso está en la cola o en la consulta. En CI suele pasar rapido que todos miren el mismo timeout, aunque cada etapa tenga un presupuesto distinto.

También revisa el drift de configuración: detectar drift antes de culpar al pod evita perder una hora buscando un bug de aplicación cuando el secreto del namespace cambió.

Un patrón simple en Kubernetes

El job puede recibir el contrato como variables inmutables y escribir su evidencia en un artefacto de CI:

env:
  - name: EMAIL_RUN_ID
    valueFrom:
      fieldRef:
        fieldPath: metadata.uid
  - name: EMAIL_LEASE_SECONDS
    value: "600"
  - name: EMAIL_EXPECTED_ENV
    value: "staging"
Enter fullscreen mode Exit fullscreen mode

El metadata.uid identifica al pod, pero el sistema de pruebas debe crear además un run_id estable para todo el job si hay varios pods. No uses solo el nombre del pod: los reintentos generan nombres distintos y complican el diagnóstico.

La limpieza debe vivir en un finally del test y también en un reconciler periódico. Si el pod desaparece antes de liberar el buzón, el reconciler recupera la lease cuando vence. El pod no tiene permisos suficiente para borrar recursos de otro namespace; limita el RBAC al recurso de leases y al namespace del job.

Cómo distinguir timeout de entrega y consumo

Un timeout de entrega significa que no hay evidencia de aceptación o de recepción. Un timeout de consumo significa que el mensaje existe, pero el test no pudo leerlo o no reconoció su forma. Son incidentes diferentes.

Una tabla de decisión corta:

Evidencia Hipótesis principal Siguiente comprobación
No existe evento Fallo en la aplicación Logs del endpoint y del outbox
Evento sin aceptación Worker, cola o credencial Reintentos y configuración
Aceptado sin mensaje Proveedor o dirección ID del mensaje y buzón
Mensaje visible sin match Selector del test run_id, asunto y timestamp
Mensaje consumido por otro job Lease compartida Propietario y namespace

Si aparece un término de búsqueda raro como temp org mail en documentación o tickets, trátalo como texto de entrada, no como una señal operativa. Lo que importa para la alerta es la identidad del job y la transición del mensaje.

Checklist de guardia

  • [ ] ¿Cada job tiene una dirección o lease exclusiva?
  • [ ] ¿El run_id aparece en eventos, logs y consultas?
  • [ ] ¿El test ignora mensajes anteriores al inicio del run?
  • [ ] ¿Se separan tiempos de cola, entrega y lectura?
  • [ ] ¿Los reintentos conservan la razón original?
  • [ ] ¿La lease expira aunque muera el pod?
  • [ ] ¿Los logs evitan tokens y cuerpos completos?
  • [ ] ¿Existe un artefacto pequeño con la evidencia final?

Preguntas rápidas

¿Debo aumentar siempre el timeout?

No. Aumentarlo puede esconder una cola atascada o una dirección compartida. Primero mide cada tramo y ajusta el presupuesto solo donde haya variación legítima.

¿Necesito un buzón por pod?

No necesariamente. Necesitas un propietario inequívoco por job. Un buzón puede servir a varios pods coordinados si la lease y el selector de mensajes lo permiten; para pruebas paralelas independientes, un buzón por ejecución suele ser mas simple.

¿Qué hace que el contrato sea útil para SRE?

Que permita responder tres preguntas sin repetir el pipeline: qué esperaba el job, qué ocurrió realmente y quién tenía la propiedad de la dirección. Si esas respuestas quedan en un artefacto seguro, el siguiente incidente se resuelve con evidencia, tambien cuando el pod ya no existe.

Top comments (0)