DEV Community

Alex Carter
Alex Carter

Posted on

Kubernetes: correos de prueba sin falsos positivos

Una alerta de email que aparece después de un despliegue no siempre significa que el despliegue rompió algo. En una guardia reciente, una prueba de verificación pasó en verde porque encontró un mensaje de la ejecución anterior. El pod nuevo estaba sano, pero la evidencia era vieja.

Ese tipo de fallo es caro: consume tiempo de SRE, genera desconfianza en las alertas y puede esconder un problema real. En Kubernetes, la solución no es simplemente esperar más. Es dar a cada prueba una identidad, un espacio de ejecución y una evidencia que se pueda relacionar con un solo intento.

El síntoma: una alerta que no corresponde al despliegue

El flujo parece razonable:

  1. CI crea un namespace temporal.
  2. Despliega la aplicación.
  3. Registra una cuenta de prueba.
  4. Espera el correo de confirmación.
  5. Abre el enlace y marca la prueba como correcta.

El problema aparece cuando el inbox es compartido o cuando el consumidor busca solo por asunto. Un reintento puede leer el mensaje antiguo. También puede leer un email de otro worker si dos pruebas usan la misma dirección.

El resultado es un falso positivo: el test confirma que existe un mensaje, no que existe el mensaje correcto para esta ejecución. A veces los logs no explica bien la diferencia porque muestran el asunto, pero no el identificador de la ejecución que originó el correo.

Por qué Kubernetes mezcla las señales

Los pods son efímeros, mientras que los sistemas de correo suelen tener una vida más larga. Si el namespace se borra y el inbox permanece, la siguiente prueba hereda estado que nadie ve en el manifiesto.

Hay tres fuentes comunes de mezcla:

  • Identidad débil: todos los workers usan qa@example.test o el mismo alias.
  • Limpieza tardía: el recurso de correo se elimina después de que ya empezó el siguiente job.
  • Correlación incompleta: se filtra por destinatario y asunto, pero no por un token único.

El namespace ayuda a aislar recursos de Kubernetes, pero no aísla por sí solo un buzón externo. La frontera de aislamiento debe continuar hasta la fixture de email y sus artefactos.

Diseñar una prueba con identidad y evidencia

Para cada ejecución genero un identificador corto, por ejemplo run-7f31. Ese valor se usa en cuatro sitios:

  1. nombre del namespace;
  2. dirección o alias de prueba;
  3. encabezado o token de correlación;
  4. nombre del archivo de evidencia.

No hace falta que el token sea secreto. Sí debe ser único dentro de la ventana en la que los mensajes pueden llegar. El consumidor no acepta el primer mensaje que coincide: exige que el identificador sea igual y que la marca temporal esté dentro de la ejecución.

La evidencia mínima que guardo es el identificador del job, el namespace, la dirección creada, el ID del mensaje, la hora de recepción y el resultado de la aserción. Si la prueba falla, ese pequeño recibo permite distinguir entrega lenta, mensaje equivocado y error de aplicación.

Este enfoque sigue la misma idea de unos contratos de correo que se pueden probar: primero se define qué significa una observación válida y luego se implementa el adaptador.

Un patrón de implementación sencillo

El job de CI puede pasar el identificador como variable inmutable:

env:
  TEST_RUN_ID: run-${{ github.run_id }}-${{ strategy.job-index }}
  TEST_NAMESPACE: email-${{ github.run_id }}-${{ strategy.job-index }}
Enter fullscreen mode Exit fullscreen mode

El despliegue recibe TEST_RUN_ID y lo incluye en la configuración de la aplicación. El worker que consulta el correo usa una ventana limitada, por ejemplo cinco minutos, con un intervalo fijo. Cada consulta registra cuántos mensajes candidatos vio y por qué descartó los demás.

Un pseudoflujo de lectura sería:

hasta que venza el plazo:
  mensajes = buscar(destinatario, desde=inicio)
  para cada mensaje:
    si token != TEST_RUN_ID: continuar
    guardar ID y timestamp
    validar enlace
    devolver éxito
  esperar intervalo
devolver error con candidatos observados
Enter fullscreen mode Exit fullscreen mode

Los contratos claros para tareas de email ayudan a separar la espera, la validación y la limpieza. Así, un timeout no se convierte en un reintento que borra la evidencia del primer intento.

Si el proveedor no permite crear una dirección por ejecución, se puede usar un buzón compartido con un token fuerte y una política explícita de limpieza. Es menos aislado, pero sigue siendo mejor que filtrar solo por asunto. En un experimento alguien escribió “tem email” en una variable; no lo conviertas en una regla de búsqueda ni en una keyword primaria.

Qué mirar cuando la prueba falla

Antes de reiniciar el job, reviso cuatro preguntas:

  • ¿El correo fue enviado con el TEST_RUN_ID esperado?
  • ¿El consumidor empezó a buscar antes de que terminara la transacción de envío?
  • ¿El mensaje encontrado pertenece a la misma ejecución y al mismo namespace?
  • ¿La limpieza dejó disponible un artefacto que explique el fallo?

Un error útil debe decir timeout esperando run-7f31, no solamente email not found. También conviene medir el tiempo entre envío y recepción. Un p95 que crece puede explicar muchos timeouts antes de que el servicio de correo esté completamente caído.

Checklist antes de confiar en el resultado

  • Crear un ID único por ejecución y worker.
  • Propagarlo a namespace, dirección, mensaje y artefactos.
  • Rechazar mensajes antiguos o de otra ejecución.
  • Limitar la ventana de polling y hacer los reintentos visibles.
  • Conservar el ID del mensaje y la razón de cada descarte.
  • Limpiar el namespace y la fixture después de guardar evidencia.
  • Probar también la ruta de timeout y de correo duplicado.

La lección es pequeña pero importante para SRE: un test de email no debe responder solo si llegó algo. Debe demostrar que llegó la señal correcta, a la ejecución correcta, dentro de un límite que podamos explicar. Con esa disciplina, Kubernetes deja de ser el sospechoso automático y las alertas vuelven a ser útiles.

Top comments (0)