Hasta ahora, cada pieza de Kilómetro Cero ha corrido en máquinas concretas: los tres nodos de Cassandra que Ansible configuró en 07-05, los brokers de Kafka que se reinician de uno en uno respetando las ISR, el clúster de Patroni con su etcd, el MinIO con sus discos, el clúster de Kubernetes sobre el que se despliegan los seis servicios. Alguien ha comprado esas máquinas, las ha instalado en un centro de datos, les ha puesto discos y red, y las sustituye cuando fallan. Y cuando en 07-03 se decidió un plan de recuperación ante desastres en pilot light con una "región secundaria", se dejó abierta la pregunta de dónde está esa región y quién la opera. Esta lección responde a eso cambiando la premisa: la infraestructura pasa a ser una API que un proveedor ofrece bajo demanda, con regiones y zonas de disponibilidad, redes virtuales, balanceadores, autoescalado y, sobre todo, servicios gestionados que hacen por nosotros lo que en el curso hemos montado a mano. Veremos los modelos de servicio y de despliegue, los conceptos que cambian el diseño (regiones y zonas, VPC, elasticidad), la correspondencia entre cada componente de Kilómetro Cero y su equivalente gestionado en AWS y GCP, las ventajas y los desafíos (incluido el que exige una revisión legal), la infraestructura como código con Terraform, los pilares de una arquitectura bien diseñada, y una introducción a FinOps con el coste mensual aproximado de la plataforma. La práctica despliega Kilómetro Cero en AWS con Terraform y los manifiestos k8s/ de 07-05. Serverless, edge y CDN quedan para 08-04.
Contenido
- Modelos de servicio: IaaS, PaaS, SaaS y la responsabilidad compartida
- Modelos de despliegue: pública, privada, híbrida y multinube
- Regiones, zonas de disponibilidad y recuperación ante desastres
- Redes en la nube: VPC, subredes, balanceadores y autoescalado
- Servicios gestionados: qué sustituye a cada pieza de Kilómetro Cero
- Ventajas y desafíos: elasticidad, pago por uso, lock-in, egress y cumplimiento
- Infraestructura como código con Terraform
- Arquitectura bien diseñada: los pilares
- FinOps: etiquetas, reservas, spot, presupuestos y el coste de Kilómetro Cero
- Práctica: Kilómetro Cero en AWS con Terraform y EKS
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- Modelos de servicio: IaaS, PaaS, SaaS y la responsabilidad compartida
La nube se define por lo que el proveedor asume y lo que sigue siendo responsabilidad del cliente. Esa línea se mueve según el modelo de servicio, y saber dónde está en cada caso es lo que evita tanto pagar por lo que no se usa como asumir que "la nube lo hace" cuando no lo hace.
| Capa | Centro de datos propio (lo que Kilómetro Cero tiene hoy) | IaaS (máquinas virtuales: EC2, Compute Engine) | PaaS (plataforma: RDS, EKS, App Engine, Cloud Run) | SaaS (aplicación: Keycloak gestionado, Confluent Cloud, Datadog) | FaaS (funciones: Lambda; es 08-04) |
|---|---|---|---|---|---|
| Edificio, energía, red física | Cliente | Proveedor | Proveedor | Proveedor | Proveedor |
| Hardware, hipervisor | Cliente | Proveedor | Proveedor | Proveedor | Proveedor |
| Sistema operativo, parches | Cliente | Cliente | Proveedor | Proveedor | Proveedor |
| Runtime, middleware (PostgreSQL, Kafka, Kubernetes) | Cliente | Cliente | Proveedor (instalación, parches, failover) | Proveedor | Proveedor |
| Configuración del servicio (parámetros, réplicas, esquemas) | Cliente | Cliente | Cliente | Parcial | Cliente |
| Aplicación y su código | Cliente | Cliente | Cliente | Proveedor | Cliente (solo la función) |
| Datos: clasificación, cifrado, acceso, backups lógicos | Cliente | Cliente | Cliente | Cliente | Cliente |
| Identidades y permisos (IAM) | Cliente | Cliente | Cliente | Cliente | Cliente |
Dos filas no cambian nunca de columna: los datos y las identidades son siempre del cliente. Ese es el modelo de responsabilidad compartida que todos los proveedores publican: el proveedor responde de la seguridad de la nube (que nadie acceda físicamente al disco, que el hipervisor aísle), el cliente de la seguridad en la nube (que el bucket no sea público, que el rol IAM no tenga *, que los datos personales estén cifrados y en la región correcta). Un bucket km0-facturas accesible sin autenticación es responsabilidad del cliente en cualquier modelo.
Para Kilómetro Cero, el reparto natural es: PaaS para todo lo que tiene equivalente gestionado (bases de datos, Kafka, Kubernetes, caché, objetos), IaaS solo donde no lo hay o donde el control compensa (los nodos de trabajo de Kubernetes son IaaS por debajo, pero gestionados como grupo), y SaaS para lo que no aporta diferencia (identidad, observabilidad si se decide externalizarla).
- Modelos de despliegue: pública, privada, híbrida y multinube
| Modelo | Qué es | Cuándo tiene sentido | Coste oculto |
|---|---|---|---|
| Nube pública | Recursos de un proveedor (AWS, GCP, Azure...) compartidos entre clientes, aislados lógicamente | Cargas variables, equipos pequeños de operación, necesidad de servicios gestionados, crecimiento incierto | Lock-in, egress, coste por uso que crece con el éxito |
| Nube privada | La misma abstracción (API, autoservicio, elasticidad) sobre hardware propio: OpenStack, VMware, Kubernetes on-premise | Regulación estricta, latencia hacia sistemas propios, volumen estable que amortiza el hardware | Hay que operar la nube además de la aplicación; sin elasticidad real más allá del hardware comprado |
| Híbrida | Parte en privada, parte en pública, conectadas (VPN o enlace dedicado) | Migración gradual (el estrangulamiento de 08-01 aplicado a la infraestructura); datos que deben quedarse dentro con cómputo elástico fuera | Latencia y coste del enlace; dos modelos de seguridad y observabilidad |
| Multinube | Cargas repartidas entre varios proveedores públicos | Evitar dependencia de uno, aprovechar servicios únicos de cada uno, exigencias de clientes | La más cara de operar: dos IAM, dos redes, dos conjuntos de servicios gestionados; el egress entre nubes; la portabilidad obliga a renunciar a lo gestionado |
Kilómetro Cero, que hoy tiene su propio hardware, pasará a nube pública con un periodo híbrido durante la migración (el enlace VPN entre el centro de datos y la VPC permite que Kafka replique con MirrorMaker y que Patroni tenga una réplica en la nube antes del corte). La multinube se descarta explícitamente: con cuatro equipos, operar dos proveedores costaría más que el riesgo que mitiga, y la portabilidad se mantiene de otra forma, con Kubernetes como capa de despliegue y con contenedores para lo que no es gestionado (apartado 6).
- Regiones, zonas de disponibilidad y recuperación ante desastres
Un proveedor organiza su infraestructura en regiones (una zona geográfica: Irlanda, Fráncfort, París...) y, dentro de cada región, en varias zonas de disponibilidad (availability zones, AZ): centros de datos físicamente separados (kilómetros de distancia, energía y red independientes) pero conectados con latencia de 1-2 ms. Esta estructura mapea directamente sobre los dominios de fallo de 07-03:
- Una AZ es un dominio de fallo de centro de datos: incendio, corte de energía, fallo de red del edificio. Distribuir las réplicas entre AZ (los tres nodos de Cassandra en tres AZ; el primario de Patroni y su réplica síncrona en AZ distintas; los brokers de Kafka con
broker.rack= AZ para que las réplicas de cada partición se repartan) hace que la pérdida de una AZ no sea un desastre, solo un failover. Es lo que en 07-05 hacíatopologySpreadConstraintscon la etiqueta de zona. - Una región es un dominio de fallo geográfico (y regulatorio): un desastre natural, un fallo de un servicio regional del proveedor, o un cambio legal. La región secundaria de 07-03 es otra región del mismo proveedor, a cientos o miles de kilómetros, con latencia de 10-80 ms, lo que descarta la replicación síncrona entre ellas.
Con eso, las tres estrategias de DR de 07-03 se concretan en la nube:
| Estrategia | Qué hay en la región secundaria | RPO | RTO | Coste relativo | Cómo se hace en AWS |
|---|---|---|---|---|---|
| Pilot light | Datos replicados (réplica de lectura de RDS entre regiones, replicación S3, MirrorMaker de MSK); clúster EKS mínimo o inexistente; Terraform listo para crear el resto | Minutos (replicación asíncrona) | 30-60 min (crear nodos, promover réplica, cambiar DNS) | ~15-20 % de la primaria | RDS cross-region read replica; S3 CRR; Route 53 con failover manual |
| Warm standby | Todo desplegado a escala reducida (EKS con pocos nodos, servicios con 1 réplica) recibiendo datos | Minutos | Minutos (escalar y cambiar DNS) | ~40-50 % | Igual + EKS activo con HPA listo; Route 53 con health checks |
| Activa-activa multirregión | Todo a escala completa en ambas, sirviendo tráfico a la vez | Segundos o cero (según el dato) | Segundos (el DNS o el balanceador global deja de enviar) | ~200 % + complejidad de datos | Aurora Global Database o Cassandra multi-DC; Route 53 latency routing; diseño multilíder de 03-04 |
Kilómetro Cero mantiene la decisión de 07-03 (pilot light) por coste, con una diferencia importante respecto al centro de datos propio: en la nube, la región secundaria no cuesta nada mientras está vacía. Terraform puede crear la infraestructura de la región secundaria en 20 minutos a partir del mismo código; lo único que debe existir de antemano son los datos replicados. La activa-activa se deja como siguiente paso en 08-05, porque implica resolver conflictos de escritura entre regiones (03-04) y cambiaría el modelo de consistencia del stock.
flowchart TB
subgraph R1[Región eu-west-1, primaria]
direction TB
ALB[Balanceador de aplicación<br/>ALB, público]
subgraph AZa[AZ a]
EKSa[Nodos EKS<br/>pedidos, catalogo...]
RDSa[(RDS primario<br/>km0_inventario)]
MSKa[Broker MSK 1]
CASa[Cassandra 1]
end
subgraph AZb[AZ b]
EKSb[Nodos EKS]
RDSb[(RDS standby<br/>Multi-AZ, síncrono)]
MSKb[Broker MSK 2]
CASb[Cassandra 2]
end
subgraph AZc[AZ c]
EKSc[Nodos EKS]
MSKc[Broker MSK 3]
CASc[Cassandra 3]
REDc[(ElastiCache<br/>réplica)]
end
S3[(S3 km0-fotos<br/>regional, 3+ AZ)]
ALB --> EKSa & EKSb & EKSc
end
subgraph R2[Región eu-central-1, pilot light]
RDSr[(Réplica de lectura<br/>asíncrona)]
S3r[(S3 replicado)]
MSKr[MirrorMaker → MSK mínimo]
EKSr[EKS: solo definido<br/>en Terraform]
end
RDSa -. "replicación asíncrona" .-> RDSr
S3 -. "CRR" .-> S3r
MSKa -. "MirrorMaker" .-> MSKr
DNS[Route 53<br/>km0.example] --> ALB
DNS -. "failover manual" .-> R2
- Redes en la nube: VPC, subredes, balanceadores y autoescalado
Una VPC (Virtual Private Cloud) es una red privada aislada dentro de la región, con su rango de direcciones (10.0.0.0/16), dividida en subredes que pertenecen cada una a una AZ. La convención que Kilómetro Cero adopta, y que es la habitual, es de tres capas por AZ:
| Subred | Qué contiene | Acceso desde Internet | Salida a Internet |
|---|---|---|---|
| Pública | Balanceadores, NAT gateway, bastión (si lo hay) | Sí (tienen IP pública y ruta al Internet Gateway) | Directa |
| Privada de aplicación | Nodos de EKS con los seis servicios, Kong | No | A través del NAT gateway (para llamar a la pasarela de pagos, descargar imágenes) |
| Privada de datos | RDS, MSK, ElastiCache, nodos de Cassandra | No | Ninguna (ni entrada ni salida a Internet) |
Los grupos de seguridad son cortafuegos con estado por recurso, y expresan las mismas reglas que las políticas de red de Kubernetes de 07-05 pero en la capa de la VPC: RDS solo acepta 5432 desde el grupo de seguridad de los nodos de EKS; MSK solo 9094 (TLS) desde EKS; nada desde fuera. Los servicios gestionados como S3 se alcanzan por endpoints de VPC, que evitan que el tráfico hacia km0-fotos salga a Internet (y evitan el coste del NAT).
Los balanceadores gestionados sustituyen al Ingress + Kong de 07-05 en su capa externa: un balanceador de aplicación (ALB en AWS, HTTP(S) Load Balancer en GCP) termina TLS con certificados gestionados y renovados automáticamente, reparte entre las AZ, hace health checks y expone una IP en varias AZ. Kong sigue existiendo detrás, como gateway con sus plugins (06-05); el balanceador es solo la puerta de la región. El WebSocket de 08-02 exige que el balanceador soporte Upgrade y tenga un timeout de inactividad largo (el ALB lo permite; un balanceador de red de capa 4 lo pasa sin mirar).
El autoescalado opera en dos niveles que hay que distinguir: el HPA de 07-05 añade Pods según CPU o lag de Kafka, pero los Pods necesitan nodos; el autoescalador del clúster (Cluster Autoscaler o Karpenter en EKS, el autoescalado de node pools en GKE) añade y quita máquinas virtuales cuando hay Pods pendientes o nodos vacíos. Por debajo, un grupo de autoescalado de máquinas virtuales (Auto Scaling Group) las reemplaza si fallan un health check y las reparte entre AZ. La Semana de la Vendimia con k6 (07-06) se convierte así en: HPA sube pedidos de 3 a 12 Pods → 6 Pods pendientes por falta de CPU → Karpenter arranca 2 nodos en 90 s → los Pods se programan. Esos 90 s son la razón de mantener margen (nodos con capacidad libre) antes de una campaña anunciada, o de escalar por calendario.
- Servicios gestionados: qué sustituye a cada pieza de Kilómetro Cero
Esta tabla es la correspondencia entre lo construido en el curso y lo que el proveedor ofrece gestionado. En cada fila, lo que se gana es lo que el proveedor asume (instalación, parches, failover, backups, escalado) y lo que se pierde es control y portabilidad.
| Pieza de Kilómetro Cero | Lección | AWS | GCP | Qué gestiona el proveedor | Qué sigue siendo nuestro |
|---|---|---|---|---|---|
PostgreSQL + Patroni (km0_inventario, km0_analitica) |
03-04, 07-03 | RDS PostgreSQL Multi-AZ (o Aurora) | Cloud SQL con alta disponibilidad | Failover entre AZ (~60 s), backups automáticos, PITR, parches, réplicas de lectura | Esquema, índices, parámetros, pooling de conexiones, restauraciones de prueba |
Kafka (pedidos.eventos, reparto.*) |
02-04 | MSK (o Confluent Cloud) | Confluent Cloud o Pub/Sub (modelo distinto) | Brokers, ZooKeeper/KRaft, parches, réplicas entre AZ, escalado de almacenamiento | Tópicos, particiones, retención, esquemas, consumidores |
Cassandra (km0_pedidos) |
04-04 | Keyspaces (API compatible, serverless) o Cassandra en EC2 | Cassandra en GCE o Astra (DataStax) | Nodos, compactaciones, reparaciones, escalado | Modelo de datos, niveles de consistencia (Keyspaces no soporta todo CQL: las transacciones ligeras y algunos tipos necesitan revisión) |
| Redis (caché, limitador, pub/sub) | 04-05 | ElastiCache for Redis (réplicas entre AZ) | Memorystore | Failover, parches, réplicas | Estrategia de caché, TTL, claves |
MinIO (km0-fotos, km0-facturas, km0-backups, km0-auditoria) |
04-03 | S3 | GCS | Durabilidad (11 nueves), versionado, ciclo de vida, replicación entre regiones | Políticas de acceso, cifrado con claves propias, estructura de claves |
| HDFS + Spark + Airflow | 04-02, 05-03, 05-05 | EMR (Spark sobre S3, sin HDFS persistente) + MWAA (Airflow) | Dataproc + Cloud Composer | Clústeres efímeros, versiones de Spark, Airflow gestionado | Los trabajos (ventas_diarias.py), los DAG (km0_ventas_diarias), el particionado del lago en S3 |
Flink (alertas_stock.py) |
05-04 | Managed Service for Apache Flink | Dataflow (modelo Beam) o Flink en GKE | Checkpoints, escalado, versiones | El código del trabajo y su estado |
| Kubernetes | 07-05 | EKS | GKE | Plano de control (API server, etcd) con SLA, actualizaciones | Nodos (grupos gestionados), manifiestos, operadores, mesh |
| Vault | 06-04 | Secrets Manager + KMS | Secret Manager + Cloud KMS | Almacenamiento, rotación programada, auditoría | Qué secretos, quién los lee (IAM), integración en los Pods |
Keycloak (realm km0) |
06-03 | Cognito | Identity Platform | Alta disponibilidad, escalado, federación | Realm, clientes, roles, flujos (con menos flexibilidad que Keycloak) |
| Prometheus + Grafana + Loki + Tempo | 07-01, 07-02 | CloudWatch (métricas, logs) + X-Ray, o Amazon Managed Prometheus/Grafana | Cloud Monitoring + Cloud Logging + Cloud Trace | Almacenamiento, retención, alertas | Instrumentación (servicios/comun/), dashboards, SLOs |
| Kong | 06-05 | Kong en EKS (o API Gateway gestionado, con menos plugins) | Kong en GKE (o Apigee) | Si gestionado: escalado y disponibilidad | Rutas, plugins |
| Mosquitto (08-02) | 08-02 | IoT Core (retirado en algunos proveedores) o EMQX en EKS | EMQX/HiveMQ en GKE | — | Todo, si se autogestiona |
Certificados km0-ca, mTLS |
06-04 | ACM (públicos) + mesh para mTLS | Certificate Manager + mesh | Emisión y renovación de públicos | mTLS interno sigue siendo del mesh |
Tres notas sobre esta tabla. Primera: Pub/Sub de GCP no es Kafka: no tiene particiones ni offsets al estilo Kafka (aunque ofrece ordenación por clave y reproducción), y el código de consumidores con grupos y commits cambia; si se quiere Kafka en GCP, es Confluent Cloud. Segunda: Keyspaces es compatible con CQL pero no es Cassandra: el nivel de consistencia y las transacciones ligeras tienen diferencias que hay que verificar con el repositorio de 04-04 antes de migrar. Tercera: lo gestionado no elimina la operación, la cambia: ya no se parchea PostgreSQL, pero sigue habiendo que dimensionar la instancia, elegir la ventana de mantenimiento (RDS reinicia para aplicar parches mayores), probar las restauraciones y vigilar las conexiones.
- Ventajas y desafíos: elasticidad, pago por uso, lock-in, egress y cumplimiento
| Ventaja | Qué aporta a Kilómetro Cero | Condición para que sea real |
|---|---|---|
| Elasticidad | La Semana de la Vendimia (×15) se absorbe con nodos que existen solo esa semana; el resto del año se paga por 1/15 | Que la aplicación escale horizontalmente (Módulos 4 y 7 lo han hecho posible) y que el autoescalado esté probado (07-06) |
| Pago por uso | Sin inversión inicial; una región secundaria vacía no cuesta | Disciplina de apagar lo que no se usa (apartado 9) |
| Servicios gestionados | Patroni, las reparaciones de Cassandra, los parches de Kafka dejan de ser guardias del equipo de plataforma | Aceptar menos control y algunas diferencias funcionales |
| Alcance global | Regiones cerca de nuevos mercados; DR en otra región con la misma API | Diseño consciente de la latencia entre regiones |
| Seguridad de base | Centro de datos certificado, cifrado en reposo por defecto, IAM granular, auditoría (CloudTrail) integrada | Configurarla: la responsabilidad compartida del apartado 1 |
| Desafío | En qué consiste | Cómo lo trata Kilómetro Cero |
|---|---|---|
| Lock-in | Cuanto más gestionado, más específico del proveedor: Keyspaces en lugar de Cassandra, Cognito en lugar de Keycloak, Step Functions (08-04) en lugar de saga_pedido.py. Migrar de proveedor pasa a ser un proyecto de meses |
Portabilidad selectiva: Kubernetes y contenedores para los servicios; APIs estándar (PostgreSQL, Kafka, S3, Redis, CQL) para los datos, de modo que el gestionado sea sustituible por el autogestionado; aceptar lock-in donde el beneficio es grande (RDS, S3) y evitarlo donde es pequeño (Cognito frente a Keycloak en EKS) |
| Coste de egress | Los datos que salen de la región se cobran por GB (el que entra suele ser gratuito): las fotos servidas a Ana, la réplica a la región secundaria, el tráfico entre AZ (también se cobra, aunque menos) | CDN delante de km0-fotos (04-03, 08-04); consumidores de Kafka en la misma AZ que su broker líder cuando sea posible (client.rack); lago y cómputo en la misma región |
| Coste que crece con el éxito | A volumen estable y alto, la nube puede costar más que el hardware propio amortizado | Reservas y spot (apartado 9); revisar cada año con números |
| Cumplimiento y residencia de datos | Los datos personales (nombre, dirección y posición de Ana; datos de pago) están sujetos a normativa (en la UE, el RGPD) que condiciona dónde pueden almacenarse y procesarse, quién puede acceder a ellos (incluido el proveedor) y qué contratos hacen falta. Elegir una región fuera de la UE, o un servicio que replica metadatos a otra región, puede incumplirla | Advertencia: esta decisión exige revisión legal, no solo técnica. Kilómetro Cero elige regiones de la UE (eu-west-1, eu-central-1), cifra los datos personales con claves propias en KMS (06-02) y documenta los subencargados; pero qué datos pueden salir de la UE, con qué cláusulas contractuales y con qué evaluación de impacto lo decide el responsable de protección de datos, no el arquitecto |
| Complejidad de IAM | Cientos de roles y políticas; un s3:* sobre * en un rol de servicio es la fuga de datos más común |
Mínimo privilegio por servicio (IAM Roles for Service Accounts en la práctica); revisión automática (Access Analyzer) |
| Dependencia de un tercero | Las regiones fallan (ocurre varias veces al año en cada proveedor) | Multi-AZ para lo cotidiano, DR entre regiones para lo excepcional (apartado 3) |
- Infraestructura como código con Terraform
En 07-05 se distinguieron las herramientas de configuración (Ansible: dejar una máquina existente en un estado) de las de aprovisionamiento (Terraform: crear la infraestructura misma), y se remitió aquí. Cuando la infraestructura es una API, describirla como código es lo que permite revisarla en pull request, reproducirla en otra región y saber qué existe. Terraform (y su bifurcación abierta OpenTofu) es declarativo: se describe el estado deseado en ficheros .tf (lenguaje HCL) y la herramienta calcula y ejecuta las llamadas a la API necesarias para llegar a él, en el orden que dictan las dependencias.
Cuatro conceptos bastan para leer y escribir la práctica del apartado 10:
- Proveedores (providers): plugins que traducen recursos a llamadas de API (
aws,google,kubernetes,helm,kafka...). Se declaran con versión fijada. - Recursos y datos:
resource "aws_db_instance" "inventario" { ... }crea;data "aws_availability_zones" "disponibles" {}consulta. Se referencian entre sí (aws_vpc.km0.id), y esas referencias son el grafo de dependencias. - Estado remoto: Terraform guarda en un fichero de estado la correspondencia entre lo declarado y lo que existe (ids reales). Ese fichero es crítico y compartido: se guarda en un backend remoto (S3 con versionado, y una tabla de DynamoDB como cerrojo para que dos personas no apliquen a la vez; es el mismo problema de exclusión mutua de 03-03 resuelto con un servicio gestionado), se cifra y nunca se sube a Git. Contiene secretos (contraseñas iniciales de RDS), lo que refuerza lo anterior.
- Módulos: carpetas de
.tfreutilizables con variables de entrada y salidas (module "vpc" { source = "terraform-aws-modules/vpc/aws" ... }). Kilómetro Cero usa módulos de la comunidad para VPC y EKS (cientos de recursos que no merece la pena escribir a mano) y módulos propios para cada entorno.
El ciclo es terraform init (descarga proveedores y módulos, conecta con el estado), terraform plan (calcula el diff entre lo declarado y el estado y lo muestra: + crear, ~ modificar, - destruir, -/+ reemplazar), y terraform apply (lo ejecuta tras confirmar). El plan es la pieza más valiosa: se ejecuta en el pipeline (07-05) y se pega en la pull request; un -/+ sobre aws_db_instance.inventario (reemplazar la base de datos) es lo que una revisión debe detener. En GitOps, apply solo lo ejecuta el pipeline tras el merge, nunca una persona desde su portátil.
- Arquitectura bien diseñada: los pilares
Los proveedores publican marcos de "arquitectura bien diseñada" (well-architected) con pilares que sirven como lista de comprobación. Los seis de AWS (GCP tiene una lista equivalente) coinciden casi uno a uno con módulos del curso:
| Pilar | Pregunta que hace | Dónde lo ha trabajado Kilómetro Cero |
|---|---|---|
| Excelencia operativa | ¿Se opera con código, se observa, se aprende de los incidentes? | IaC (07-05, esta lección), observabilidad (07-01/07-02), postmortems y runbooks (07-03), GitOps |
| Seguridad | ¿Identidad, mínimo privilegio, cifrado, auditoría, defensa en profundidad? | Módulo 6 completo; IAM y grupos de seguridad de esta lección |
| Fiabilidad | ¿Sobrevive a fallos de componente, AZ y región? ¿Se ha probado? | Redundancia y failover (07-03), resiliencia (07-04), caos (07-06), multi-AZ y DR (apartado 3) |
| Eficiencia de rendimiento | ¿Se usan los recursos adecuados a cada carga y se miden? | Almacenamiento por carga (Módulo 4), caché (04-05), autoescalado, SLO p99 (07-01) |
| Optimización de costes | ¿Se paga por lo que se usa, se mide y se revisa? | FinOps (apartado 9) |
| Sostenibilidad | ¿Se minimiza el consumo: apagar lo ocioso, regiones con energía limpia, eficiencia del código? | Autoescalado a la baja, clústeres efímeros de Spark, retención de datos (04-02) |
Su utilidad práctica es la revisión periódica: cada semestre, recorrer las preguntas del marco sobre la plataforma y anotar qué falta. Es un ejercicio del proyecto final (08-05).
- FinOps: etiquetas, reservas, spot, presupuestos y el coste de Kilómetro Cero
En la nube, el coste es una métrica más del sistema, y como cualquier métrica hay que medirla, atribuirla y ponerle alertas. La disciplina se llama FinOps y, en su nivel básico, se reduce a cinco prácticas:
- Etiquetado (tagging): todo recurso lleva etiquetas
equipo,servicio,entorno,centro_coste. Sin ellas, la factura es un número; con ellas, "el equipo de reparto gastó 1 840 € en septiembre, el 60 % en MSK". Terraform las aplica por defecto a todo (default_tags). - Reservas y planes de ahorro: comprometerse a un uso durante 1 o 3 años a cambio de un 30-60 % de descuento. Se aplica a la base que siempre está encendida (los nodos mínimos de EKS, RDS, MSK, ElastiCache), nunca al pico.
- Instancias spot: capacidad sobrante del proveedor con un 60-90 % de descuento, que puede ser retirada con 2 minutos de aviso. Ideal para lo que tolera interrupciones y reinicia solo: los ejecutores de Spark (05-03 ya toleraba la pérdida de ejecutores recomputando particiones), los nodos de EKS para cargas sin estado con varias réplicas. Nunca para RDS, Cassandra, Kafka ni el plano de control.
- Presupuestos y alertas: un presupuesto por equipo y entorno, con alerta al 80 % y al 100 % previstos, y detección de anomalías (un NAT gateway que de repente mueve 2 TB porque alguien sirvió las fotos sin CDN). Son alertas como las de 07-01, hacia el mismo canal.
- Apagar lo ocioso: entornos de
stagingapagados de noche y en fin de semana (con Terraform o con programaciones), clústeres de Spark efímeros (EMR arranca para el DAGkm0_ventas_diariasy se destruye al acabar), volúmenes y snapshots huérfanos.
Coste mensual aproximado de Kilómetro Cero en AWS
Los precios siguientes son ficticios y orientativos, del orden de magnitud correcto para una región europea, y sirven para el razonamiento, no como presupuesto. El ejercicio útil es ver dónde está el coste, no la cifra exacta.
| Componente | Dimensionamiento | Coste base mensual (€) | Con reservas / spot (€) | Observación |
|---|---|---|---|---|
| EKS plano de control | 1 clúster | 70 | 70 | Fijo |
| Nodos EKS (servicios, Kong, mesh, observabilidad) | 6 × 4 vCPU/16 GB permanentes + hasta 12 en campaña | 900 + 300 (media de picos) | 550 + 120 (reserva base, spot en picos) | Mayor partida de cómputo |
RDS PostgreSQL Multi-AZ (km0_inventario) |
2 vCPU/8 GB × 2 (standby), 200 GB | 380 | 250 | Multi-AZ duplica |
RDS PostgreSQL (km0_analitica) |
2 vCPU/8 GB, 500 GB | 210 | 140 | Sin Multi-AZ: tolera RTO de horas |
| MSK | 3 brokers × 2 vCPU/8 GB, 1 TB total | 650 | 450 | Coste por broker-hora + almacenamiento |
| Cassandra en EC2 (3 nodos) | 3 × 4 vCPU/16 GB + 3 × 500 GB SSD | 480 | 320 | Keyspaces sería por lectura/escritura; con 1,2 M pedidos/mes sale similar |
| ElastiCache Redis | 2 nodos 2 GB (primario + réplica) | 90 | 60 | |
S3 (km0-fotos 30 GB, km0-facturas, km0-backups 800 GB, km0-auditoria) |
~1 TB + peticiones | 35 | 35 | El almacenamiento es barato |
| Egress a Internet (fotos, API) | 3 TB/mes sin CDN | 240 | 240 → ~40 con CDN (08-04) | La partida que la CDN reduce |
| Tráfico entre AZ | ~2 TB/mes (réplicas, consumidores) | 40 | 40 | Se olvida siempre |
| NAT gateway | 2 (una por AZ activa) + 500 GB | 110 | 110 | Endpoints de VPC para S3 lo reducen |
| ALB | 1 + tráfico | 45 | 45 | |
| EMR (Spark) efímero | 2 h/día × 6 nodos spot | 180 | 60 | Spot: −70 % |
| MWAA (Airflow) | Entorno pequeño | 320 | 320 | Caro para lo que hace: candidato a Airflow en EKS (~40) |
| Flink gestionado | 4 KPU | 420 | 420 | Alternativa: Flink en EKS |
| Secrets Manager + KMS | 60 secretos, 3 claves | 45 | 45 | |
| CloudWatch (logs 200 GB/mes, métricas) | 180 | 180 | O Loki/Tempo/Prometheus en EKS (coste en nodos) | |
| Región secundaria (pilot light) | Réplica RDS, S3 CRR, MSK mínimo | 350 | 350 | ~10 % de la primaria |
| Soporte del proveedor | Plan de negocio | 250 | 250 | |
| Total aproximado | ≈ 5 300 | ≈ 3 900 |
Lo que la tabla enseña: el cómputo (EKS + bases de datos + Kafka) es dos tercios del total; las reservas ahorran un 25-30 % sobre la base; el egress es la partida más sensible al diseño (la CDN de 08-04 la divide por seis); y dos servicios gestionados (MWAA, Flink gestionado) cuestan más que sus equivalentes autogestionados en el clúster, lo que ilustra que "gestionado" no siempre gana: la decisión depende de cuánto cuesta el tiempo del equipo que lo operaría.
- Práctica: Kilómetro Cero en AWS con Terraform y EKS
La estructura del código de infraestructura, que se añade al proyecto km0/:
km0/infra/ ├── aws/ │ ├── main.tf # proveedores, backend de estado, VPC, EKS, RDS, MSK, ElastiCache, S3, IAM │ ├── variables.tf # region, entorno, tamaños │ ├── outputs.tf # endpoints que necesitan los manifiestos k8s/ │ └── entornos/ │ ├── prod.tfvars │ └── staging.tfvars └── gcp/ # el equivalente en GCP (solo tabla en esta lección)
Lo que sigue es main.tf explicado bloque a bloque. Está simplificado (faltan algunos parámetros de seguridad y de red que un módulo real incluye), pero cada bloque es real y corresponde a una decisión de las secciones anteriores.
Bloque 1: proveedores y estado remoto
# km0/infra/aws/main.tf
terraform {
required_version = ">= 1.6"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.40" }
kubernetes = { source = "hashicorp/kubernetes", version = "~> 2.27" }
helm = { source = "hashicorp/helm", version = "~> 2.12" }
}
# Estado remoto: S3 con versionado (para recuperar un estado anterior) y
# DynamoDB como cerrojo (dos 'apply' simultáneos se excluyen).
backend "s3" {
bucket = "km0-terraform-estado"
key = "prod/infra.tfstate"
region = "eu-west-1"
dynamodb_table = "km0-terraform-cerrojo"
encrypt = true
}
}
provider "aws" {
region = var.region # "eu-west-1": región de la UE (cumplimiento, apartado 6)
default_tags { # FinOps: toda pieza lleva estas etiquetas
tags = { proyecto = "km0", entorno = var.entorno, gestionado_por = "terraform" }
}
}
data "aws_availability_zones" "disponibles" { state = "available" }
locals {
azs = slice(data.aws_availability_zones.disponibles.names, 0, 3) # tres AZ: a, b, c
}Bloque 2: la VPC con tres capas por AZ
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.5"
name = "km0-${var.entorno}"
cidr = "10.0.0.0/16"
azs = local.azs
public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"] # ALB, NAT
private_subnets = ["10.0.10.0/23", "10.0.12.0/23", "10.0.14.0/23"] # nodos EKS (512 IPs por AZ: los Pods consumen IPs)
database_subnets = ["10.0.20.0/24", "10.0.21.0/24", "10.0.22.0/24"] # RDS, MSK, ElastiCache, Cassandra
enable_nat_gateway = true
one_nat_gateway_per_az = true # sin esto, un NAT en una sola AZ es un punto único de fallo para la salida
create_database_subnet_group = true
# Etiquetas que EKS necesita para descubrir en qué subredes crear balanceadores
public_subnet_tags = { "kubernetes.io/role/elb" = 1 }
private_subnet_tags = { "kubernetes.io/role/internal-elb" = 1 }
}
# Endpoint de VPC para S3: el tráfico a km0-fotos no sale a Internet ni pasa por el NAT (coste y seguridad)
resource "aws_vpc_endpoint" "s3" {
vpc_id = module.vpc.vpc_id
service_name = "com.amazonaws.${var.region}.s3"
route_table_ids = module.vpc.private_route_table_ids
}Bloque 3: EKS con grupos de nodos permanentes y spot
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.8"
cluster_name = "km0-${var.entorno}"
cluster_version = "1.29"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets # los nodos nunca en subredes públicas
cluster_endpoint_public_access = true # kubectl desde el pipeline (restringir por CIDR en prod)
enable_irsa = true # IAM Roles for Service Accounts (bloque 6)
eks_managed_node_groups = {
base = { # capacidad permanente: aquí van las reservas
instance_types = ["m6i.xlarge"] # 4 vCPU / 16 GB
min_size = 6, desired_size = 6, max_size = 9
labels = { "km0/pool" = "base" }
}
campana = { # capacidad elástica en spot: solo Pods sin estado
instance_types = ["m6i.xlarge", "m5.xlarge", "m6a.xlarge"] # varios tipos: más probabilidad de spot
capacity_type = "SPOT"
min_size = 0, desired_size = 0, max_size = 12
labels = { "km0/pool" = "spot" }
taints = [{ key = "km0/spot", value = "true", effect = "NO_SCHEDULE" }] # solo quien lo tolere explícitamente
}
}
}Los Pods de pedidos y catalogo llevan en k8s/pedidos-deployment.yaml una toleration para km0/spot y una affinity que prefiere base, de modo que en campaña desbordan a spot; Cassandra, Kafka (si fuera autogestionado) y las bases de datos no toleran el taint y nunca caen en spot. El autoescalador del clúster (Karpenter, instalado con el proveedor helm) es quien pasa el grupo campana de 0 a 12 según los Pods pendientes.
Bloque 4: RDS PostgreSQL Multi-AZ para km0_inventario
resource "aws_db_subnet_group" "datos" {
name = "km0-datos"
subnet_ids = module.vpc.database_subnets
}
resource "aws_security_group" "rds" {
name = "km0-rds"
vpc_id = module.vpc.vpc_id
ingress { # solo 5432 y solo desde los nodos de EKS
from_port = 5432, to_port = 5432, protocol = "tcp"
security_groups = [module.eks.node_security_group_id]
}
}
resource "aws_db_instance" "inventario" {
identifier = "km0-inventario-${var.entorno}"
engine = "postgres"
engine_version = "16"
instance_class = var.entorno == "prod" ? "db.m6g.large" : "db.t4g.medium"
allocated_storage = 200
storage_encrypted = true
kms_key_id = aws_kms_key.datos.arn # clave propia (06-02): la rotación y el acceso los controlamos nosotros
db_name = "km0_inventario"
username = "km0_admin"
manage_master_user_password = true # la contraseña la genera y guarda Secrets Manager
multi_az = var.entorno == "prod" # standby síncrono en otra AZ: sustituye a Patroni (07-03)
backup_retention_period = 14 # backups diarios + WAL continuo: PITR de 14 días
backup_window = "02:00-03:00"
maintenance_window = "sun:03:30-sun:04:30" # RDS reinicia para parches mayores: ventana conocida
deletion_protection = var.entorno == "prod" # un 'terraform destroy' accidental no borra producción
performance_insights_enabled = true
db_subnet_group_name = aws_db_subnet_group.datos.name
vpc_security_group_ids = [aws_security_group.rds.id]
}
# Réplica de lectura en la región secundaria: la base del pilot light (07-03, apartado 3)
resource "aws_db_instance" "inventario_dr" {
provider = aws.secundaria # un segundo proveedor con region = "eu-central-1"
identifier = "km0-inventario-dr"
replicate_source_db = aws_db_instance.inventario.arn
instance_class = "db.t4g.medium" # pequeña: se redimensiona al promover
kms_key_id = aws_kms_key.datos_dr.arn
skip_final_snapshot = true
}Lo que aquí sustituye a lo construido a mano: multi_az = true es Patroni + etcd + réplica síncrona + failover automático (RDS cambia el DNS del endpoint al standby en ~60 s; la aplicación solo necesita reconectar, que es lo que el pool con reintentos de 07-04 ya hace); backup_retention_period es el pgBackRest y el archivado de WAL de 07-03. Lo que no sustituye: la prueba de restauración semanal, que sigue siendo un trabajo de Airflow que restaura el último snapshot en una instancia temporal y ejecuta comprobaciones.
Bloque 5: MSK, ElastiCache y S3
resource "aws_msk_cluster" "eventos" {
cluster_name = "km0-eventos-${var.entorno}"
kafka_version = "3.6.0"
number_of_broker_nodes = 3 # uno por AZ: réplicas de partición repartidas
broker_node_group_info {
instance_type = "kafka.m5.large"
client_subnets = module.vpc.database_subnets
security_groups = [aws_security_group.msk.id]
storage_info { ebs_storage_info { volume_size = 350 } } # 3 × 350 GB ≈ 1 TB: retención de 02-04
}
encryption_info {
encryption_in_transit { client_broker = "TLS", in_cluster = true } # TLS obligatorio (06-02)
}
configuration_info {
arn = aws_msk_configuration.eventos.arn
revision = aws_msk_configuration.eventos.latest_revision
}
}
resource "aws_msk_configuration" "eventos" {
name = "km0-eventos"
server_properties = <<-EOT
default.replication.factor=3
min.insync.replicas=2
auto.create.topics.enable=false
EOT # los mismos valores de 02-04 y 07-03
}
resource "aws_elasticache_replication_group" "cache" {
replication_group_id = "km0-cache-${var.entorno}"
description = "Caché de catálogo, limitador, pub/sub de reparto"
engine = "redis"
node_type = "cache.t4g.small"
num_cache_clusters = 2 # primario + réplica en otra AZ
automatic_failover_enabled = true
multi_az_enabled = true
at_rest_encryption_enabled = true
transit_encryption_enabled = true
subnet_group_name = aws_elasticache_subnet_group.datos.name
security_group_ids = [aws_security_group.redis.id]
}
resource "aws_s3_bucket" "fotos" {
bucket = "km0-fotos-${var.entorno}"
}
resource "aws_s3_bucket_versioning" "fotos" { # versionado, como en MinIO (04-03)
bucket = aws_s3_bucket.fotos.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_public_access_block" "fotos" { # nunca público: se sirve por URL prefirmada o CDN
bucket = aws_s3_bucket.fotos.id
block_public_acls = true, block_public_policy = true, ignore_public_acls = true, restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "fotos" {
bucket = aws_s3_bucket.fotos.id
rule {
id = "versiones-antiguas"
status = "Enabled"
noncurrent_version_expiration { noncurrent_days = 30 } # la regla de ciclo de vida de 04-03
}
}
resource "aws_s3_bucket_replication_configuration" "fotos_dr" { # CRR a la región secundaria (pilot light)
bucket = aws_s3_bucket.fotos.id
role = aws_iam_role.replicacion_s3.arn
rule {
id = "dr"
status = "Enabled"
destination { bucket = aws_s3_bucket.fotos_dr.arn, storage_class = "STANDARD_IA" }
}
}Bloque 6: IAM roles para service accounts (IRSA)
Los Pods de catalogo necesitan escribir en km0-fotos; los de pedidos, leer sus secretos. En lugar de credenciales de larga duración en un Secret de Kubernetes (lo que 06-04 evitaba con Vault), EKS permite asociar un rol IAM a una service account de Kubernetes: el Pod obtiene credenciales temporales automáticamente, con el mínimo privilegio, y CloudTrail registra quién hizo qué. Es la identidad de carga de trabajo de 06-04 (SPIFFE) con el IAM del proveedor.
data "aws_iam_policy_document" "catalogo_fotos" {
statement {
actions = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"] # sin s3:* ni ListAllMyBuckets
resources = ["${aws_s3_bucket.fotos.arn}/fotos/*"] # solo el prefijo de fotos
}
}
module "irsa_catalogo" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.39"
role_name = "km0-catalogo-${var.entorno}"
role_policy_arns = { fotos = aws_iam_policy.catalogo_fotos.arn }
oidc_providers = {
eks = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["km0-prod:catalogo"] # SOLO esta service account puede asumir el rol
}
}
}En el manifiesto k8s/catalogo-deployment.yaml basta con serviceAccountName: catalogo y la anotación eks.amazonaws.com/role-arn: <arn del rol> en la ServiceAccount; el SDK de AWS en el Pod (boto3, el mismo fotos.py de 04-03 apuntando a S3 en lugar de a MinIO) encuentra las credenciales solo.
Bloque 7: salidas para los manifiestos
# km0/infra/aws/outputs.tf
output "rds_inventario_endpoint" { value = aws_db_instance.inventario.address }
output "msk_bootstrap_tls" { value = aws_msk_cluster.eventos.bootstrap_brokers_tls }
output "redis_endpoint" { value = aws_elasticache_replication_group.cache.primary_endpoint_address }
output "bucket_fotos" { value = aws_s3_bucket.fotos.bucket }
output "eks_cluster_name" { value = module.eks.cluster_name }Desplegar los manifiestos k8s/ de 07-05 en EKS
Los manifiestos no cambian de forma: cambian los valores de configuración que apuntan a las dependencias. En 07-05, pedidos-config tenía KAFKA_BOOTSTRAP=kafka-0.kafka:9092; ahora lo genera el pipeline a partir de las salidas de Terraform:
# km0/infra/aws/desplegar.sh — ejecutado por el pipeline tras 'terraform apply'
set -euo pipefail
cd km0/infra/aws
terraform init -input=false
terraform plan -var-file=entornos/prod.tfvars -out=plan.bin # el plan se adjunta a la PR
terraform apply -input=false plan.bin
aws eks update-kubeconfig --name "$(terraform output -raw eks_cluster_name)" --region eu-west-1
# ConfigMaps con los endpoints reales (los manifiestos de 07-05 los referencian por nombre)
kubectl -n km0-prod create configmap plataforma-endpoints \
--from-literal=KAFKA_BOOTSTRAP="$(terraform output -raw msk_bootstrap_tls)" \
--from-literal=PG_INVENTARIO_HOST="$(terraform output -raw rds_inventario_endpoint)" \
--from-literal=REDIS_URL="rediss://$(terraform output -raw redis_endpoint):6379" \
--from-literal=S3_BUCKET_FOTOS="$(terraform output -raw bucket_fotos)" \
--dry-run=client -o yaml | kubectl apply -f -
# Los mismos manifiestos de 07-05: Deployments, Services, HPA, PDB, NetworkPolicies, canary
kubectl apply -k km0/k8s/overlays/aws-prod # kustomize: la base de 07-05 + parches (serviceAccountName, tolerations spot)
kubectl -n km0-prod rollout status deployment/pedidos --timeout=300sLo que desaparece de k8s/ al pasar a servicios gestionados: los StatefulSets de PostgreSQL con Patroni, de Kafka y de Redis, y sus operadores. Lo que se queda: los seis servicios, Kong, el mesh, Cassandra (si no se adopta Keyspaces), Prometheus/Loki/Tempo (si no se adopta CloudWatch) y Mosquitto. El HPA de 07-05 funciona igual; lo nuevo es Karpenter debajo. Y las pruebas de 07-06 se repiten contra el entorno staging desplegado con staging.tfvars (instancias pequeñas, sin Multi-AZ, sin DR), que se apaga por las noches.
El equivalente en GCP
Recurso en AWS (main.tf) |
Equivalente en GCP (proveedor google) |
Diferencia relevante |
|---|---|---|
| VPC con subredes por AZ | google_compute_network + google_compute_subnetwork (las subredes son regionales, abarcan todas las zonas) |
Menos subredes; la distribución por zona la hace GKE |
| EKS + grupos de nodos | GKE (google_container_cluster regional + google_container_node_pool, con Autopilot como opción sin nodos) |
GKE regional reparte el plano de control y los nodos en 3 zonas por defecto |
| RDS Multi-AZ | Cloud SQL con availability_type = "REGIONAL" |
Failover similar (~60 s); PITR con point_in_time_recovery_enabled |
| MSK | Confluent Cloud (Kafka) o Pub/Sub (modelo distinto: sin particiones ni offsets al estilo Kafka) | Pub/Sub exige reescribir consumidores; Confluent conserva el código |
| ElastiCache | Memorystore for Redis (tier = "STANDARD_HA") |
Sin modo clúster en los niveles básicos |
| S3 + CRR | GCS con bucket dual-region o multi-region | La replicación entre regiones es una propiedad del bucket, no una regla |
| IRSA | Workload Identity (service account de Kubernetes ↔ service account de Google) | Mismo concepto |
| Secrets Manager + KMS | Secret Manager + Cloud KMS | Equivalente |
| EMR / MWAA / Flink gestionado | Dataproc / Cloud Composer / Dataflow | Dataflow usa el modelo Beam, no la API de Flink |
| CloudWatch | Cloud Monitoring + Cloud Logging + Cloud Trace | Equivalente |
| Estado remoto en S3 + DynamoDB | Backend gcs (el cerrojo lo da el propio bucket) |
Más simple |
Errores Comunes y Consejos
- Suponer que "gestionado" significa "sin operar". RDS sigue reiniciando en la ventana de mantenimiento, MSK sigue necesitando dimensionar particiones, y nadie prueba las restauraciones por nosotros. Cambia el trabajo, no desaparece.
- Una sola AZ "para ahorrar". Un NAT, una subred, RDS sin Multi-AZ: la primera incidencia de AZ del proveedor es una caída total. Multi-AZ para todo lo que esté en el camino de un pedido.
- Olvidar el egress y el tráfico entre AZ en el presupuesto. Son las dos partidas que sorprenden en la primera factura. CDN para lo público, endpoints de VPC para S3,
client.racken los consumidores de Kafka. - Roles IAM con
*. Un rol de servicio cons3:*sobre todos los buckets convierte un Pod comprometido en una fuga total. Una política por service account, con acciones y recursos concretos (bloque 6). - El estado de Terraform en Git o en un portátil. Contiene secretos y es el único mapa de lo que existe. Backend remoto cifrado con versionado y cerrojo, siempre.
- Aplicar sin leer el plan. Un
-/+sobre una base de datos es una destrucción con recreación. El plan va en la pull request ydeletion_protectionen producción. - Elegir la región solo por precio o latencia. Los datos personales tienen restricciones de residencia; la región es una decisión con revisión legal, y documentada.
- Comprar reservas para el pico. Las reservas cubren la base que siempre está encendida; el pico va a spot o a bajo demanda. Comprar de más es pagar por capacidad que no se usa.
- Consejo: despliega primero
stagingcon la misma configuración de Terraform y tamaños pequeños; el 90 % de los errores de red, IAM y grupos de seguridad aparecen ahí a coste mínimo. - Consejo: pon el coste en Grafana junto al resto de métricas (los proveedores exportan el gasto diario por etiqueta). Un gráfico de euros por pedido es la métrica de negocio que más conversaciones útiles genera.
Ejercicios
Ejercicio 1: dominios de fallo en la VPC
Revisa el main.tf del apartado 10 y responde: (a) si la AZ b desaparece entera, qué componentes de Kilómetro Cero se ven afectados, qué mecanismo recupera cada uno y en cuánto tiempo aproximado; (b) qué habría pasado con one_nat_gateway_per_az = false; (c) qué componente del main.tf no sobreviviría a la pérdida de la región y qué habría que ejecutar para recuperarlo.
Ejercicio 2: la factura del cambio de decisión
Usando la tabla de costes del apartado 9 (precios ficticios), calcula el ahorro o el sobrecoste mensual aproximado de estas tres decisiones y razona si compensan: (a) sustituir MWAA por Airflow en EKS (estimado: 40 € de nodos); (b) pasar km0_analitica de RDS a Multi-AZ; (c) mover los nodos permanentes de EKS de bajo demanda a una reserva de 3 años (descuento estimado 55 % en lugar de 40 %) sabiendo que el equipo planea reducir a 4 nodos permanentes dentro de un año.
Ejercicio 3: lock-in selectivo
Para cada uno de estos componentes, decide si Kilómetro Cero debería usar el servicio gestionado del proveedor o desplegarlo en EKS, aplicando el criterio de "lock-in selectivo" del apartado 6 (beneficio del gestionado frente a coste de la dependencia): PostgreSQL, Kafka, Cassandra, Keycloak, Vault, Prometheus/Grafana. Justifica cada una en dos frases.
Soluciones
Ejercicio 1.
(a) Con la AZ b caída: RDS km0_inventario: el standby estaba en b (o el primario; RDS lo decide); si era el standby, no pasa nada visible y RDS crea uno nuevo en otra AZ; si era el primario, failover automático al standby en ~60 s, durante los cuales las reservas de stock fallan y la saga reintenta (07-04). MSK: el broker 2 cae; las particiones cuyo líder estaba allí eligen nuevo líder entre las ISR (03-03) en segundos; con min.insync.replicas=2 y RF=3 se sigue escribiendo. Cassandra: un nodo de tres; con QUORUM (2 de 3) se sigue leyendo y escribiendo. ElastiCache: si el primario estaba en b, failover automático a la réplica (~30 s) y la caché se rellena; si era la réplica, nada. EKS: los nodos de b desaparecen; Kubernetes reprograma sus Pods en a y c (07-05: topologySpreadConstraints garantizaba que ningún servicio tuviera todas sus réplicas en b) en 1-3 minutos, y Karpenter añade nodos si falta capacidad. ALB: deja de enviar a b tras fallar los health checks (segundos). NAT: el de b cae, pero los nodos de b también, así que nadie lo necesita.
(b) Un solo NAT en, por ejemplo, la AZ a: si cae a, los nodos de b y c siguen vivos pero pierden la salida a Internet: pagos no alcanza la pasarela, el pipeline no descarga imágenes. Un componente sano queda inutilizado por un dominio de fallo ajeno: exactamente lo que 07-03 llamaba redundancia no independiente.
(c) Todo lo de la región primaria. Lo que hay en la secundaria: la réplica de RDS (inventario_dr), el bucket replicado y el MSK mínimo con MirrorMaker. Para recuperar: terraform apply del mismo código con region = "eu-central-1" (unos 20-30 min para VPC + EKS + ElastiCache), promover la réplica de RDS (aws rds promote-read-replica, ~5 min), desplegar los manifiestos apuntando a los nuevos endpoints, restaurar Cassandra desde los snapshots replicados en S3 (el paso más largo y el que fija el RPO de pedidos), y cambiar el registro de Route 53. Es el runbook del pilot light de 07-03, y el RTO real es el que salga en el game day, no el de la tabla.
Ejercicio 2.
(a) MWAA 320 € → Airflow en EKS 40 €: ahorro ≈ 280 €/mes (3 360 €/año). Compensa si el equipo de plataforma asume operar Airflow (actualizaciones, base de metadatos, guardia); con el operador de Kubernetes y una instancia pequeña de RDS para los metadatos (~30 € más), sigue compensando, y además evita una dependencia específica del proveedor. Es el ejemplo de que lo gestionado no siempre gana.
(b) 210 € → aproximadamente el doble, ≈ 420 €: sobrecoste ≈ 210 €/mes. Para km0_analitica, cuya pérdida temporal no impide vender (RTO de horas en 07-03, se reconstruye desde el lago), no compensa; la decisión de 07-03 sigue siendo válida.
(c) Base de nodos: 900 € bajo demanda; con reserva de 1 año al 40 %: 540 €; con reserva de 3 años al 55 %: 405 €. Diferencia a favor de 3 años: 135 €/mes. Pero la reserva de 3 años cubre 6 nodos y dentro de un año solo se usarán 4: se pagarían 2 nodos reservados sin usar durante 2 años (≈ 2 × 67 € × 24 meses ≈ 3 200 €), frente a un ahorro de 135 × 12 = 1 620 € el primer año. No compensa: reserva de 1 año, renegociable cuando el dimensionamiento cambie. Las reservas se compran para la base estable, y "estable" incluye el futuro previsible.
Ejercicio 3.
- PostgreSQL → RDS (gestionado). Beneficio alto (Multi-AZ, PITR, parches sustituyen al clúster de Patroni completo) y lock-in bajo: es PostgreSQL estándar, un
pg_dumplo mueve a cualquier sitio. - Kafka → MSK (gestionado). Beneficio alto (el rolling de brokers de 07-05 y la gestión de discos desaparecen) y lock-in bajo: protocolo Kafka estándar, MirrorMaker lo replica a cualquier Kafka. En GCP, Confluent antes que Pub/Sub por la misma razón.
- Cassandra → en EKS (o EC2), no Keyspaces. Keyspaces cambia la semántica (transacciones ligeras, algunos tipos, modelo de coste por petición) y el repositorio de 04-04 necesitaría revisión: lock-in alto para un beneficio medio. Se mantiene autogestionado con el operador de 07-05, y se revisa si el equipo de plataforma no da abasto.
- Keycloak → en EKS, no Cognito. Cognito es más limitado en flujos y personalización, y migrar usuarios y clientes de un proveedor de identidad es doloroso: lock-in alto. Keycloak en dos réplicas con RDS detrás es fácil de operar.
- Vault → Secrets Manager (gestionado), con matiz. Para los secretos estáticos y su rotación, Secrets Manager con IRSA es más simple y se integra con IAM. Las credenciales dinámicas de base de datos y la PKI para mTLS que Vault daba en 06-04 no tienen equivalente directo: si el mesh asume el mTLS (08-01), Vault puede retirarse; si no, se mantiene Vault en EKS solo para eso.
- Prometheus/Grafana → en EKS. La instrumentación de
servicios/comun/es estándar (OpenTelemetry, exposición Prometheus) y los dashboards y alertas de 07-01 existen ya; CloudWatch costaría más y cambiaría el lenguaje de consulta (PromQL). Lock-in evitable a coste bajo. Los servicios gestionados de Prometheus/Grafana del proveedor son un punto medio aceptable si el almacenamiento de métricas crece.
Conclusión
La nube convierte la infraestructura en una API, y con ello cambia tres cosas en el diseño de Kilómetro Cero. La primera es la responsabilidad: IaaS, PaaS, SaaS y FaaS mueven la línea entre lo que hace el proveedor y lo que hace el cliente, pero los datos y las identidades quedan siempre a este lado. La segunda es la geografía: regiones y zonas de disponibilidad son dominios de fallo con nombre, las AZ absorben los fallos cotidianos con Multi-AZ en todo lo que toca un pedido, y la región secundaria del pilot light de 07-03 pasa a ser código de Terraform que no cuesta nada mientras no se ejecuta. La tercera es la sustitución: RDS por Patroni, MSK por los brokers de Ansible, S3 por MinIO, EKS por el clúster propio, con una tabla que dice qué se gana y qué se pierde en cada fila, y un criterio de lock-in selectivo (gestionado donde el beneficio es alto y la API estándar, propio donde la dependencia costaría más que la operación). Terraform describe la VPC de tres capas por AZ, EKS con nodos base y spot, RDS Multi-AZ con su réplica en otra región, MSK, ElastiCache, S3 con versionado y replicación, e IRSA para que cada Pod tenga solo los permisos que necesita; los manifiestos de 07-05 se despliegan casi sin cambios, y el plan de Terraform se revisa en la pull request como cualquier código. Los pilares de la arquitectura bien diseñada resumen el curso como lista de comprobación, y FinOps añade el coste como métrica: etiquetas, reservas para la base, spot para Spark y los picos, presupuestos con alerta, y una tabla que muestra que el cómputo es dos tercios de la factura, que dos servicios gestionados cuestan más que sus equivalentes en el clúster, y que el egress es la partida que el diseño más puede reducir.
Precisamente esa partida, y dos preguntas más, abren la siguiente lección. ¿Hace falta un servicio en Kubernetes, con sus réplicas, sus sondas y su guardia, para generar una miniatura cada vez que la Quesería Montblanc sube una foto, o para producir un PDF cuando llega un pago.confirmado? ¿Y por qué las fotos de queso-curado tienen que viajar desde Irlanda hasta el móvil de Ana en Valencia en cada visita, cuando podrían estar a veinte kilómetros de ella? Funciones que existen solo mientras hay un evento que procesar, y cómputo y caché en el borde de la red: Arquitecturas Serverless y Edge Computing.
Curso de Arquitecturas Distribuidas
Módulo 1: Introducción a los Sistemas Distribuidos
- Conceptos Básicos de Sistemas Distribuidos
- Modelos de Sistemas Distribuidos
- Ventajas y Desafíos de los Sistemas Distribuidos
- Las Falacias de la Computación Distribuida
- Tiempo, Relojes y Ordenación de Eventos
- Del Monolito a la Plataforma Distribuida: el Caso Kilómetro Cero
Módulo 2: Comunicación en Sistemas Distribuidos
- Protocolos de Comunicación
- RPC y RMI
- gRPC y Serialización de Datos
- Mensajería y Colas de Mensajes
- Patrones de Comunicación Asíncrona
Módulo 3: Consistencia y Replicación
- Modelos de Consistencia
- El Teorema CAP y PACELC
- Algoritmos de Consenso
- Replicación de Datos
- Transacciones Distribuidas y Sagas
Módulo 4: Almacenamiento Distribuido
- Particionado de Datos y Hashing Consistente
- Sistemas de Archivos Distribuidos
- Almacenamiento de Objetos
- Bases de Datos Distribuidas
- Cachés Distribuidos
Módulo 5: Computación Distribuida
- Modelos de Computación Distribuida
- MapReduce y Hadoop
- Spark y Computación en Memoria
- Procesamiento de Flujos de Datos
- Planificación de Trabajos y Pipelines de Datos
Módulo 6: Seguridad en Sistemas Distribuidos
- Autenticación y Autorización
- Cifrado y Protección de Datos
- Gestión de Identidades
- Seguridad entre Servicios: mTLS y Gestión de Secretos
- Puertas de Enlace, Limitación de Tasa y Auditoría
Módulo 7: Monitoreo y Mantenimiento
- Monitoreo de Sistemas Distribuidos
- Logs Centralizados y Trazabilidad Distribuida
- Gestión de Fallos y Recuperación
- Patrones de Resiliencia: Timeouts, Reintentos y Circuit Breaker
- Automatización y Orquestación
- Pruebas en Sistemas Distribuidos e Ingeniería del Caos
