Ejercicio 1 — El mapa sin lock
- La tabla para GCP (la del curso): Cloud Run (2M requests free tier, luego ~0.40 €/M), Cloud SQL PostgreSQL (no hay free tier: ~25 € el small), Memorystore Redis basic 1GB (~35 €), Cloud Storage (5GB free), Cloud DNS (~0.20 €/zona), Pub/Sub (10GB free). El arranque del lab: ~60% en free tier — la factura real del prod: ~100-150 €.
- El finding del SDK directo: el export RGPD (23) con
import boto3dentro del servicio (la URL y el SDK clavados). El fix con el Protocol del 24:
class ObjectStore(Protocol):
def put(self, key: str, data: bytes) -> None: ...
def signed_url(self, key: str, ttl: timedelta) -> str: ...
class GCSStore: # la implementación del borde; el servicio no la conoce
...El servicio firma el contrato; el proveedor es una línea en el settings (27).
- La traducción de 15 líneas: lo que cambió entre GCP y AWS: nombres (Cloud Run→Fargate, Cloud SQL→RDS, Memorystore→ElastiCache) y los manifests. Lo que NO cambió: la imagen del 40 (mismo digest despliega en los dos), las env vars del 27 (misma estructura), el pipeline del 41 (build/push a otro registry), el smoke de la 35. El costo del cambio de proveedor: ~1 día de manifests — ese es el precio del lock evitado.
Ejercicio 2 — El cómputo
- La meseta del 36 (200 VUs, mezcla realista): con concurrencia 1/instancia y p95 380 ms → ~52 instancias concurrentes en el pico (200 VUs × 1 req cada ~1 s con think time / 380 ms por req ≈ 76; con la mezcla 70/20/9/1 el browse cacheado (38) no llega al app: ~52 reales). Costo Cloud Run por uso: ~45 €/mes en ese pico × 1 h/día + baseline mínimo. La conclusion: el autoscaling por-uso PaaS es el termostato del presupuesto: el pico se paga solo cuando ocurre.
- El timeout del edge: el export RGPD síncrono tardaba 8-12 s (el job del 23 se lo movió a async con el 31, pero el endpoint de descarga del export grande: 14 s). Con timeout del edge 10 s: el request muere EN el edge (el cliente ve 504, el trabajo sigue y se pierde el download). El fix: el export SIEMPRE async con URL firmada (§3) — el edge timeout 60 s cubre el p99 × 2 de los endpoints sincrónicos, y nada síncrono tarda >5 s por diseño.
- El MTTR del pod: con healthcheck + restart: ~25-30 s (el healthcheck detecta en ~15 s con interval 5s×3, el restart+bind en ~10). En PaaS: el reemplazo de instancia ~20-60 s. El MTTR de "proceso muerto" es barato; el del "proceso zombi" (responde 500 sin morir) exige el healthcheck con DEPENDENCIA (el /healthz del 40 que toca la BD) — sin él, el pod sano-en-apariencia sirve 500 eternos y el 47 se pregunta por qué el MTTR de ese incidente fue de 40 min.
Ejercicio 3 — Los datos
- La aritmética contra el gestionado: Cloud SQL small = 100 max_connections (el tier básico). La tabla del 39 (90 conexiones de app) deja 10: insuficiente para el margen del 20%. Ajustes en orden: (a) pool max_size 10→6 por proceso (total 68: margen 32%); (b) si no cabe: PgBouncer transaction mode del 43 (la aritmética se vuelve "instancias × pool_bouncer" con SUS reglas — advisory locks fuera). El finding del ejercicio: el límite del gestionado es la PRIMERA constraint real del despliegue, no la CPU.
- El restore drill: el runbook (5 pasos): (1) identificar el backup objetivo (el PITR del momento del incidente, 47); (2) restaurar a una instancia NUEVA (jamás sobre el vivo: el restore sobre el vivo borra la única copia si falla); (3) conectar el app de test a la restaurada y correr el smoke (35): los datos son los esperados (el chequeo es del NEGOCIO: "¿está la reserva del cliente?"); (4) cronometrar (el small: 8-14 min el restore + 5 min el smoke); (5) el switchover DNS/config del app. El drill se corre 2×/año en el calendario del 31 — el backup no probado es una hipótesis.
- El bucket: la evidencia del link firmado: descarga OK a los 5 min;
403 Forbiddena los 16. La retención: la política del 23 — los exports expirados se borran a los 30 días (lifecycle rule) y el audit del 56 registra quién descargó qué. El bucket no es un disco: es un contrato con expiración y dueño.
Ejercicio 4 — La red y la factura
- La VPC (ASCII):
VPC 10.0.0.0/16
├── subnet-privada 10.0.1.0/24 # SIN IP pública
│ ├── Cloud SQL 10.0.1.10 ← solo: web(5432), worker(5432)
│ └── Memorystore 10.0.1.11 ← solo: web, worker
└── subnet-app 10.0.2.0/24
└── Cloud Run (salida NAT si habla a APIs externas: pasarela)Las reglas: el Postgres acepta SOLO 5432 desde el rango de la subnet-app; el Redis igual; el app acepta 8080 desde el edge/proxy. Nada del privado tiene salida a Internet directa: el worker del export usa el endpoint privado de GCS (Private Service Connect): el NAT no existe en la arquitectura y su €35/mes tampoco.
- El egreso: browse 70% del tráfico ≈ 22 GB/mes de JSON sin CDN → egreso ~1.8 € (barato pero crece con el tráfico ×10 del 55: 18 €); CON el CDN del 38: el 90% sale del edge (~0.6 €) y el origen solo ve los miss. El CDN no "paga su costo" a esta escala en euros: paga en p95 (38 ms vs 210 ms) — la factura es la excusa, la latencia es la razón.
- Los huérfanos y su impuesto: IP estática del experimento k6 del 36 (3.6 €/mes), disco del PG de test del 34 (4 €/mes), snapshot del restore drill (2 €/mes), el NAT provisional del mes pasado (35 €/mes), el cluster K8s del 53 que se levantó para "probar" (70 €/mes). Total: ~115 €/mes = el 70% de la factura REAL del sistema. La revisión mensual (31) con la pregunta "¿esto sirve a algún entorno vivo?" es la tarea más rentable del calendario.
Ejercicio 5 — El IAM
- La política del export worker:
{
"Version": "2027-01-01",
"Statement": [
{"Effect": "Allow", "Action": ["storage.objects.create"],
"Resource": "projects/ticketflow/buckets/exports-rgpd/objects/*"},
{"Effect": "Deny", "Action": ["storage.objects.delete", "storage.buckets.*"],
"Resource": "*"}
]
}Puede: crear objetos en el bucket de exports. NO puede: borrarlos, tocar otro bucket, ni nada de IAM/BD. Radio de daño si lo comprometen (22): escribir basura en un bucket (sanitizable con lifecycle) — no leer el bucket de otro cliente (la política no lo permite ni por error), no tocar la BD (no tiene la identidad).
- El anti-patrón encontrado: en la historia temprana del repo (06), un
.envcon la key de la pasarela sandbox commiteado 4 meses. El fix: (1) revocar YA (la rotación dual del 27 en el gestor de secretos); (2) la key está en la historia de git: elgit filter-repo/BFG es el tratamiento, PERO la asunción operativa es "toda key en la historia está comprometida" — se rota y se muere, la historia se limpia como cosmética; (3) el pre-commit del 06 con el secret-scan (gitleaks) como el guard que evita la siguiente.
- La auditoría: Cloud Audit Logs encendido por proyecto (data access logs para el bucket del export). El evento de borrado del bucket registra: actor (identidad IAM, no IP), timestamp, source (consola/API), y el trace del request. Sin el log: el "¿quién borró el bucket?" del postmortem (47) es una conversación de fe — con el log: una línea con nombre y hora. El audit log es barato (~gratis); el no-auditado es caro el día que se cobra.
Resumen del profesor
- La imagen + config por entorno hacen reversible al proveedor; el negocio habla Protocols (24), el SDK vive en el borde.
- Gestionado para los datos (la aritmética de conexiones sigue siendo tuya), PaaS para el cómputo, el objeto-store con URLs firmadas y expiración.
- El muro es el IAM: rol de servicio sin claves eternas, mínimo privilegio por worker, audit log siempre — y la factura se revisa por huérfanos, no por miedo.