ADR-024: despliegue de las cargas en Kubernetes y Kafka en Confluent Cloud
Aceptado el 15/11/2026 por Andrés (propuesto el 13/11/2026 por el carril `infra`). Complementa ADR-022 y no cambia ninguna decisión registrada. Complementado por ADR-028 (Kafka en pod). La decisión 9 (particiones) se cerró el 15/11/2026 con la opción A; ver "Cierre de la decisión 9" al final.
Contexto
ADR-022 deja la cuenta, el clúster y los secretos en OpenTofu, pero no dice cómo llegan las cargas al clúster ni de dónde sale Kafka en la nube. F7 pide las dos cosas, y el demo debe poder ensayarse completo sin gastar (docs/00-enfoque.md). Hay que fijar: cómo se empaquetan y despliegan las cargas, dónde se montan los secretos, qué imagen va a cada entorno, cómo se crean los tópicos y permisos en Confluent y cómo se revisa la cuenta antes de encender.
Al probar en kind con el volumen que inyecta EKS Pod Identity apareció un choque con el contrato de plataforma (regla 8 de CLAUDE.md): el token de Pod Identity se monta en /var/run/secrets/pods.eks.amazonaws.com/serviceaccount, /var/run es un enlace a /run en las imágenes del proyecto, y con los secretos de solo lectura en /run/secrets el contenedor no arranca.
Decisiones
| # | Tema | Decisión | Alternativa descartada y por qué |
|---|---|---|---|
| 1 | Empaquetado | Un chart genérico workload para todas las cargas (Deployment, Service, ServiceAccount, ConfigMap, SecretProviderClass, PodDisruptionBudget) y charts propios solo para postgres, redis, kafka, namespaces e istio-routing. Las releases y su orden en platform/k8s/releases.txt; los valores por carga y por entorno en platform/k8s/values |
Un chart por servicio: trece copias de lo mismo; un cambio de plataforma (sondas, apagado, seguridad) se haría trece veces. Kustomize: no tiene valores por entorno con condiciones y el resto del proyecto ya usa Helm |
| 2 | Ensayo | El despliegue completo se ensaya en kind (make k8s-kind-up, k8s-images, k8s-kind-deploy y k8s-kind-test, que corre tests/k8s/kind-e2e.mjs) con los mismos charts y los valores de env/kind.yaml. El token de Pod Identity se simula con el mismo volumen y ruta que inyecta EKS |
Probar solo en EKS: cada error costaría horas de clúster. Minikube: kind ya es la herramienta de F0 |
| 3 | Carpeta de secretos | /etc/andes/secrets, junto a /etc/andes/config, por PLATFORM_SECRETS_DIR, que Java y Node ya leen. El contrato de plataforma decía /run/secrets |
/run/secrets: choca con el token de Pod Identity (ver Contexto). Desactivar el token: sin él la carga no puede leer Secrets Manager |
| 4 | Secretos en EKS | SecretProviderClass del Secrets Store CSI Driver con provider: aws y usePodIdentity: "true", un archivo por clave (jmesPath con la clave entre comillas: algunas llevan guion). Sin Secret de Kubernetes sincronizado |
Sincronizar a Secret de Kubernetes: el valor quedaría en etcd y visible para quien lea Secrets en el espacio de nombres |
| 5 | Claves compartidas | Una clave que usan dos cargas (la de cada base con postgres, la de cada usuario con redis, las del legado y la firma de la pasarela con mocks) se genera una vez y se escribe en los dos secretos (shared en el catálogo) |
Que una carga lea el secreto de otra: rompe "cada rol lee solo lo suyo" de ADR-022 |
| 6 | Cifrado por campo | Una llave de KMS por carga que cifra campos (order-pii para order-service, webhook-secrets para webhook-service), con el permiso solo en el rol de Pod Identity de esa carga |
La llave del demo: cualquier carga con permiso sobre ella podría descifrar los datos personales de las órdenes |
| 7 | Imágenes | Etiqueta igual al hash del árbol de git de la carpeta de la carga (git rev-parse HEAD:<carpeta>): mismo código, misma etiqueta, compatible con ECR inmutable. make images-push exige que la carpeta esté en un commit |
latest o la fecha: ECR inmutable las rechaza o no dicen qué código corre. Hash del commit: cambia aunque la carga no cambie |
| 8 | Kafka en la nube | Confluent Cloud Basic en AWS us-east-1, raíz kafka aparte. Tópicos, particiones y configuración salen de scripts/topics-plan.js; permisos de scripts/topics-plan.js --acl (groupId lee, x-producers escribe). Una cuenta de servicio y una llave por carga, con ACL de mínimo privilegio, en el secreto <carga>-kafka |
Amazon MSK: el clúster mínimo cuesta más por hora y pide su propia red. Kafka dentro de EKS: un nodo más grande y sin el servicio administrado que prevé 04-stack.md |
| 9 | Particiones en Confluent | Abierta, decide Andrés. Basic cobra 0,14 dólares por eCKU y hora (la primera gratis) y admite 30 particiones por eCKU. A: las 151 del contrato, 6 eCKU, 0,70 por hora. B: tope de 2 por tópico solo en el demo, 3 eCKU, 0,28 por hora. C: tope de 1, 0,14 por hora | B y C cambian las particiones por ambiente, que ADR-017 hoy no admite (solo las réplicas). El código ya trae la variable max_partitions_per_topic (por defecto, sin tope) |
| 10 | Llaves de Confluent en el estado | La parte secreta de cada llave de Confluent queda en el estado cifrado de kafka: el proveedor de Confluent no tiene recursos efímeros. Es la segunda excepción a "ningún secreto en el estado" de ADR-022, junto a la llave del certificado de origen |
Crear las llaves a mano y pasarlas como secreto externo: un paso fuera del código y nueve llaves que rotar a mano |
| 11 | Seguridad de pods | restricted impuesto en kind. En EKS, privileged impuesto y restricted en aviso y auditoría en los espacios con sidecar de Istio: sin istio-cni, el contenedor istio-init necesita permisos de red |
istio-cni desde el principio: otro componente con su chart y su versión, y lo que más falla al instalar Istio |
| 12 | Revisión de la cuenta | Raíz preflight de solo lectura (make infra-preflight): identidad (y el ARN del rol cuando la sesión es de SSO), región, cuota de vCPU, versión de EKS con soporte estándar, complementos, IP pública y la zona de Cloudflare. No crea nada |
Descubrir los faltantes en el primer apply: con el estado a medias y el reloj corriendo |
| 13 | Acceso a la cuenta | IAM Identity Center con credenciales temporales (docs/demo/11-acceso-aws-para-tofu.md): perfil andesretail-demo con AdministratorAccess para OpenTofu. El rol de SSO de ViewOnlyAccess recibe en el clúster AmazonEKSViewPolicy, que no lee Secrets |
Llaves de un usuario IAM: llave fija en disco. AmazonEKSAdminViewPolicy: incluye Secrets |
| 14 | Herramientas | kubectl, Helm, kubeconform y la CLI de AWS portables en .tools/bin, con versión de versions.env y sha256 comprobado. Esquemas de kubeconform fijados por commit (KUBERNETES_JSON_SCHEMA_COMMIT) |
Instalarlas en el sistema: cambia la máquina de Andrés y no es reproducible en CI |
Qué se sacrifica
- El contrato de plataforma cambia de ruta:
/etc/andes/secretsen Kubernetes,/run/secretssigue en Docker Compose. Las dos se leen porPLATFORM_SECRETS_DIR, pero una ruta absoluta escrita en una configuración falla (hoy,mocks/config/payment-gateway.json). - Con
privilegedimpuesto en los espacios con sidecar, Kubernetes no frena un pod privilegiado ahí; solo avisa. - Las llaves de Confluent viven en el estado. Perder la frase del estado obliga a rotarlas.
- La opción A de la decisión 9 cuesta más de la mitad de todo el demo encendido; B y C piden cambiar ADR-017.
- El presupuesto de AWS no ve el gasto de Confluent: se revisa en su consola.
Pendiente de implementación (fuera del carril infra)
- Adaptadores de Node: el cliente de Kafka de la plantilla no pasa
saslnitls; contra Confluent no conectan. order-serviceywebhook-serviceleen su llave de cifrado de un archivo (orders_pii_key,webhooks_secrets_key). En EKS el diseño es la llave de KMS de la decisión 6, y el código aún no la usa: hasta entonces esas dos cargas no se encienden en EKS.- El simulador de la pasarela lee su clave en
/run/secrets: apagado en EKS hasta que usePLATFORM_SECRETS_DIR. LEGACY_API_CLOSED(respuesta 403 del modo TO-BE) no está en la tabla de errores dedocs/to-be/07-contratos-api.md.
Verificación
make verify corre infra-verify (incluye tofu test de kafka con proveedores simulados y la comparación de contrato.json con el AsyncAPI) y k8s-verify (helm lint y template de cada release en kind y EKS, kubeconform estricto, catálogo y Trivy). El ensayo en kind (tests/k8s/kind-e2e.mjs) quedó en verde el 11/11/2026; lo que solo se comprueba en EKS está en platform/k8s/README.md, "Solo se verifica en EKS".
Cierre de la decisión 9 (15/11/2026)
Andrés eligió la opción A: las 151 particiones del contrato en Confluent, 6 eCKU, 0,70 dólares por hora. Razón: el crédito de la cuenta nueva de Confluent cubre el costo, y así el demo no difiere del contrato AsyncAPI. No hace falta cambiar ADR-017 ni usar max_partitions_per_topic. La tabla de la decisión 9 arriba queda como se escribió el 13/11/2026.