Capa de interoperabilidad · Prueba técnica de arquitectura

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

Pendiente de implementación (fuera del carril infra)

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.