Kubernetes: un contrato de fallos para emails de prueba

# kubernetes# sre# devops# testing
Kubernetes: un contrato de fallos para emails de pruebaAlex Carter

Un patrón SRE para aislar buzones temporales en Kubernetes, medir fallos de email y limpiar cada ejecución sin sorpresas.

En un pipeline de CI, un email de prueba parece un detalle pequeño. Hasta que una ejecución reutiliza un buzón temporal de ayer, recibe un mensaje atrasado y termina verde por casualidad. O hasta que cinco jobs comparten la misma dirección y uno consume el código de verificación del otro.

En equipos que operan Kubernetes, el problema no se resuelve solamente con generar correo desechable. La pregunta importante es: ¿qué contrato de fiabilidad tiene ese recurso dentro de una ejecución? Si no podemos responderlo, el test está observando un estado ambiguo.

Este es el patrón que uso para pensar estos incidentes: un buzón por ejecución, una identidad trazable, límites explícitos y una limpieza que tenga dueño.

El síntoma: el test verde que deja basura

Imaginemos un deployment efímero para probar un flujo de registro. El job crea un usuario, espera el email y confirma el enlace. En el dashboard solo vemos que el paso final falló después de 90 segundos. El pod sigue vivo, el buzón conserva mensajes antiguos y el namespace tiene varios secretos de pruebas.

La investigación suele descubrir una de estas causas:

  • Dos ejecuciones apuntan al mismo buzón.
  • El consumidor busca el primer mensaje, no el mensaje de su propia ejecución.
  • El polling no tiene un límite coordinado con el timeout del job.
  • La limpieza ocurre solo cuando el test termina correctamente.
  • Los logs muestran el email completo, y el diagnóstico crea un problema de privacidad.

El error no es solo de la aplicación. Es un error de contrato entre CI, Kubernetes y el proveedor de email. Un SRE necesita poder distinguir un fallo real del producto, un fallo de infraestructura y un dato de prueba que llegó tarde.

Qué debe prometer el contrato

Antes de crear recursos, escribo cuatro promesas simples:

  1. Aislamiento: cada ejecución recibe un identificador único y no lee mensajes de otra ejecución.
  2. Caducidad: el buzón, el secreto y los pods tienen un TTL o una limpieza garantizada.
  3. Observabilidad: cada espera registra el run ID, el intento, la latencia y la razón de salida, nunca el contenido privado.
  4. Clasificación: el resultado diferencia message_not_found, provider_error, test_assertion y cleanup_error.

El run ID debe viajar por todos los límites: etiqueta del namespace, nombre de la fixture, metadato del mensaje y contexto del job. Una etiqueta como run=20261006-0315-a7f2 es mas útil que un nombre genérico como email-test.

También conviene definir qué significa “recibido”. No es lo mismo que el proveedor acepte el mensaje, que la API lo devuelva, o que el cuerpo contenga el enlace esperado. Esta distinción ahorra bastante tiempo cuando el incidente ocurre de madrugada.

Un diseño sencillo por ejecución

El job de CI puede crear una fixture y pasar solo su referencia al pod de pruebas:

env:
  TEST_RUN_ID: "20261006-0315-a7f2"
  MAILBOX_REF: "ci/20261006-0315-a7f2"
Enter fullscreen mode Exit fullscreen mode

El contenedor que consulta el email no debería conocer credenciales globales ni buscar por asunto sin más. Recibe un selector de ejecución y aplica una fecha mínima de creación. Si el proveedor soporta un identificador de mensaje, se guarda ese recibo antes de validar el contenido.

En Kubernetes, el namespace efímero puede tener un ResourceQuota pequeño y un ttlSecondsAfterFinished donde aplique. Para los recursos que no tienen TTL nativo, un cleanup job debe ejecutarse en una ruta finally, incluso si el test fue cancelado. La limpieza no deberia depender de que el runner conserve el workspace.

Un punto práctico: conserva un recibo mínimo, no el email completo. Por ejemplo, guarda el hash del mensaje, el timestamp, el run ID, el resultado y la latencia. Así el equipo puede reconstruir la secuencia sin convertir los logs en un archivo de datos personales.

Si el flujo también usa automatización con agentes LLM, aplica el mismo contrato: la herramienta recibe un alcance limitado y no puede leer bandejas de otras ejecuciones. Un agente que reintenta sin presupuesto puede convertir un test lento en una tormenta de consultas.

Métricas y límites que sí ayudan

No hace falta empezar con veinte métricas. Estas cuatro suelen explicar el incidente:

  • email_fixture_create_seconds: cuánto tarda en estar disponible la fixture.
  • email_poll_attempts: cuántas consultas necesita cada ejecución.
  • email_delivery_age_seconds: edad del mensaje cuando se consume.
  • email_cleanup_result: éxito, reintento o abandono.

Pon límites concretos: por ejemplo, 12 intentos cada 5 segundos y un timeout total de 75 segundos. El número exacto depende del proveedor, pero debe ser menor que el timeout general del job para dejar tiempo a cerrar recursos. Si se alcanza el límite, el error debe decir qué se esperaba, desde cuando y con qué run ID.

Los dashboards deberían agrupar por causa, no solo por estado rojo o verde. Una tasa alta de provider_error pide una investigación distinta a una tasa alta de message_not_found. Para una revisión rapida, un enlace a la ejecución y un recibo redacted valen mas que una captura del pod.

En la interfaz que dispara el flujo, ayuda validar emails sin esperar a ciegas: estados como “creando fixture”, “esperando mensaje” y “limpiando” hacen visible dónde se consume el tiempo. No arregla el backend, pero evita que el operador repita clicks y multiplique ejecuciones.

Checklist para el siguiente incidente

Cuando falle una prueba de email en Kubernetes, revisa en este orden:

  1. ¿El run ID aparece en el namespace, la fixture y el recibo?
  2. ¿La ejecución pudo leer mensajes de otro run?
  3. ¿El reloj del consumidor y el del proveedor están razonablemente alineados?
  4. ¿El timeout de polling deja margen para limpiar?
  5. ¿El error identifica proveedor, aplicación o infraestructura?
  6. ¿La ruta de cancelación eliminó pods, secretos y buzones?
  7. ¿Los logs evitan direcciones y cuerpos completos?

Si aparece una búsqueda manual como tepm mail com o tamp mail com, trátala como una señal de que alguien está intentando resolver el problema fuera del flujo controlado. No es una estrategia operativa: es deuda de observabilidad.

El objetivo no es que cada test sea perfecto. Es que cada fallo sea acotado, explicable y barato de retirar. Cuando un buzón temporal tiene dueño, caducidad y recibo, Kubernetes deja de ser el lugar donde se esconden los tests intermitentes y pasa a ser parte de la evidencia.