ADR-034: Kafka en pod y Confluent Cloud siempre arriba, con un cambio rápido entre ellos
aceptado el 15/11/2026. Requisito de Andrés (15/11/2026, 14:15 hora de Colombia): "kafka debe estar funcionando siempre en los dos ambientes: pod y en confluent. El switch debe ser algo muy rápido cuando sea necesario como en este momento." Escrito por el carril `nube`. Complementa ADR-028 y no lo reemplaza: sigue habiendo dos modos y un interruptor, `KAFKA_MODE`; cambia qué hace el interruptor.
Contexto
ADR-028 dejó dos modos de Kafka: un Kafka de un nodo en un pod del clúster y Confluent Cloud. Con KAFKA_MODE se elegía cuál instalar. Pasar a Confluent desinstalaba el Kafka del pod con su disco; volver lo instalaba vacío y creaba los tópicos. Cada cambio era un make k8s-eks-deploy completo: el del 15/11/2026 de vuelta al pod tardó unos 15 minutos de reloj (tiempo exacto por medir).
El 15/11/2026, con la capa en Confluent, apareció un defecto de código: los consumidores de wms-adapter y oms-adapter se crean sin SASL_SSL. Contra Confluent no consumen; en el pod no se nota, porque ahí no hay TLS (platform/k8s/README.md, "Defectos y pendientes conocidos"). Resultado: stock stale y órdenes confirmadas sin registrar en el OMS. Volver al pod como respaldo costó un redespliegue completo, con el Kafka del pod reinstalado vacío. Un respaldo que tarda varios minutos y arranca vacío no sirve en medio de un demo.
Opciones
| Opción | A favor | En contra |
|---|---|---|
| A. Como ADR-028: un Kafka a la vez, el cambio reinstala | Menos memoria y menos costo en el modo pod | Un despliegue completo por cambio (unos 15 minutos de reloj el 15/11/2026, sin medición exacta); el Kafka que vuelve arranca vacío; el respaldo está frío |
B. Los dos siempre arriba; KAFKA_MODE solo decide a cuál se conectan las cargas; una orden cambia la conexión |
El respaldo está caliente, con sus tópicos; el cambio toca solo las cargas con Kafka, en paralelo | Memoria del Kafka del pod siempre ocupada; Confluent cobra aunque no se use; dos Kafka que vigilar |
| C. Replicar entre los dos (MirrorMaker 2 o Cluster Linking) | El cambio no pierde mensajes ni offsets | Otra pieza que operar; Cluster Linking no existe en Confluent Basic; demasiado para el demo |
Decisión
Opción B.
- Dos Kafka arriba. La release
kafkase instala siempre, en kind y en EKS, con sus tópicos desde el contrato (factor 1). La raízkafkamantiene Confluent encendido. La salida de la malla hacia Confluent (optional/egress-confluent.yaml) queda siempre aplicada en EKS.make k8s-eks-deployya no desinstala nada al pasar aconfluent. - El interruptor solo decide la conexión.
KAFKA_MODE=pod: sin SASL akafka.data.svc.cluster.local:9092.KAFKA_MODE=confluent: SASL_SSL con la llave de cada carga (<carga>-kafka). Igual que en ADR-028. - Cambio rápido.
make kafka-switch MODE=pod|confluent(platform/k8s/scripts/k8s.sh kafka-switch eks) hace unhelm upgradeen paralelo solo de las releases con Kafka (secrets.kafka: trueen sus valores; hoy son ocho: los cinco servicios y los tres adaptadores). Cada una cambia su ConfigMap y el montaje de<carga>-kafka, y su Deployment se reinicia de forma ordenada. No instala ni desinstala nada y no toca OpenTofu. La dirección de Confluent sale del secreto de una carga y el registro de imágenes, de la cuenta: la orden no necesita más argumentos. Al terminar imprime cuánto tardó. - Drenar antes de salir del pod. Saliendo del pod, la orden espera a que los grupos de consumidores no tengan atraso (
kafka-consumer-groups.sh --describe --all-groups, columna LAG) antes de reconfigurar. Por defecto espera hasta 120 segundos (DRAIN_TIMEOUT).DRAIN=offcambia igual; por ejemplo, cuando el Kafka de origen está roto. Saliendo de Confluent no se mide el atraso desde el clúster: se mira en la consola de Confluent (usuario de solo lectura, rol Operator) antes de cambiar.
Qué pasa con una orden a medio camino
Los mensajes y los offsets no se migran entre los dos Kafka.
- Un evento ya escrito en el outbox de un servicio y todavía sin publicar sale al Kafka nuevo cuando el servicio arranca con la conexión nueva: el outbox está en PostgreSQL, no en Kafka.
- Un evento publicado en el Kafka viejo y todavía sin consumir se queda allá. Si era un paso de la saga (por ejemplo, el pago aprobado de una orden), esa orden queda detenida en su estado hasta que el paso vuelva a ocurrir: un callback repetido de la pasarela, la consulta activa de payment-service o una revisión manual.
- Las lecturas no se pierden: pricing-service e inventory-service tienen su respaldo en la base y en Redis, y los adaptadores vuelven a publicar su marca de sincronización.
Cómo se cambia sin perder una orden: se detiene la entrada nueva unos segundos (sin compras en la tienda ni carga de k6) y se deja que el drenaje del paso 4 lleve el atraso a cero. Recién ahí se reconfigura. Con atraso cero y el outbox en la base, ningún evento queda varado.
Qué se sacrifica
- Memoria del clúster: el Kafka del pod reserva su memoria y su disco
gp3de 10 GiB aunque las cargas usen Confluent. Medido el 15/11/2026, antes de sumar la pila de observabilidad: los dos nodosm7i-flex.xlargetenían reservados 4.240 MiB (28%) y 3.628 MiB (24%) con la capa completa y el Kafka del pod. Caben los dos Kafka. - Costo: Confluent cobra 0,70 dólares por hora (6 eCKU de las 151 particiones) también mientras las cargas usan el pod. Hoy va contra el crédito de 400 dólares.
- El cambio no lleva mensajes en vuelo ni offsets (ver arriba): un cambio sin drenar puede dejar una saga detenida.
- Dos Kafka que vigilar, y el defecto de un modo solo aparece si alguien usa ese modo. Por eso la prueba del borde en EKS (
EDGE_ENV=eks node tests/k8s/edge-e2e.mjs) comprueba la frescura del stock y la orden registrada en el OMS, y se corre después de cada cambio. - Kind no tiene Confluent: el cambio en los dos sentidos solo se prueba en EKS.
Verificación
make verify(k8s-verify): EKS renderizado en los dos modos con la releasekafkaendata;check-catalog.mjslo exige en los dos.- En EKS:
make kafka-switch MODE=confluentyMODE=pod, cada uno seguido deEDGE_ENV=eks node tests/k8s/edge-e2e.mjs. Medido el 15/11/2026: pod a confluent en 195 s; confluent a pod en 28 s, con la prueba en 23 de 23 (detalle enplatform/k8s/README.md). Antes de este ADR, cada cambio era un despliegue completo.