DEV Community

Alex Carter
Alex Carter

Posted on

Kubernetes: límites de confianza para emails CI

Un job de CI que envía un email de verificación parece una tarea pequeña. Hasta que dos ejecuciones comparten buzón, un pod conserva un token demasiado tiempo o alguien revisa un fallo y descubre que los logs no dicen qué mensaje fue aceptado. Entonces el problema deja de ser "el correo no llegó" y pasa a ser un incidente de límites de confianza.

En Kubernetes, la solución no es solo crear un namespace por equipo. Cada ejecución necesita una frontera entre el job, el buzón de prueba y la evidencia que se conserva. Esa separación hace mas fácil saber si falló la aplicación, el proveedor de email o el propio pipeline.

El síntoma: un email que cruza de namespace

Imagina dos jobs que prueban el mismo flujo de registro. El primero crea una cuenta de correo temporal y espera el enlace. El segundo empieza unos segundos después, pero reutiliza una variable de entorno compartida. Si el lector busca "el mensaje más reciente", puede consumir el correo equivocado sin levantar una alerta clara.

También hay fallos menos visibles. Un pod termina, pero su Secret queda disponible para el siguiente job. Una cuenta de servicio puede leer todos los buzones de QA cuando solo necesitaba uno. O un retry conserva el mismo identificador y acepta un mensaje tardío de la ejecución anterior. El test queda rojo de forma intermitente y el diagnostico empieza con conjeturas.

La señal útil es que no existe un contrato de propiedad: nadie puede responder con precisión quién crea el buzón, quién puede leerlo, cuándo expira y qué evidencia debe quedar.

Dibujar el límite de confianza

Antes de tocar YAML, dibujo tres zonas:

  1. Job efímero: ejecuta la prueba y conoce el token de correlación.
  2. Proveedor de email: recibe mensajes y expone solo la bandeja asignada.
  3. Evidencia: guarda estado, timestamps y razones de aceptación, pero no necesita el cuerpo completo.

El namespace ayuda, pero no es una política completa. Un pod comprometido podría usar credenciales montadas en el mismo namespace para acceder a recursos que no corresponden. Por eso el identificador del buzón debe incluir run_id, el token debe ser único y el lector debe verificar ambos valores.

Un contrato pequeño puede verse así:

{
  "run_id": "ci-1842-attempt-2",
  "mailbox_id": "qa-7f31",
  "correlation_token": "signup-91ab",
  "expires_at": "2026-10-10T06:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

Si el equipo mantiene un arnés reproducible para flujos de email, conviene que este contrato sea una entrada explícita del arnés, no una convención escondida en un script. Así el fallo se puede repetir con los mismos límites.

Permisos mínimos para el job

La cuenta de servicio del runner debería poder solicitar una fixture, leer su propio buzón y marcarla como consumida. No debería listar buzones de otros jobs, cambiar políticas globales ni conservar acceso después del expires_at.

En Kubernetes, una política orientativa podría separar el acceso de lectura del acceso de limpieza:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ci-mail-reader
rules:
  - apiGroups: ["mail.example.internal"]
    resources: ["mailboxes/messages"]
    verbs: ["get", "list"]
Enter fullscreen mode Exit fullscreen mode

Los nombres dependen del operador o del adaptador que use el equipo; lo importante es que list no termine exponiendo todos los buzones por defecto. Para la limpieza, prefiero un worker separado con permisos limitados y una lease corta. Si el runner desaparece, ese worker puede cerrar la fixture sin devolver al job permisos permanentes.

También revisaría la interfaz que dispara el flujo. Un frontend que valida email sin mover el layout reduce ruido para la persona, pero no sustituye la validación de ownership en el backend. El pod debe rechazar un mensaje cuyo run_id no coincida, aunque el asunto parezca correcto.

Guardar evidencia sin guardar de más

Cuando una prueba falla, la evidencia mínima suele ser suficiente:

  • run_id y número de intento
  • identificador redactado del buzón
  • hora de creación y de expiración
  • ids o hashes de los mensajes candidatos
  • resultado de cada verificación
  • estado de limpieza: pending, done o expired

No guardaría el cuerpo completo del email ni el enlace de verificación en logs normales. Si hace falta inspeccionarlo, conserva una copia redactada con TTL corto y acceso de solo lectura. Un tem email escrito por una integración vieja puede aparecer en datos de prueba, pero no debería ser un selector ni un permiso.

Un generador de correo temporal puede servir como contexto para pruebas manuales, pero no debe convertirse en una dependencia del contrato de producción. La prueba tiene que funcionar aunque cambie el proveedor.

Checklist de revisión

  • ¿Cada job recibe un buzón aislado y un run_id único?
  • ¿El lector verifica buzón, token y ventana de tiempo?
  • ¿La cuenta de servicio tiene permisos mínimos?
  • ¿Un retry crea una nueva tentativa en vez de reciclar secretos?
  • ¿Existe una limpieza independiente si el pod muere?
  • ¿Los logs evitan cuerpos, tokens y enlaces completos?
  • ¿La evidencia permite distinguir entrega, correlación y autorización?
  • ¿El namespace se usa como una frontera adicional, no como la única defensa?

Preguntas rápidas

¿Necesito un namespace por ejecución?

No siempre. Para muchos equipos basta con un namespace por entorno y una identidad por job, siempre que el proveedor de email aplique aislamiento por mailbox_id. Un namespace por ejecución añade coste operativo y no arregla permisos demasiado amplios por sí solo.

¿Qué hago cuando llega un mensaje tarde?

Marca la ejecución como expirada, registra el mensaje como rechazado y no lo entregues a un retry nuevo. Es mas importante conservar la razón del rechazo que forzar un estado verde.

¿Puedo probar el flujo con una bandeja compartida?

Solo para una prueba local muy acotada. En CI, la bandeja compartida mezcla evidencia y hace que una prueba dependa del orden de otra. Sale barato al principio, pero despues cuesta bastante reconstruir el incidente.

La regla que uso es sencilla: cada email de prueba debe tener un dueño, una expiración y una prueba de pertenencia. Kubernetes ayuda a ejecutar esa disciplina; no puede inventarla por nosotros.

Top comments (0)