Si le preguntas a la dirección de AlpinaShop qué nivel de servicio espera de la tienda, la respuesta será "que no se caiga nunca". Es una respuesta comprensible y completamente inútil, por tres motivos: no se puede cumplir, no se puede medir, y no permite decidir nada.
No se puede cumplir porque el 100 % de disponibilidad no existe: falla el hardware, falla la red del cliente, falla el proveedor de DNS, falla un despliegue, se equivoca una persona. No se puede medir porque nadie ha definido qué cuenta como "caída". Y no permite decidir nada porque si el objetivo es la perfección, cualquier inversión en fiabilidad está justificada y ninguna es suficiente, lo cual en la práctica significa que las decisiones se toman por intuición.
La lección anterior terminó señalando exactamente eso: AlpinaShop paga la alta disponibilidad de Cloud SQL, ha duplicado los túneles VPN y mantiene el balanceador global sin ningún objetivo declarado que justifique esas tres decisiones con números. Y arrastra desde 07-04 una deuda calificada de crítica: nadie ha comprobado nunca que las copias de seguridad se puedan restaurar.
Esta lección convierte la fiabilidad en ingeniería. Al terminar sabrás definir SLI, SLO y SLA con precisión, calcular y usar un presupuesto de error para decidir cuándo se despliega y cuándo se para, crear SLO y alertas por consumo de presupuesto en Cloud Monitoring, elegir el nivel de alta disponibilidad de cada componente sabiendo lo que cuesta y lo que aporta, diseñar contra los modos de fallo reales, y tener un plan de recuperación ante desastres con RTO y RPO — ensayado, no escrito.
Contenido
- Qué significa fiabilidad y por qué la perfección no es un objetivo
- SLI, SLO y SLA: las tres siglas que todo el mundo confunde
- Elegir buenos SLI para AlpinaShop
- Los SLO de AlpinaShop, con sus consultas reales
- Presupuesto de error: qué es y para qué sirve de verdad
- SLO en Cloud Monitoring y alertas por consumo de presupuesto
- Arquitecturas de alta disponibilidad por niveles
- Modos de fallo y su mitigación
- Recuperación ante desastres: RTO, RPO y estrategias
- El plan de recuperación de AlpinaShop
- Backups que no se han probado no existen
- Práctica operativa: guardias, runbooks y post-mortem
- Pruebas de caos a escala razonable
- Qué significa fiabilidad y por qué la perfección no es un objetivo
Fiabilidad es la probabilidad de que un sistema haga lo que se espera de él durante un periodo determinado. No es una propiedad binaria: es una magnitud continua que se elige, se paga y se gestiona.
La curva que hay que tener en la cabeza:
| Objetivo | Coste relativo | Qué exige |
|---|---|---|
| 99 % | 3 | Un servidor bien configurado |
| 99,9 % | 8 | Multizona, despliegue canario, reversión rápida |
| 99,95 % | 15 | Lo anterior + guardias en horario ampliado |
| 99,99 % | 35 | Multirregión, guardias 24×7, automatización de conmutación |
| 99,999 % | 95 | Activo-activo, ingeniería dedicada, despliegues mucho más lentos |
Cada nueve adicional multiplica el coste, y no de forma lineal. Pasar de 99 % a 99,9 % suele ser cuestión de buenas prácticas. Pasar de 99,99 % a 99,999 % requiere multirregión activo-activo, guardias 24×7, ingeniería dedicada y aceptar que despliegas mucho más despacio.
Y la otra mitad de la ecuación, la que casi nunca se calcula:
| Disponibilidad | Tiempo caído al mes | Pedidos perdidos (AlpinaShop) | Coste aproximado del corte |
|---|---|---|---|
| 99 % | 7 h 18 min | ~12 | ~780 € |
| 99,5 % | 3 h 39 min | ~6 | ~390 € |
| 99,9 % | 43 min 50 s | ~1,2 | ~78 € |
| 99,95 % | 21 min 55 s | ~0,6 | ~39 € |
| 99,99 % | 4 min 23 s | ~0,1 | ~8 € |
| 99,999 % | 26 s | ~0 | ~0 € |
Con 1.200 pedidos al mes y un ticket medio de 65 €, pasar de 99,9 % a 99,99 % le ahorraría a AlpinaShop unos 70 € al mes en pedidos no perdidos — y le costaría multirregión activo-activo, es decir, varios cientos de euros mensuales más el trabajo de ingeniería.
La conclusión incómoda y correcta: para AlpinaShop, 99,99 % sería una mala inversión. Y saber decir eso con números en la mano, en lugar de perseguir nueves por inercia, es de lo más valioso que puede aportar un ingeniero.
Con dos matices que hay que hacer explícitos:
- No todo el tiempo vale igual. Cuarenta minutos de caída de madrugada en febrero cuestan casi nada; cuarenta minutos el viernes de la campaña de otoño pueden costar veinte veces más. La disponibilidad importa ponderada por tráfico.
- Hay costes que no son pedidos perdidos: reputación, clientes que no vuelven, soporte, y —en un incidente largo— el desgaste del equipo.
- SLI, SLO y SLA: las tres siglas que todo el mundo confunde
| Sigla | Qué es | Naturaleza | Ejemplo en AlpinaShop |
|---|---|---|---|
| SLI (Service Level Indicator) | Lo que mides | Un número, una proporción | % de peticiones al catálogo con código < 500 |
| SLO (Service Level Objective) | El objetivo que te pones | Interno, un umbral sobre el SLI | 99,9 % en 28 días |
| SLA (Service Level Agreement) | Lo que prometes por contrato | Externo, con penalización | 99,5 %, o se devuelve el 10 % |
La relación entre los tres, que es lo que hay que interiorizar:
flowchart LR
M["Metricas crudas<br/>Cloud Monitoring"] --> SLI["SLI<br/>buenas / validas"]
SLI --> SLO["SLO<br/>umbral interno"]
SLO --> EB["Presupuesto<br/>de error"]
EB --> DEC["Decisiones:<br/>desplegar o estabilizar"]
SLO -.->|"siempre mas exigente"| SLA["SLA<br/>compromiso externo"]
Tres reglas que evitan casi todos los errores:
- El SLO siempre es más exigente que el SLA. Si prometes 99,5 % y tu objetivo interno es 99,5 %, incumplirás el contrato la mitad de las veces. La práctica habitual es un margen holgado: SLA 99,5 %, SLO 99,9 %.
- El SLI se mide desde donde está el usuario, no desde donde es cómodo. Un servidor que responde perfectamente mientras el balanceador devuelve 502 tiene un SLI del 100 % y un usuario furioso.
- Un SLO sin consecuencia es un adorno. Si al incumplirlo no cambia nada, no es un objetivo: es un número en un panel.
Y una definición precisa de SLI que resuelve la mayoría de las dudas:
SLI = eventos buenos / eventos válidos
Todo lo difícil está en definir esos dos términos. ¿Cuenta como "malo" un 404? (No: el usuario pidió algo que no existe.) ¿Y un 429 del limitador de tasa? (Depende: si es un ataque, no; si es un cliente legítimo, sí.) ¿Y una petición de un robot rastreador? (Probablemente no debería ser un evento válido.) Esas decisiones son el 80 % del trabajo de definir un SLI, y hay que tomarlas por escrito.
- Elegir buenos SLI para AlpinaShop
Un buen SLI cumple cuatro condiciones:
| Condición | Significa | Contraejemplo |
|---|---|---|
| Refleja la experiencia del usuario | Si el SLI está bien, el usuario está contento | CPU del servidor |
| Es medible con lo que ya tienes | Sale de métricas existentes | "Satisfacción del cliente" |
| Es predictivo | Cuando se degrada, el negocio se resiente | Número de logs |
| Es accionable | Sabes qué hacer si baja | "Salud general del sistema" |
El error más común es medir lo fácil en lugar de lo importante. La CPU es facilísima de medir y no le importa a ningún cliente. Los "cuatro señales de oro" —latencia, tráfico, errores y saturación— son un buen punto de partida, pero solo las dos primeras y la tercera son candidatas a SLI; la saturación es una causa, no una experiencia.
Los cuatro SLI de AlpinaShop, elegidos recorriendo el viaje del cliente:
SLI 1 — Disponibilidad del catálogo
Eventos válidos: peticiones HTTP al balanceador con rutas de la tienda, excluyendo rastreadores conocidos. Eventos buenos: las que devuelven código < 500.
Decisiones tomadas y anotadas:
| Caso | ¿Válido? | ¿Bueno? | Razón |
|---|---|---|---|
200 OK |
Sí | Sí | — |
404 producto inexistente |
Sí | Sí | El sistema funcionó correctamente |
429 de Cloud Armor a un atacante |
No | — | No es un usuario a quien servir |
500, 502, 503 |
Sí | No | Fallo nuestro |
499 cliente cortó |
No | — | Se fue el cliente, no falló el sistema |
| Petición de un rastreador | No | — | No es experiencia de usuario |
SLI 2 — Latencia del catálogo
Eventos válidos: las mismas peticiones que en el SLI 1. Eventos buenos: las que se sirven en menos de 800 ms.
Ojo con el error clásico: un SLI de latencia no es "el p95 es menor que X". Es la proporción de peticiones más rápidas que X. La diferencia importa: un percentil no se puede combinar con un presupuesto de error, y una proporción sí.
El umbral de 800 ms no se elige por intuición: se mide la distribución actual, se comprueba dónde está el punto a partir del cual los usuarios abandonan, y se elige un valor que hoy se cumpla holgadamente pero que detecte una degradación real.
SLI 3 — Éxito del proceso de pago
El más importante de los cuatro, porque es el que toca el dinero.
Eventos válidos: intentos de pago iniciados. Eventos buenos: los que terminan en confirmación o en un rechazo legítimo de la pasarela (fondos insuficientes, tarjeta caducada).
El matiz que lo hace correcto: un rechazo de la pasarela por fondos insuficientes no es un fallo nuestro. Contarlo como error haría que el SLI dependiera de la solvencia de los clientes, no de la calidad del sistema. Lo que sí cuenta como malo: tiempos de espera, errores 5xx, excepciones y transacciones que quedan en estado indeterminado.
SLI 4 — Frescura de los datos analíticos
Distinto de los anteriores porque no es una petición: es un proceso.
Eventos válidos: ejecuciones programadas del pipeline de pedidos. Eventos buenos: las que dejan los datos en BigQuery con menos de 2 horas de retraso.
La tabla de SLI
| SLI | Qué mide | Fuente | Criticidad |
|---|---|---|---|
| Disponibilidad del catálogo | ¿Se puede comprar? | Logs del balanceador | Crítica |
| Latencia del catálogo | ¿Es usable? | Logs del balanceador | Alta |
| Éxito del pago | ¿Se cobra? | Métrica personalizada (06-04) | Crítica |
| Frescura analítica | ¿Decide bien Lucía? | Métrica de Dataflow / Workflows | Media |
Nótese que no hay SLI de CPU, memoria, número de instancias ni tamaño de la cola. Todo eso se monitoriza —hace falta para diagnosticar— pero no es un objetivo de nivel de servicio, porque a ningún cliente le importa.
- Los SLO de AlpinaShop, con sus consultas reales
| SLI | SLO | Ventana | Presupuesto de error | Justificación |
|---|---|---|---|---|
| Disponibilidad del catálogo | 99,9 % | 28 días móviles | 40 min 19 s | Equilibrio coste/valor del apartado 1 |
| Latencia < 800 ms | 99 % | 28 días móviles | 6 h 43 min | Una petición lenta molesta; no impide comprar |
| Éxito del pago | 99,95 % | 28 días móviles | 20 min 10 s | Más exigente: aquí se pierde dinero directo |
| Frescura analítica < 2 h | 99 % | 28 días móviles | 6 h 43 min | Un retraso puntual no afecta a las decisiones |
Por qué 28 días y no un mes natural: una ventana móvil de 28 días contiene exactamente cuatro semanas, así que siempre incluye el mismo número de fines de semana y de lunes. Con meses naturales, febrero y marzo no son comparables, y el presupuesto se "reinicia" el día 1, lo que crea el incentivo perverso de esperar al cambio de mes para desplegar lo arriesgado.
Las consultas reales, sobre las métricas de 06-04, en PromQL —que es el lenguaje que Cloud Monitoring admite hoy junto a MQL:
# SLI 1 - Disponibilidad del catalogo
sum(rate(loadbalancing_googleapis_com:https_request_count{
monitored_resource="https_lb_rule",
url_map_name="alpinashop-url-map",
response_code_class!="500"
}[5m]))
/
sum(rate(loadbalancing_googleapis_com:https_request_count{
monitored_resource="https_lb_rule",
url_map_name="alpinashop-url-map"
}[5m]))# SLI 2 - Latencia: proporcion de peticiones bajo 800 ms
sum(rate(loadbalancing_googleapis_com:https_total_latencies_bucket{
url_map_name="alpinashop-url-map", le="800"
}[5m]))
/
sum(rate(loadbalancing_googleapis_com:https_total_latencies_count{
url_map_name="alpinashop-url-map"
}[5m]))Para el pago, se usa la métrica personalizada que la aplicación ya emite desde 06-04:
# En el codigo del catalogo, tras cada intento de pago
from opentelemetry import metrics
medidor = metrics.get_meter("alpinashop.pagos")
contador = medidor.create_counter(
"pagos_intentos",
description="Intentos de pago por resultado",
)
def registrar_pago(resultado: str, pasarela: str):
# resultado: "confirmado" | "rechazado_legitimo" | "error_tecnico"
contador.add(1, {"resultado": resultado, "pasarela": pasarela})# SLI 3 - Exito del pago: confirmados + rechazos legitimos / total
sum(rate(custom_googleapis_com:pagos_intentos{
resultado=~"confirmado|rechazado_legitimo"
}[5m]))
/
sum(rate(custom_googleapis_com:pagos_intentos[5m]))La separación entre rechazado_legitimo y error_tecnico es una decisión de instrumentación que hay que tomar en el código, y es exactamente donde se ve si alguien ha pensado el SLI o lo ha copiado. Si la aplicación solo registra "éxito" y "fallo", el SLI de pago es irreparablemente ambiguo.
- Presupuesto de error: qué es y para qué sirve de verdad
Presupuesto de error = 100 % − SLO
Con un SLO del 99,9 % en 28 días, el presupuesto es del 0,1 %: 40 minutos y 19 segundos de indisponibilidad, o su equivalente en peticiones fallidas.
La tabla de "nueves" traducida a tiempo, que conviene tener a mano:
| SLO | Al día | A la semana | A los 28 días | Al año |
|---|---|---|---|---|
| 99 % | 14 min 24 s | 1 h 40 min | 6 h 43 min | 3 d 15 h |
| 99,5 % | 7 min 12 s | 50 min | 3 h 22 min | 1 d 19 h |
| 99,9 % | 1 min 26 s | 10 min 5 s | 40 min 19 s | 8 h 46 min |
| 99,95 % | 43 s | 5 min 2 s | 20 min 10 s | 4 h 23 min |
| 99,99 % | 8,6 s | 1 min | 4 min 2 s | 52 min 36 s |
| 99,999 % | 0,9 s | 6 s | 24 s | 5 min 15 s |
Para qué sirve de verdad
Aquí está la parte que transforma el concepto de curiosidad en herramienta de gestión. El presupuesto de error resuelve la tensión permanente entre desarrollo y operaciones:
| Desarrollo quiere | Operaciones quiere | |
|---|---|---|
| Ritmo | Desplegar rápido y a menudo | Cambiar poco |
| Riesgo | Probar cosas nuevas | Estabilidad |
| Incentivo | Funcionalidades | Que no suene el teléfono |
Sin un criterio objetivo, esa tensión se resuelve por autoridad, por cansancio o por quién grita más. Con presupuesto de error, se resuelve con un número:
Mientras quede presupuesto, se despliega. Cuando se agota, se para y se estabiliza.
Y esto convierte la fiabilidad en algo que desarrollo también quiere, porque un sistema frágil consume el presupuesto y le impide entregar funcionalidades. El incentivo deja de estar enfrentado.
La política de presupuesto de error de AlpinaShop
Escrita, acordada y en el README de alpinashop-infra:
| Presupuesto consumido | Estado | Qué se puede hacer |
|---|---|---|
| < 50 % | 🟢 Verde | Todo normal. Se despliega a diario. Se puede experimentar |
| 50-75 % | 🟡 Amarillo | Se despliega, pero con canario más largo y sin cambios de esquema los viernes |
| 75-100 % | 🟠 Naranja | Solo correcciones y mejoras de fiabilidad. Nada de funcionalidades nuevas |
| > 100 % | 🔴 Rojo | Congelación de funcionalidades. Todo el equipo a estabilizar. Post-mortem obligatorio |
Y la cláusula que hace que la política sea creíble:
Si el presupuesto se agota por un fallo de la plataforma de Google y no por nuestros cambios, se documenta y no se congela. El objetivo es corregir lo que está en nuestra mano, no castigar al equipo por lo que no controla.
Sin esa cláusula, la primera caída regional de Google convertiría la política en algo que el equipo dejaría de respetar — y una política que se ignora es peor que no tenerla.
El uso que casi nadie hace: gastar el presupuesto a propósito
El presupuesto de error no es un objetivo que minimizar. Si al final de los 28 días has consumido el 5 %, no has ganado: probablemente estabas siendo demasiado conservador y podías haber entregado más rápido, o tu SLO es demasiado laxo y no refleja lo que los usuarios notan.
Un presupuesto que nunca se toca es tan mala señal como uno que siempre se agota. El consumo sano ronda el 50-80 %.
- SLO en Cloud Monitoring y alertas por consumo de presupuesto
Cloud Monitoring tiene soporte nativo para SLO, y merece la pena usarlo en lugar de construirlo a mano.
Crear el SLO
gcloud monitoring slos create \
--service=alpinashop-web-service \
--slo-id=disponibilidad-catalogo \
--display-name="Disponibilidad del catalogo 99.9%" \
--goal=0.999 \
--rolling-period=28d \
--request-based-good-total-ratio \
--project=alpinashop-prodY en Terraform, que es donde debe vivir:
resource "google_monitoring_slo" "disponibilidad_catalogo" {
project = var.proyecto_prod
service = google_monitoring_custom_service.tienda.service_id
slo_id = "disponibilidad-catalogo"
display_name = "Disponibilidad del catalogo 99.9%"
goal = 0.999
rolling_period_days = 28
request_based_sli {
good_total_ratio {
good_service_filter = join(" AND ", [
"metric.type=\"loadbalancing.googleapis.com/https/request_count\"",
"resource.type=\"https_lb_rule\"",
"resource.label.\"url_map_name\"=\"alpinashop-url-map\"",
"metric.label.\"response_code_class\"!=\"500\"",
])
total_service_filter = join(" AND ", [
"metric.type=\"loadbalancing.googleapis.com/https/request_count\"",
"resource.type=\"https_lb_rule\"",
"resource.label.\"url_map_name\"=\"alpinashop-url-map\"",
])
}
}
}Dos tipos de SLI que hay que distinguir:
| Tipo | Cómo cuenta | Cuándo usarlo |
|---|---|---|
Basado en peticiones (request_based) |
Proporción de peticiones buenas | El habitual. Servicios con tráfico |
Basado en ventanas (windows_based) |
Proporción de minutos "buenos" | Servicios de bajo tráfico, o cuando importa la duración del corte más que el número de afectados |
Para AlpinaShop, basado en peticiones. Con un matiz honesto: de madrugada, con tres peticiones por minuto, un solo error es el 33 % de ese minuto. Los SLI basados en peticiones son ruidosos con tráfico bajo, y por eso las alertas del apartado siguiente exigen ventanas largas para los umbrales suaves.
Alertas por consumo de presupuesto (burn rate)
Esta es la parte más valiosa del apartado, y sustituye a las alertas de umbral fijo de 06-04.
La tasa de consumo (burn rate) es cuántas veces más rápido de lo sostenible se está gastando el presupuesto:
- Tasa 1 = a este ritmo, el presupuesto se agota exactamente al final de los 28 días. Es el ritmo sostenible.
- Tasa 14,4 = se agota en 2 días.
- Tasa 6 = se agota en poco menos de 5 días.
El problema de alertar sobre un umbral fijo de errores es conocido: si el umbral es sensible, hay falsas alarmas; si es laxo, no detecta degradaciones lentas. Las alertas de tasa de consumo resuelven las dos cosas con dos alertas complementarias.
| Alerta | Tasa | Ventanas | Consumo | Acción |
|---|---|---|---|---|
| Rápida | 14,4× | 1 h y 5 min | 2 % del presupuesto en 1 h | 📟 Despertar a alguien |
| Lenta | 6× | 6 h y 30 min | 5 % del presupuesto en 6 h | 🎫 Crear un ticket, horario laboral |
Por qué dos ventanas por alerta. La ventana larga (1 h) evita falsas alarmas por un pico de treinta segundos. La ventana corta (5 min) hace que la alerta se apague rápido cuando el problema se resuelve — sin ella, una alerta con ventana de una hora seguiría sonando cincuenta minutos después de arreglado el fallo. Es un detalle pequeño con un impacto enorme en la vida del que está de guardia.
resource "google_monitoring_alert_policy" "consumo_rapido" {
project = var.proyecto_prod
display_name = "SLO catalogo: consumo rapido del presupuesto (14.4x)"
combiner = "AND"
conditions {
display_name = "Tasa de consumo > 14.4 en 1h y en 5min"
condition_threshold {
filter = join(" AND ", [
"select_slo_burn_rate(\"${google_monitoring_slo.disponibilidad_catalogo.name}\", \"3600s\")",
])
threshold_value = 14.4
comparison = "COMPARISON_GT"
duration = "300s"
}
}
notification_channels = [var.canal_movil_marta, var.canal_slack_incidentes]
documentation {
content = <<-EOT
# Consumo rapido del presupuesto de error
A este ritmo, el presupuesto de 28 dias se agota en menos de 2 dias.
## Primeros pasos
1. Panel de campana: https://console.cloud.google.com/monitoring/dashboards/...
2. Ultimo despliegue: `gcloud run revisions list --service=alpinashop-web --region=europe-west1`
3. Si coincide con un despliegue reciente, REVERTIR PRIMERO:
`gcloud run services update-traffic alpinashop-web --region=europe-west1 --to-revisions=REVISION_ANTERIOR=100`
4. Errores por tipo: Error Reporting
5. Runbook completo: alpinashop-infra/runbooks/catalogo-5xx.md
EOT
mime_type = "text/markdown"
}
}El bloque documentation es lo más importante de la política, y lo que casi todo el mundo deja vacío. Una alerta que llega al móvil a las 3 de la madrugada con el texto "Umbral superado" obliga a la persona a reconstruir el contexto medio dormida. Una que llega con los tres primeros pasos y el enlace al runbook convierte quince minutos de desorientación en dos de acción. Se desarrolla en el apartado 12.
- Arquitecturas de alta disponibilidad por niveles
La alta disponibilidad se construye eliminando puntos únicos de fallo, y se hace por niveles, cada uno con su precio.
| Nivel | Qué resiste | Qué NO resiste | Coste relativo |
|---|---|---|---|
| Una zona | Fallo de una máquina | Caída de la zona (mantenimiento, corte eléctrico) | 1× |
| Multizona (regional) | Caída de una zona completa | Caída de la región entera | 1,5-2× |
| Multirregión | Caída de una región | Fallo global de la plataforma o error propio replicado | 2,5-4× |
| Multinube | Fallo global de un proveedor | Errores propios | 4-10× |
La frecuencia relativa de cada evento es lo que debería guiar la decisión, y rara vez se mira:
| Evento | Frecuencia aproximada |
|---|---|
| Fallo de una VM concreta | Semanal en flotas grandes |
| Degradación de una zona | Unas pocas veces al año |
| Caída de una región completa | Muy infrecuente, pero ocurre |
| Fallo global de un proveedor | Extraordinario |
| Error humano o despliegue defectuoso | La causa más común de todas |
La última fila es la que cambia las prioridades: la mayoría de las caídas no las provoca la infraestructura, sino un cambio. Invertir en multirregión mientras no tienes despliegue canario ni reversión rápida es blindar la puerta y dejar la ventana abierta. AlpinaShop hizo lo correcto: primero pipeline y reversión (módulo 6 y 07-02), después arquitectura.
Componente a componente
| Componente | Nivel actual | Cómo subir | Coste extra | ¿Merece la pena? |
|---|---|---|---|---|
| Balanceador global | Multirregión por diseño | — | — | ✅ Ya está |
| Cloud DNS | Anycast global | — | — | ✅ Ya está |
| Cloud Armor | Global | — | — | ✅ Ya está |
| Cloud CDN | Global | — | — | ✅ Ya está |
| Cloud Run | Regional: reparte entre zonas solo | Segundo servicio en europe-southwest1 + NEG |
~+7 €/mes | ⏸️ Documentado, no hecho |
| Cloud SQL | HA regional (réplica en otra zona) | Réplica de lectura en otra región | ~+55 €/mes | ⏸️ Evaluado, no hecho |
| Cloud Storage | Regional | Bucket birregional o multirregión | ~+30 % | ✅ Hecho en el bucket crítico |
| Firestore | Multirregión | — | — | ✅ Ya está |
| BigQuery | Multirregión EU | — | — | ✅ Ya está |
| Artifact Registry | Regional | Réplica | Bajo | ⏸️ No crítico |
| Secret Manager | Replicación automática | — | — | ✅ Ya está |
| VPN a Sabadell | Dos túneles | Segunda pasarela en Sabadell | ~+30 €/mes | ❌ No: la tienda no es crítica |
Observación importante: una parte grande de la arquitectura de AlpinaShop ya es multirregión sin esfuerzo, porque los servicios globales de Google lo son por diseño. El balanceador, DNS, CDN, Armor, Firestore, BigQuery y Secret Manager no requieren ninguna decisión. Los dos componentes que sí son regionales y críticos son Cloud Run y Cloud SQL.
Cloud SQL: qué es exactamente HA regional
Merece detalle porque se paga el doble y mucha gente no sabe qué compra.
flowchart LR
APP["Cloud Run"] --> IP["IP privada unica"]
IP --> P["Instancia primaria<br/>europe-west1-b"]
P <-->|"replicacion sincrona<br/>de disco"| S["Instancia en espera<br/>europe-west1-c"]
P --> R["Replica de lectura<br/>alpinashop-pedidos-replica-informes"]
R -.->|"solo lectura"| LU["Consultas de Lucia"]
style S fill:#e8f0fe,stroke:#4285f4
| Aspecto | Detalle |
|---|---|
| Qué replica | El disco, de forma síncrona. Cero pérdida de datos |
| Dónde | Otra zona de la misma región |
| Conmutación | Automática, típicamente 1-2 minutos |
| Qué ve la aplicación | La misma IP. Reconecta sola si tiene reintentos |
| Coste | El doble de la instancia |
| Qué no cubre | Caída de la región completa; borrado accidental de datos; corrupción lógica |
La última fila es la que hay que subrayar. HA regional protege contra fallos de infraestructura. No protege contra un DELETE FROM pedidos sin WHERE, que se replica al instante en la instancia en espera. Para eso están las copias de seguridad y la recuperación a un punto en el tiempo — apartado 11.
Cómo sería Cloud Run multirregión
Documentado en alpinashop-infra como diseño listo para activar:
# 1. Desplegar el mismo servicio en la segunda region
gcloud run deploy alpinashop-web \
--image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
--region=europe-southwest1 \
[... mismas banderas ...]
# 2. NEG sin servidor en esa region
gcloud compute network-endpoint-groups create neg-catalogo-run-esw1 \
--region=europe-southwest1 \
--network-endpoint-type=serverless \
--cloud-run-service=alpinashop-web
# 3. Anadirlo al MISMO servicio de backend
gcloud compute backend-services add-backend bs-catalogo-web \
--global \
--network-endpoint-group=neg-catalogo-run-esw1 \
--network-endpoint-group-region=europe-southwest1Tres comandos. El balanceador global envía cada petición a la región más cercana con capacidad, y si una región falla, deja de enviarle tráfico automáticamente.
Lo que impide activarlo hoy no es Cloud Run: es Cloud SQL. Con la base de datos solo en europe-west1, las instancias de Madrid tendrían que cruzar región en cada consulta —latencia y coste de tráfico— y si cae europe-west1, la base de datos cae igual. La alta disponibilidad la determina el componente menos disponible, y en AlpinaShop ese es Cloud SQL.
- Modos de fallo y su mitigación
Los sistemas distribuidos fallan de formas concretas y conocidas. Diseñar contra ellas es más rentable que añadir redundancia.
Dependencias y su clasificación
Lo primero es distinguir dos tipos, porque el tratamiento es opuesto:
| Tipo | Definición | Ejemplo | Si falla |
|---|---|---|---|
| Dura | Sin ella el servicio no puede funcionar | Cloud SQL para el catálogo | Fallo del servicio |
| Blanda | Aporta valor pero no es imprescindible | El recomendador, la analítica | El servicio debe seguir |
El fallo de diseño más común es tratar una dependencia blanda como si fuera dura. Un recomendador que tarda 5 segundos no debe hacer que la página de producto tarde 5 segundos: debe desaparecer de la página.
Tiempos de espera
Toda llamada de red debe tener un tiempo de espera. Sin excepciones.
Un tiempo de espera ausente o demasiado largo es el mecanismo por el que un fallo pequeño se convierte en una caída total: las peticiones se acumulan esperando, se agotan los hilos, y el servicio deja de responder a todo, incluido lo que no dependía del sistema lento.
| Llamada | Tiempo de espera | Razón |
|---|---|---|
| Cloud SQL, consulta normal | 2 s | Si tarda más, algo va mal |
| Cloud SQL, conexión | 5 s | — |
| Pasarela de pago | 10 s | Es lenta por naturaleza |
| Recomendador (blanda) | 300 ms | Si no responde rápido, no se muestra |
| ERP de Sabadell por VPN | 5 s | Puede estar el enlace regular |
| Secret Manager | 3 s | Se cachea en memoria tras la primera lectura |
Regla de oro: el tiempo de espera de una llamada siempre debe ser menor que el tiempo de espera del que llama. Si Cloud Run corta a los 60 s y tu consulta espera 90 s, ese código nunca se ejecuta.
Reintentos con retroceso exponencial y jitter
Reintentar está bien. Reintentar mal empeora el fallo:
# MAL: reintento inmediato. Ante un fallo, multiplica la carga sobre
# un servicio que ya esta sufriendo. Es como golpear a alguien caido.
for intento in range(5):
try:
return llamar()
except Exception:
continue
# BIEN: retroceso exponencial + jitter + limite + solo errores transitorios
import random, time
TRANSITORIOS = (503, 502, 504, 429)
def llamar_con_reintentos(fn, max_intentos=4, base=0.2, tope=8.0):
for intento in range(max_intentos):
try:
return fn()
except ErrorHTTP as e:
if e.codigo not in TRANSITORIOS or intento == max_intentos - 1:
raise # no se reintenta lo permanente
espera = min(tope, base * (2 ** intento)) # 0.2, 0.4, 0.8, 1.6...
espera = espera * (0.5 + random.random()) # jitter: +/- 50%
time.sleep(espera)Los cuatro elementos, y por qué cada uno:
- Retroceso exponencial: dar tiempo a que el otro se recupere.
- Jitter: sin él, mil clientes que fallaron a la vez reintentan a la vez, creando una onda sincronizada que vuelve a tumbar el servicio justo cuando se estaba levantando. Es un fallo real y frecuente, y el jitter cuesta una línea.
- Límite de intentos: reintentar indefinidamente convierte un fallo en un ataque a tu propio sistema.
- Solo errores transitorios: reintentar un
400o un403no lo va a arreglar nunca; solo gasta tiempo y recursos.
Interruptor de circuito
Cuando un servicio lleva fallando un rato, deja de llamarlo:
class Interruptor:
"""Cerrado = pasa. Abierto = falla rapido. Semiabierto = prueba con una."""
def __init__(self, umbral=5, espera=30):
self.umbral, self.espera = umbral, espera
self.fallos, self.abierto_desde = 0, None
def llamar(self, fn, alternativa=None):
import time
if self.abierto_desde:
if time.time() - self.abierto_desde < self.espera:
return alternativa() if alternativa else None # falla rapido
self.abierto_desde = None # semiabierto: se prueba
try:
r = fn()
self.fallos = 0
return r
except Exception:
self.fallos += 1
if self.fallos >= self.umbral:
self.abierto_desde = time.time()
if alternativa:
return alternativa()
raiseEl beneficio doble: el que llama deja de esperar (falla en microsegundos en vez de en segundos) y el que falla deja de recibir carga, lo que le permite recuperarse.
Degradación elegante
El principio que hace que un fallo parcial sea invisible:
La tienda sigue vendiendo aunque el recomendador falle.
@app.route("/producto/<sku>")
def producto(sku):
p = catalogo.obtener(sku) # dependencia DURA: si falla, error 500
# Dependencia BLANDA: si falla, la pagina se sirve sin recomendaciones
try:
recomendados = interruptor_reco.llamar(
lambda: recomendador.para(sku, timeout=0.3),
alternativa=lambda: catalogo.mas_vendidos_de(p.categoria), # plan B
)
except Exception:
logging.warning(json.dumps({
"severity": "WARNING", "message": "Recomendador no disponible",
"sku": sku, "degradado": True,
}))
recomendados = [] # plan C
return render("producto.html", producto=p, recomendados=recomendados)Los tres niveles son la clave del patrón: plan A el recomendador; plan B algo simple y local —los más vendidos de la categoría, que casi nadie distinguirá—; plan C nada, y la página se sirve igual. Un cliente puede comprar en los tres casos, que es lo único que importa.
Y el detalle que hace posible medirlo: el log lleva "degradado": true. Sin esa marca, el servicio funciona degradado durante semanas y nadie se entera, porque el SLI de disponibilidad sigue en verde. Una alerta sobre el porcentaje de peticiones degradadas es lo que evita que la degradación elegante se convierta en degradación silenciosa.
Limitación de carga
Cuando la demanda supera la capacidad, hay dos opciones: degradar para todos o rechazar a algunos y servir bien al resto. La segunda es casi siempre mejor.
| Mecanismo | Dónde | Qué hace |
|---|---|---|
Cloud Armor rate-based-ban |
Borde | Corta abuso antes de llegar (03-05) |
--max-instances de Cloud Run |
Servicio | Cortafuegos de coste y de conexiones (07-02) |
| Cola con longitud máxima | Aplicación | Rechaza con 503 en lugar de acumular |
| Vertido de carga por prioridad | Aplicación | Rechaza rastreadores antes que compradores |
El último es el más sofisticado y el más útil: cuando hay saturación, no todas las peticiones valen lo mismo. Un usuario en el proceso de pago vale mucho más que un rastreador indexando la página 47 del catálogo.
- Recuperación ante desastres: RTO, RPO y estrategias
Alta disponibilidad y recuperación ante desastres no son lo mismo, y confundirlos lleva a arquitecturas caras que no protegen de lo que de verdad pasa:
| Alta disponibilidad | Recuperación ante desastres | |
|---|---|---|
| Protege de | Fallos de infraestructura | Desastres y errores humanos |
| Actuación | Automática | Habitualmente manual |
| Escala temporal | Segundos o minutos | Horas o días |
| Ejemplo | Conmutación de zona en Cloud SQL | Restaurar tras un borrado masivo |
Un dato que reordena prioridades: la causa más frecuente de pérdida de datos no es un desastre natural ni un ataque. Es alguien ejecutando algo en el entorno equivocado. Y contra eso, la alta disponibilidad no sirve de nada: replica el error fielmente.
Las dos métricas
| Métrica | Pregunta | Determina |
|---|---|---|
| RTO (Recovery Time Objective) | ¿Cuánto tiempo puede estar caído? | La estrategia |
| RPO (Recovery Point Objective) | ¿Cuántos datos puedo perder? | La frecuencia de copia |
flowchart LR
C1["Ultima copia<br/>03:00"] -.->|"RPO = datos perdidos"| D["DESASTRE<br/>10:15"]
D -.->|"RTO = tiempo caido"| R["Servicio restaurado<br/>14:15"]
Con copias diarias a las 3:00 y un desastre a las 10:15, el RPO real son 7 horas y 15 minutos de datos perdidos. Para AlpinaShop, eso serían los pedidos de toda una mañana: inaceptable.
Las cuatro estrategias
| Estrategia | RTO | RPO | Coste | Cómo |
|---|---|---|---|---|
| Copia y restauración | Horas a días | Horas | € | Copias en otra región; se reconstruye |
| Luz piloto | Decenas de min a horas | Minutos | €€ | Núcleo mínimo encendido (BD replicada); el resto se levanta |
| Espera templada | Minutos | Segundos a min | €€€ | Todo desplegado a escala reducida |
| Activo-activo | Segundos | ~0 | €€€€ | Ambas regiones sirviendo |
flowchart TB
subgraph A["Copia y restauracion"]
A1["Copias en otra region"] -.->|"horas"| A2["Se reconstruye todo"]
end
subgraph B["Luz piloto"]
B1["Replica de BD encendida"] -.->|"minutos"| B2["Se levanta el resto"]
end
subgraph C["Espera templada"]
C1["Todo desplegado, escala minima"] -.->|"minutos"| C2["Se escala"]
end
subgraph D["Activo-activo"]
D1["Ambas regiones sirviendo"] -.->|"segundos"| D2["El balanceador redirige"]
end
Cómo se elige, y no es por la tecnología: se calcula el coste de una hora de caída y se compara con el coste mensual de cada estrategia. Para AlpinaShop, una hora de caída en un día normal son unos 100 € de pedidos; en campaña, quizá 800 €. Con esos números, activo-activo —varios cientos de euros al mes, todos los meses— no se justifica.
- El plan de recuperación de AlpinaShop
Escrito, con responsables, y guardado también fuera de Google Cloud (porque si el desastre es de acceso a Google Cloud, el plan tiene que ser accesible).
Escenarios y respuesta
| # | Escenario | Probabilidad | RTO objetivo | RPO objetivo | Estrategia |
|---|---|---|---|---|---|
| 1 | Fallo de una zona | Media | 2 min | 0 | Automático (HA de Cloud SQL, Cloud Run multizona) |
| 2 | Despliegue defectuoso | Alta | 5 min | 0 | Reversión de revisión (07-02) |
| 3 | Borrado accidental de datos | Media | 1 h | 5 min | Recuperación a un punto en el tiempo |
| 4 | Corrupción lógica detectada tarde | Baja | 4 h | Hasta 24 h | Restaurar copia + reprocesar desde Pub/Sub |
| 5 | Caída de la región europe-west1 |
Baja | 4 h | 5 min | Luz piloto en europe-southwest1 |
| 6 | Ransomware / cuenta comprometida | Baja | 8 h | 1 h | Copias inmutables + reconstruir con Terraform |
| 7 | Pérdida del enlace con Sabadell | Media | 1 h | 0 | Pub/Sub retiene; la tienda sigue (DA-004) |
El escenario 2 es el más probable con diferencia, y su RTO de 5 minutos ya está resuelto desde 07-02. Es un buen recordatorio de que el trabajo de fiabilidad más rentable ya estaba hecho antes de esta lección.
Configuración de copias de seguridad
resource "google_sql_database_instance" "pedidos" {
name = "alpinashop-pedidos"
database_version = "POSTGRES_15"
region = "europe-west1"
settings {
tier = "db-custom-2-7680"
availability_type = "REGIONAL" # HA: replica sincrona en otra zona
backup_configuration {
enabled = true
start_time = "02:00"
location = "eu" # MULTIRREGION: sobrevive a la region
point_in_time_recovery_enabled = true # <<< lo mas importante
transaction_log_retention_days = 7 # ventana de recuperacion puntual
backup_retention_settings {
retained_backups = 30
retention_unit = "COUNT"
}
}
maintenance_window {
day = 2 # martes
hour = 4
update_track = "stable"
}
}
deletion_protection = true # 06-07
}Las cuatro líneas que hacen el trabajo:
point_in_time_recovery_enabled = truees lo que convierte un RPO de 24 horas en un RPO de minutos. Sin ella, solo se puede restaurar al momento de la última copia; con ella, a cualquier instante dentro de la ventana de logs de transacciones.location = "eu"guarda las copias fuera de la región. Copias en la misma región que muere con la región no son copias: son una ilusión.transaction_log_retention_days = 7define la ventana de recuperación puntual. Siete días cubre el caso "alguien borró algo el lunes y nos dimos cuenta el jueves".deletion_protectionimpide el borrado accidental de la instancia entera.
Exportación de configuración
Los datos no son lo único que hay que poder reconstruir:
| Qué | Cómo | Dónde |
|---|---|---|
| Infraestructura | Terraform en alpinashop-infra |
GitHub + backend en alpinashop-terraform-estado |
| Secretos | Secret Manager con replicación automática | Gestionado por Google |
| Imágenes de contenedor | Artifact Registry | Con réplica |
| Políticas de IAM | Export periódico de Cloud Asset Inventory (07-07) | Bucket en otra región |
| Paneles y alertas | JSON versionado (06-04) | Git |
| Reglas de Cloud Armor | Export a YAML (03-05) | Git |
| El propio plan de DR | Documento | Fuera de GCP: Drive, papel, gestor de contraseñas |
- Backups que no se han probado no existen
Aquí se paga la deuda que 07-04 marcó como crítica.
Una copia de seguridad no probada no es una copia de seguridad: es una esperanza con nombre técnico.
Los motivos por los que una restauración falla el día que hace falta, todos reales y todos frecuentes:
| Motivo | Cómo se descubre |
|---|---|
| La copia estaba vacía o incompleta | Solo restaurando |
| Faltaban permisos para restaurar | Solo intentándolo |
| El proceso tarda 6 h y el RTO era 1 h | Solo cronometrándolo |
| El esquema restaurado no es compatible con la aplicación actual | Solo arrancando la aplicación contra él |
| Nadie sabe hacerlo y la persona que sabía se fue | Solo el día del desastre |
| La copia estaba en la misma región que murió | El día del desastre |
El ensayo de restauración de alpinashop-pedidos
Procedimiento completo, ejecutado trimestralmente por Marta, y cronometrado.
Paso 1 — Preparar y anotar la hora de inicio.
INICIO=$(date +%s)
echo "Ensayo de restauracion iniciado: $(date)"
gcloud sql backups list --instance=alpinashop-pedidos --project=alpinashop-prod \
--format="table(id, windowStartTime, status, type, location)" --limit=5Paso 2 — Restaurar a una instancia NUEVA.
Nunca sobre la de producción. La restauración crea una instancia aparte, lo que hace el ensayo totalmente seguro:
# Recuperacion a un punto en el tiempo: hace 2 horas
MOMENTO=$(date -u -d '2 hours ago' +%Y-%m-%dT%H:%M:%S.000Z)
gcloud sql instances clone alpinashop-pedidos alpinashop-pedidos-ensayo \
--point-in-time="$MOMENTO" \
--project=alpinashop-prodPaso 3 — Verificar la integridad, que es lo que casi nadie hace.
Restaurar sin verificar solo demuestra que el comando funciona:
-- 1. Las tablas existen y tienen filas
SELECT schemaname, relname, n_live_tup
FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 10;
-- 2. El ultimo pedido es coherente con el momento restaurado
SELECT MAX(fecha_pedido) AS ultimo_pedido, COUNT(*) AS total FROM pedidos;
-- 3. Integridad referencial: no hay lineas huerfanas
SELECT COUNT(*) AS lineas_huerfanas
FROM lineas_pedido lp
LEFT JOIN pedidos p ON p.id = lp.pedido_id
WHERE p.id IS NULL;
-- 4. Comparar totales con produccion (deben cuadrar hasta el momento restaurado)
SELECT DATE(fecha_pedido) AS dia, COUNT(*) AS pedidos, SUM(importe_total) AS importe
FROM pedidos
WHERE fecha_pedido >= NOW() - INTERVAL '7 days'
GROUP BY dia ORDER BY dia;Paso 4 — Arrancar la aplicación contra la copia restaurada.
Este es el paso que descubre los problemas de verdad:
gcloud run deploy alpinashop-web-ensayo \
--image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
--region=europe-west1 --no-traffic --tag=ensayo \
--add-cloudsql-instances=alpinashop-prod:europe-west1:alpinashop-pedidos-ensayo \
--set-env-vars=ENTORNO=ensayo
URL=$(gcloud run services describe alpinashop-web-ensayo --region=europe-west1 \
--format='value(status.traffic[?tag==`ensayo`].url)')
curl -sf -H "Authorization: Bearer $(gcloud auth print-identity-token)" "$URL/salud"
curl -sf -H "Authorization: Bearer $(gcloud auth print-identity-token)" "$URL/producto/1"Paso 5 — Cronometrar, limpiar y documentar.
FIN=$(date +%s)
echo "Tiempo total de restauracion: $(( (FIN - INICIO) / 60 )) minutos"
gcloud sql instances delete alpinashop-pedidos-ensayo --quiet
gcloud run services delete alpinashop-web-ensayo --region=europe-west1 --quietEl resultado del primer ensayo real
Y aquí está el valor del ejercicio, porque el primer ensayo nunca sale bien:
| Hallazgo | Impacto |
|---|---|
| La restauración tardó 47 minutos, no los "unos minutos" que todos suponían | El RTO de 1 hora para el escenario 3 es ajustado, no cómodo |
| El usuario de la aplicación no existía en la instancia clonada | La aplicación no arrancó hasta crearlo a mano. Habría costado 20 minutos en un incidente real |
Faltaba roles/cloudsql.admin en el grupo gcp-infra@ para clonar |
Marta tuvo que pedírselo a sí misma por otra vía |
| Las secuencias de PostgreSQL quedaron desincronizadas | Los primeros inserts habrían fallado por clave duplicada |
| Nadie había documentado el procedimiento | Marta lo reconstruyó sobre la marcha |
Cinco problemas que habrían convertido una restauración de 1 hora en una de 3 horas, con la tienda parada y todo el mundo mirando. Y ninguno era detectable sin ensayar.
Las cinco correcciones se escribieron en un runbook, se automatizó la creación del usuario y el reajuste de secuencias en un script de post-restauración, y se ajustó el RTO del escenario 3 de 1 hora a 90 minutos, que es un número honesto.
El ensayo no sirve para confirmar que las copias funcionan. Sirve para encontrar los cinco problemas que impiden que funcionen.
- Práctica operativa: guardias, runbooks y post-mortem
Guardias sostenibles para un equipo de tres personas
La fiabilidad la sostienen personas, y un equipo quemado es el mayor riesgo de fiabilidad que existe. Con tres personas técnicas, una guardia 24×7 tradicional es inviable: serían dos semanas de cada tres de disponibilidad permanente.
El modelo realista de AlpinaShop:
| Franja | Cobertura | Qué se atiende |
|---|---|---|
| L-V 8:00-20:00 | Rotación semanal de una persona | Todo |
| L-V 20:00-8:00 | Solo alertas de página (tasa 14,4×) | Caída total o pérdida de dinero |
| Fines de semana | Ídem | Ídem |
| Campaña de otoño | Dos personas de guardia, con compensación acordada | Todo |
Y las reglas que lo hacen sostenible, que importan tanto como el calendario:
- Solo despierta lo que hay que arreglar ya. Si una alerta llega de madrugada y la acción es "mirarlo mañana", esa alerta no debe sonar de noche. Es la regla que más reduce el desgaste.
- Compensación real. Horas o dinero, acordado por escrito. La guardia no remunerada dura hasta que alguien se harta.
- Máximo dos noches interrumpidas seguidas. Si pasa, se releva.
- Toda alerta nocturna se revisa el día siguiente: ¿era necesaria? Si no, se ajusta o se elimina.
- Menos alertas es mejor. La fatiga de alertas (06-04) es un problema de fiabilidad: una persona que ha aprendido a ignorar el móvil ignorará también la que importaba.
Runbooks enlazados desde las alertas
Un runbook es el documento que dice qué hacer ante una alerta concreta. Vive en alpinashop-infra/runbooks/ y se enlaza desde el campo documentation de la política de alerta.
# Runbook: errores 5xx en el catálogo
**Alerta:** SLO catálogo - consumo rápido del presupuesto (14.4x)
**Gravedad:** Página (despierta)
**Última revisión:** 2026-08-05 · **Probado en simulacro:** sí
## 1. Confirmar (2 min)
- Panel de campaña: [enlace directo]
- ¿Afecta a todas las rutas o a una? Filtrar por `url_map_name` y ruta.
- Comprobación externa: `curl -sS -o /dev/null -w '%{http_code}' https://alpinashop.example/salud`
## 2. Causa más probable: un despliegue reciente (3 min)gcloud run revisions list --service=alpinashop-web --region=europe-west1 --limit=5
Si la revisión activa se desplegó hace menos de 2 horas, **REVERTIR PRIMERO Y DIAGNOSTICAR DESPUÉS**:
gcloud run services update-traffic alpinashop-web --region=europe-west1
--to-revisions=REVISION_ANTERIOR=100
## 3. Si no fue un despliegue | Síntoma | Causa probable | Acción | | --- | --- | --- | | `Memory limit exceeded` en logs | Memoria insuficiente | Subir `--memory`, revisar concurrencia | | Errores de conexión a BD | Pool agotado o Cloud SQL degradado | Panel de Cloud SQL; conmutación | | Latencia alta sin errores | Dependencia lenta | Cloud Trace, buscar el tramo lento | | 502 del balanceador | Cloud Run no responde | `gcloud run services describe` | | Solo desde una geografía | Cloud Armor | Revisar reglas recientes (03-05) | ## 4. Escalar Si en 30 min no hay diagnóstico: avisar a Dani (backend) y abrir caso con soporte de Google. ## 5. Cerrar - Comprobar que el SLI se recupera. - Anotar inicio, fin y presupuesto consumido. - **Post-mortem obligatorio si el corte superó 15 minutos.**
Las tres propiedades de un runbook útil: empieza por confirmar (no por diagnosticar), pone primero la causa más probable —un despliegue reciente, en el 70 % de los casos—, y dice explícitamente cuándo escalar, para que la persona de guardia no se quede sola dando vueltas a las 4 de la mañana.
Y una cuarta que se ve en la cabecera: "Probado en simulacro: sí". Un runbook que nadie ha ejecutado nunca es una hipótesis.
Post-mortem sin culpables
Tras un incidente significativo, un documento con estructura fija:
| Sección | Contenido |
|---|---|
| Resumen | Qué pasó, en dos frases |
| Impacto | Duración, usuarios afectados, presupuesto de error consumido, pedidos perdidos |
| Cronología | Con horas: detección, diagnóstico, mitigación, resolución |
| Causa raíz | Los "cinco porqués", hasta llegar a una causa sistémica |
| Qué fue bien | Sí, esta sección importa |
| Acciones | Con responsable y fecha. Concretas, no "tener más cuidado" |
Sin culpables (blameless) no significa que no haya responsabilidades. Significa una cosa muy concreta:
Si la respuesta a "por qué pasó" es el nombre de una persona, el análisis se ha detenido demasiado pronto.
Ejemplo aplicado:
❌ "Dani desplegó sin probar." ✅ "Un cambio llegó a producción sin cobertura de pruebas para el caso de un carrito vacío. ¿Por qué? El pipeline no exige umbral de cobertura. ¿Por qué? Se configuró rápido en 06-01 y quedó pendiente. ¿Por qué no se detectó en el canario? El canario duró 10 min con tráfico bajo (07-02). ACCIONES: 1. Umbral de cobertura del 70% en el pipeline (Dani, 2026-08-20) 2. Canario mínimo de 30 min o 500 peticiones, lo que sea mayor (Marta, 2026-08-15) 3. Prueba de regresión del carrito vacío (Dani, 2026-08-12)"
La razón práctica —no moral— de la ausencia de culpa: en una cultura donde se busca al culpable, la gente oculta los errores, y los errores ocultos no se corrigen. En una cultura sin culpa, la gente cuenta lo que pasó y el sistema mejora. Es una decisión de eficacia.
El registro de incidentes
Todos los post-mortem en un sitio, con etiquetas. Al cabo de un año permite ver patrones que ningún incidente aislado revela: "el 60 % de nuestros incidentes son despliegues" es una frase que cambia las prioridades de un equipo entero.
- Pruebas de caos a escala razonable
Ingeniería del caos es provocar fallos a propósito para verificar que el sistema los tolera. Netflix popularizó la idea matando instancias en producción; una pyme no necesita eso, pero sí necesita algo.
Todo sistema tiene pruebas de recuperación ante desastres. Unos las hacen a propósito; otros esperan a que ocurran.
Los experimentos de AlpinaShop, ordenados de menos a más agresivo:
| # | Experimento | Dónde | Hipótesis | Frecuencia |
|---|---|---|---|---|
| 1 | Parar el recomendador | dev |
La página se sirve con los más vendidos | Mensual |
| 2 | Añadir 3 s de latencia a Cloud SQL | dev |
El tiempo de espera corta y sale un 503 limpio, no una cascada | Mensual |
| 3 | Conmutación manual de Cloud SQL | prod, ventana planificada |
Corte < 2 min, la aplicación reconecta sola | Trimestral |
| 4 | Restauración de copia | prod → instancia nueva |
RTO < 90 min | Trimestral |
| 5 | Cortar el túnel VPN a Sabadell | prod |
La tienda sigue; Pub/Sub retiene | Semestral |
| 6 | Simulacro de sala: "cae europe-west1" |
Papel | El plan es ejecutable | Anual |
| 7 | Revertir un despliegue a ciegas | prod |
Cualquiera del equipo lo hace en < 5 min | Trimestral |
Las reglas que hacen esto responsable en lugar de temerario:
- Hipótesis escrita antes. Si no sabes qué esperas, no es un experimento: es romper cosas.
- Empezar en desarrollo.
- Radio de explosión pequeño, y ampliarlo solo cuando el anterior salió bien.
- Un botón de parada y la capacidad de abortar en cualquier momento.
- Nunca en campaña. Ni el viernes por la tarde.
- Avisar al equipo. El caos no anunciado con guardias reales es una broma pesada.
El experimento 3 merece un comentario, porque asusta y no debería: la conmutación de Cloud SQL va a ocurrir algún día, elegida por Google y en el peor momento. Provocarla un martes a las 10 de la mañana, con todo el mundo delante y con la posibilidad de arreglarlo, es infinitamente mejor que descubrir un viernes de campaña que la aplicación no reconecta porque le falta pool_pre_ping (07-02).
Y el experimento 7 es el más subestimado: que solo Marta sepa revertir un despliegue es un punto único de fallo humano. Que Dani y Lucía lo hayan hecho al menos una vez cada uno vale más que cualquier redundancia de infraestructura.
Errores Comunes y Consejos
- Poner como objetivo "que no se caiga nunca". No se puede cumplir, medir ni usar para decidir. Elige un número y págalo.
- Confundir SLO y SLA. El SLO siempre más exigente que el SLA, con margen holgado.
- Medir el SLI desde el servidor. Se mide desde donde está el usuario: el balanceador o una comprobación externa.
- Definir el SLI de latencia como "el p95 < X". Es "la proporción de peticiones < X". Un percentil no se combina con un presupuesto de error.
- Contar los rechazos legítimos de la pasarela como errores. El SLI pasaría a depender de la solvencia de los clientes.
- SLO sobre CPU o memoria. No son experiencia de usuario. Se monitorizan, no se convierten en objetivo.
- Ventanas de mes natural. Crean el incentivo de esperar al día 1 para desplegar lo arriesgado. Ventana móvil de 28 días.
- Un SLO sin política asociada. Si al incumplirlo no cambia nada, es un adorno.
- Congelar por agotar el presupuesto cuando la culpa fue de la plataforma. La política pierde credibilidad y el equipo deja de respetarla.
- Perseguir un presupuesto consumido al 5 %. Señal de exceso de conservadurismo o de un SLO demasiado laxo.
- Alertas de umbral fijo sobre errores. O son ruidosas o no detectan degradaciones lentas. Usa tasa de consumo con dos ventanas.
- Dejar vacío el campo
documentationde la alerta. A las 3 de la madrugada, ese texto vale más que la propia alerta. - Llamadas de red sin tiempo de espera. Es el mecanismo por el que un fallo pequeño se convierte en caída total.
- Reintentos sin jitter. Mil clientes reintentando a la vez tumban el servicio justo cuando se recuperaba.
- Reintentar errores permanentes. Un
403no mejora por insistir. - Tratar una dependencia blanda como dura. El recomendador no debe poder tumbar la ficha de producto.
- Degradar sin marcarlo en los logs. El servicio funciona a medias durante semanas y el SLI sigue verde.
- Copias en la misma región. No son copias: son una ilusión.
- Creer que HA protege de un borrado accidental. Lo replica al instante en la instancia en espera.
- No probar la restauración. Es la deuda más peligrosa porque es invisible hasta el día que importa.
- Guardias sin compensación. Duran hasta que alguien se harta, y entonces te quedas sin guardias y sin persona.
- Buscar culpables en un post-mortem. Los errores se ocultan, y los errores ocultos no se corrigen.
- Consejo: empieza por un SLO de disponibilidad y nada más. Cuatro SLO desde el primer día es demasiado. Uno bueno vale más que cuatro copiados.
- Consejo: la reversión rápida vale más que la redundancia. La causa más frecuente de caída es un cambio, no un fallo de hardware.
- Consejo: que todo el equipo sepa revertir un despliegue. Es la redundancia más barata que existe.
- Consejo: cronometra el ensayo de restauración. El número real siempre sorprende, y es el que da un RTO honesto.
Ejercicios
Ejercicio 1 — Definir el SLO de un servicio nuevo
Dani va a lanzar alpinashop-buscador (el servicio del ejercicio de 07-02). Define su SLI y su SLO completos:
- Qué eventos son válidos y cuáles buenos, resolviendo explícitamente al menos seis casos límite.
- El SLO y la ventana, justificados.
- El presupuesto de error resultante en minutos y en peticiones.
- Las dos alertas de tasa de consumo con sus umbrales.
- La política de qué se hace en cada estado del presupuesto.
Considera que el buscador es una dependencia blanda del catálogo: si falla, se puede mostrar un enlace a la navegación por categorías.
Ejercicio 2 — Diseñar un plan de recuperación con números
AlpinaShop lanza AlpinaPro, un servicio de suscripción para clubes de montaña: cuota mensual, contenido exclusivo y reservas de material. Datos:
- 400 suscriptores, 25 €/mes cada uno: 10.000 €/mes de ingreso recurrente.
- Base de datos propia
alpinapro-suscripciones(PostgreSQL). - Integración con la pasarela para cobros recurrentes el día 1 de cada mes.
- Los clubes reservan material con 48 h de antelación; una reserva perdida es una queja seria.
- Dirección dice que "no se puede caer nunca", pero el presupuesto adicional es de 150 €/mes.
Diseña: los SLI y SLO del servicio, el RTO y RPO justificados con números de negocio, la estrategia de recuperación elegida dentro del presupuesto, y la configuración concreta de copias. Explica qué le respondes a dirección sobre el "no se puede caer nunca".
Ejercicio 3 — Analizar un incidente y escribir el post-mortem
Los hechos. Martes, 18:42. Salta la alerta de consumo rápido. Cronología reconstruida:
- 18:30 — Cloud Build despliega la revisión
alpinashop-web-00051con canario al 10 %. Las pruebas de humo pasan. - 18:38 — Marta aprueba el paso al 100 % (llevaba prisa, se iba a las 18:45).
- 18:42 — Alerta: tasa de consumo 22×. Errores 500 en el 35 % de las peticiones.
- 18:44 — Marta ve la alerta ya en el coche. No puede actuar.
- 18:51 — Dani, avisado por Marta, se conecta y revierte a la revisión
00050. - 18:53 — Los errores desaparecen.
- Impacto: 11 minutos, ~230 peticiones fallidas, 4 pedidos perdidos, 27 % del presupuesto mensual consumido.
- Causa técnica: la revisión nueva leía una variable de entorno
TARIFA_ENVIOque solo estaba definida enalpinashop-dev.
Escribe el post-mortem completo siguiendo la estructura del apartado 12, con los cinco porqués llevados hasta una causa sistémica, y al menos cinco acciones concretas con responsable. Señala también qué hizo bien el equipo.
Soluciones
Solución 1 — Definir el SLO de alpinashop-buscador
1. Definición del SLI.
SLI de disponibilidad = peticiones de búsqueda con respuesta útil / peticiones de búsqueda válidas
Casos límite, resueltos y anotados:
| Caso | ¿Válido? | ¿Bueno? | Razón |
|---|---|---|---|
200 con 12 resultados |
Sí | Sí | — |
200 con 0 resultados |
Sí | Sí | El sistema funcionó; simplemente no hay "piolet rosa fucsia" |
400 consulta malformada |
Sí | Sí | El sistema rechazó correctamente una entrada inválida |
500 excepción interna |
Sí | No | Fallo nuestro |
503 Elasticsearch caído |
Sí | No | Es nuestra dependencia; el usuario no distingue |
429 límite de tasa a un rastreador |
No | — | No es un usuario a quien servir |
Petición con User-Agent de bot |
No | — | No es experiencia de usuario |
| Tiempo de espera agotado (> 3 s) | Sí | No | Peor que un error: el usuario espera y no recibe nada |
| Petición de la sonda de salud | No | — | No es tráfico real |
El caso "0 resultados" es el que más discusión genera y es importante acertarlo. Contarlo como malo haría que el SLI midiera la calidad del catálogo en lugar de la fiabilidad del servicio, y bajaría cada vez que alguien busca algo que no vendemos. La calidad de la búsqueda se mide aparte, con métricas de producto —tasa de búsquedas sin resultado, tasa de clic— que no son un SLO.
SLI de latencia = búsquedas servidas en < 500 ms / búsquedas válidas
500 ms y no 800 ms como el catálogo, porque un buscador es interactivo: el usuario escribe y espera. La percepción de lentitud aparece antes.
2. SLO y ventana.
| SLI | SLO | Ventana | Justificación |
|---|---|---|---|
| Disponibilidad | 99,5 % | 28 días | Menos exigente que el catálogo (99,9 %) |
| Latencia < 500 ms | 99 % | 28 días | Coherente con el catálogo |
La justificación del 99,5 % es la parte interesante del ejercicio. El buscador es una dependencia blanda: si falla, el catálogo muestra un enlace a la navegación por categorías y el cliente puede seguir comprando. Un servicio blando no necesita el mismo objetivo que uno crítico, y ponerle 99,9 % significaría gastar dinero e ingeniería en una fiabilidad que no cambia el resultado de negocio.
Este es el razonamiento que hay que llevarse: el SLO se deriva del impacto de negocio del fallo, no del cariño que se le tenga al servicio.
3. Presupuesto de error.
Con 99,5 % sobre 28 días: 0,5 % = 3 h 21 min 36 s.
En peticiones: si el buscador recibe unas 150.000 búsquedas válidas en 28 días, el presupuesto son 750 búsquedas fallidas.
Es cómodo, y a propósito: permite desplegar mejoras del buscador con frecuencia sin que un fallo puntual bloquee al equipo.
4. Las dos alertas.
| Alerta | Tasa | Ventanas | Consumo en la ventana | Canal |
|---|---|---|---|---|
| Rápida | 14,4× | 1 h y 5 min | 2 % (≈ 4 min) | 🎫 Ticket, no página |
| Lenta | 6× | 6 h y 30 min | 5 % (≈ 10 min) | 🎫 Ticket |
Ninguna de las dos despierta a nadie, y esa es la consecuencia lógica de ser una dependencia blanda con SLO del 99,5 %. Levantarse a las 4 de la mañana porque el buscador falla, cuando la tienda sigue vendiendo, es exactamente el tipo de alerta que produce fatiga y hace que se ignore la que sí importa.
La excepción que hay que prever: si el buscador cae y además el catálogo empieza a fallar —porque el tiempo de espera no cortaba y se agotaron los hilos—, la alerta que sonará es la del catálogo, que sí es de página. El diseño de alertas debe reflejar el diseño de dependencias.
5. Política de presupuesto.
| Consumido | Estado | Acción |
|---|---|---|
| < 60 % | 🟢 | Despliegue normal |
| 60-90 % | 🟡 | Se sigue desplegando; se revisa la causa del consumo en la reunión semanal |
| > 90 % | 🟠 | Solo correcciones. Revisar si la dependencia de Elasticsearch necesita más fiabilidad |
| > 100 % | 🔴 | Evaluar si el interruptor de circuito del catálogo funciona bien, que es lo realmente importante: que el fallo del buscador no arrastre a la tienda |
La fila roja es la clave del ejercicio. Cuando un servicio blando agota su presupuesto, la pregunta prioritaria no es "cómo lo hago más fiable" sino "¿está bien aislado?". Un buscador que falla el 2 % del tiempo sin afectar a nadie es un problema menor; un buscador que falla el 0,5 % del tiempo y cada fallo degrada la ficha de producto es un problema grave.
Solución 2 — Diseñar un plan de recuperación para AlpinaPro
Punto de partida: traducir el negocio a números.
| Concepto | Cálculo | Valor |
|---|---|---|
| Ingreso recurrente mensual | 400 × 25 € | 10.000 € |
| Ingreso por hora (prorrateado) | 10.000 / 730 | ~13,70 €/h |
| Coste real de una hora de caída | Ver abajo | Muy superior a 13,70 € |
Y aquí está el matiz que distingue un análisis bueno de uno superficial: en un servicio de suscripción, el ingreso no se pierde por estar caído. Los 400 suscriptores pagan igual. Lo que se pierde es otra cosa, y es peor:
| Impacto real | Estimación |
|---|---|
| Reservas de material perdidas | Una queja seria por reserva; riesgo de baja |
| Bajas por mala experiencia | Una baja son 25 €/mes × meses restantes de vida del cliente. Con 24 meses de vida media, una baja son 600 € |
| Cobros recurrentes fallidos el día 1 | Crítico: si el sistema cae el día 1, no se cobra a nadie |
| Reputación en el nicho | Los clubes de montaña se conocen entre sí |
Conclusión: una caída de 4 horas que provoque 5 bajas cuesta 3.000 €, no 55 €. El análisis por ingreso prorrateado subestima el impacto en más de un orden de magnitud, y es un error muy común en servicios de suscripción.
SLI y SLO propuestos.
| SLI | SLO | Justificación |
|---|---|---|
| Disponibilidad del portal | 99,5 % | 3 h 22 min al mes. Los clubes no consultan a diario |
| Éxito de reservas | 99,9 % | Una reserva perdida es una queja. Más exigente |
| Éxito del cobro recurrente | 99,99 % | Crítico y concentrado: falla el día 1 o no falla |
El tercero es el más interesante y el que se suele olvidar. El cobro recurrente no es un servicio continuo: es un proceso que se ejecuta un día al mes. Un SLO mensual sobre él no tiene sentido; lo que hace falta es un objetivo distinto: "el 99,99 % de los cobros programados se ejecutan correctamente dentro de las 24 horas siguientes", con reintentos. Y un tratamiento arquitectónico específico: cola con reintentos y DLQ, no una ejecución única que o funciona o se pierde.
RTO y RPO justificados.
| Escenario | RTO | RPO | Justificación con números |
|---|---|---|---|
| Fallo de zona | 5 min | 0 | Automático, no cuesta decidirlo |
| Despliegue defectuoso | 5 min | 0 | Reversión de revisión |
| Borrado de datos | 2 h | 5 min | Perder reservas es inaceptable. 5 min ≈ 0 reservas perdidas |
| Caída de región | 6 h | 15 min | Baja probabilidad; 6 h de caída ≈ 3-5 bajas ≈ 2.400 € |
| Fallo el día 1 (cobros) | 2 h | 0 | Se reintenta; el proceso debe ser idempotente |
Estrategia dentro de 150 €/mes.
Presupuesto muy ajustado. Reparto:
| Medida | Coste/mes | Qué cubre |
|---|---|---|
| Cloud SQL HA regional | ~60 € | Fallo de zona, automático, RPO 0 |
| Recuperación a un punto en el tiempo + copias multirregión | ~15 € | Borrado accidental con RPO de 5 min; supervivencia a la región |
Réplica de lectura en europe-southwest1 |
~55 € | Luz piloto: promocionable en minutos si cae la región |
| Uptime checks + alertas | ~5 € | Detección |
| Cloud Run (escala a cero) | ~5 € | Cómputo |
| Total | ~140 € |
Estrategia elegida: luz piloto. La base de datos replicada en la segunda región es lo único que se mantiene encendido; el cómputo en Cloud Run se levanta con tres comandos (apartado 7) porque la imagen ya existe y Cloud Run escala desde cero. RTO estimado: 30-60 minutos, mejor que el objetivo de 6 h.
Lo que no se hace y por qué: activo-activo costaría 400-600 €/mes para cubrir un evento de probabilidad muy baja, cuando el presupuesto entero son 150 €.
Configuración de copias.
resource "google_sql_database_instance" "alpinapro" {
name = "alpinapro-suscripciones"
database_version = "POSTGRES_15"
region = "europe-west1"
settings {
tier = "db-custom-1-3840"
availability_type = "REGIONAL"
backup_configuration {
enabled = true
start_time = "03:00"
location = "eu"
point_in_time_recovery_enabled = true
transaction_log_retention_days = 7
backup_retention_settings {
retained_backups = 30
retention_unit = "COUNT"
}
}
# Ventana de mantenimiento: NUNCA cerca del dia 1
maintenance_window {
day = 3 # miercoles
hour = 4
update_track = "stable"
}
}
deletion_protection = true
}
resource "google_sql_database_instance" "alpinapro_replica_dr" {
name = "alpinapro-suscripciones-dr-esw1"
master_instance_name = google_sql_database_instance.alpinapro.name
region = "europe-southwest1" # OTRA region
database_version = "POSTGRES_15"
replica_configuration { failover_target = false }
settings {
tier = "db-custom-1-3840"
availability_type = "ZONAL" # la replica de DR no necesita HA propia
}
}Dos decisiones a señalar: la ventana de mantenimiento el miércoles, deliberadamente lejos del día 1 —un mantenimiento de Google durante el ciclo de cobros sería el peor momento posible—, y la réplica de DR en modo ZONAL, porque pagar HA para una réplica que solo se usa en un desastre es duplicar el coste de un seguro.
Qué se le responde a dirección.
"No podemos garantizar que no se caiga nunca, y quien lo prometa no está diciendo la verdad. Lo que sí podemos hacer es decidir cuánto estamos dispuestos a que se caiga y pagarlo.
Con los 150 € al mes que tenemos, el compromiso es este: si falla una máquina o un centro de datos concreto, se recupera solo en cinco minutos y sin perder ningún dato. Si alguien borra algo por error, lo recuperamos en dos horas perdiendo como mucho cinco minutos de reservas. Y si cayera una región entera de Google —algo muy poco frecuente pero posible—, estaríamos funcionando en menos de una hora desde Madrid.
Para el proceso de cobro del día 1 tenemos un tratamiento aparte, porque es el momento crítico del mes: si algo falla, se reintenta automáticamente durante 24 horas y ningún cobro se pierde.
Lo que no cubrimos con este presupuesto es la caída simultánea de dos regiones o un problema global de Google. Cubrirlo costaría entre tres y cuatro veces más, todos los meses, para un escenario que quizá no ocurra nunca.
Y hay algo que quiero pedir además del presupuesto: probar todo esto cada trimestre. Un plan de recuperación que no se ensaya no funciona el día que hace falta, y lo sabemos porque nos pasó con la tienda: la primera vez que probamos restaurar la base de datos encontramos cinco problemas que habrían triplicado el tiempo de recuperación."
Solución 3 — Post-mortem del incidente del 18:42
Post-mortem: errores 500 en el catálogo tras el despliegue 00051
Fecha del incidente: martes 2026-08-04, 18:42-18:53 Autor: Marta Ruiz · Revisado por: Dani, Lucía Estado: acciones en curso · Clasificación: sin culpables
Resumen. Un despliegue promocionado al 100 % del tráfico contenía código que dependía de una variable de entorno inexistente en producción, provocando errores 500 en el 35 % de las peticiones durante 11 minutos.
Impacto.
| Métrica | Valor |
|---|---|
| Duración | 11 min (18:42 – 18:53) |
| Peticiones fallidas | ~230 |
| Pedidos perdidos | 4 (~260 € de ingreso) |
| Presupuesto de error consumido | 27 % del mensual en 11 minutos |
| Estado del SLO tras el incidente | 🟡 Amarillo (58 % consumido) |
Cronología.
| Hora | Evento |
|---|---|
| 18:30 | Cloud Build despliega 00051, canario al 10 %. Pruebas de humo ✅ |
| 18:38 | Marta aprueba el 100 %. Sale de la oficina a las 18:45 |
| 18:42 | Alerta de consumo rápido (22×). 35 % de errores |
| 18:44 | Marta ve la alerta en el coche. No puede actuar |
| 18:47 | Marta llama a Dani |
| 18:51 | Dani revierte a 00050 |
| 18:53 | Errores a cero. Detección: 4 min. Mitigación: 11 min |
Causa raíz — los cinco porqués.
- ¿Por qué fallaron las peticiones? El código leía
os.environ["TARIFA_ENVIO"], que no existía en producción, lanzandoKeyError. - ¿Por qué no existía? Se añadió a
alpinashop-devdurante el desarrollo y no se propagó a producción. - ¿Por qué no se propagó? Las variables de entorno de producción se gestionan en Terraform, pero el desarrollo las configura con
gclouda mano. No hay un mecanismo que garantice la paridad. - ¿Por qué no lo detectó el canario? Las pruebas de humo solo comprueban
/saludy/producto/1, y ninguna de esas rutas usaTARIFA_ENVIO(solo la del carrito). Además el canario duró 8 minutos con tráfico bajo. - ¿Por qué se promocionó al 100 % con esa cobertura? Porque el proceso permite aprobar manualmente sin ninguna comprobación automática de la revisión candidata, y la aprobación se dio con prisa al final de la jornada.
Causa raíz sistémica: no existe verificación automática de que una revisión candidata tenga toda su configuración presente en el entorno de destino, ni una comprobación objetiva que deba pasar antes de la promoción al 100 %. La aprobación manual es el único control y depende del estado y la disponibilidad de una persona.
Qué fue bien — y esta sección importa tanto como la anterior:
- La alerta funcionó: 4 minutos desde el primer error hasta la notificación. Exactamente lo que 06-04 y las alertas de tasa de consumo prometían.
- La reversión fue trivial: un comando, 2 minutos, sin reconstruir nada. El trabajo de 07-02 se pagó solo en este incidente.
- La escalada funcionó: Marta, sin poder actuar, avisó a Dani en 3 minutos en lugar de intentar arreglarlo conduciendo.
- El presupuesto de error hizo su trabajo: convirtió "11 minutos, no fue para tanto" en "el 27 % del presupuesto mensual", que es un dato que obliga a actuar.
- Nadie buscó culpables durante el incidente. Se arregló primero y se analizó después.
Acciones.
| # | Acción | Responsable | Fecha | Previene |
|---|---|---|---|---|
| 1 | Todas las variables de entorno de todos los entornos gestionadas en Terraform. Prohibido gcloud run services update --set-env-vars fuera del pipeline |
Marta | 2026-08-15 | La divergencia entre entornos (porqués 2 y 3) |
| 2 | Paso de compilación que compara las variables de entorno declaradas en el código con las definidas en el entorno destino y falla si falta alguna | Dani | 2026-08-20 | La causa directa |
| 3 | Pruebas de humo ampliadas: /salud, /producto/1, /carrito, /api/categorias, cubriendo toda ruta que lea configuración |
Dani | 2026-08-12 | El porqué 4 |
| 4 | Canario mínimo de 30 minutos o 500 peticiones, lo que sea mayor, con verificación automática de tasa de error antes de habilitar la aprobación | Marta | 2026-08-18 | El porqué 5 |
| 5 | Ventana de despliegue: no se promociona a producción después de las 17:00 ni los viernes por la tarde | Equipo | Inmediato | Aprobar con prisa al final del día |
| 6 | Que Lucía sepa revertir un despliegue. Práctica en la próxima prueba de caos | Marta | 2026-09-01 | El punto único de fallo humano |
| 7 | Añadir este caso al registro de incidentes con la etiqueta configuracion-divergente |
Marta | Inmediato | Ver patrones |
Nota sobre la acción 5. Podría parecer una regla burocrática que ralentiza al equipo, y merece justificarse: no se prohíbe desplegar por la tarde —el canario puede correr toda la noche al 10 %— sino promocionar al 100 % cuando no queda nadie disponible para reaccionar. La restricción no es sobre el riesgo del cambio, sino sobre la capacidad de respuesta. Esa distinción es la que hace que el equipo acepte la regla en lugar de saltársela.
Lo que este incidente NO fue. No fue un error de Dani por escribir código que lee una variable, ni de Marta por aprobar con prisa. Fue un sistema que permitía promocionar al 100 % del tráfico un cambio cuya configuración nadie había verificado, en un momento en el que nadie podía reaccionar. Cualquiera de los tres habría hecho lo mismo, y por eso las siete acciones son sobre el sistema y ninguna sobre las personas.
Conclusión
AlpinaShop ha dejado de aspirar a "que no se caiga nunca" y ha empezado a gestionar la fiabilidad con números.
Sabes que cada nueve multiplica el coste y has hecho la cuenta que casi nadie hace: pasar de 99,9 % a 99,99 % le ahorraría a AlpinaShop unos 70 € al mes en pedidos y le costaría varios cientos. Decir "99,99 % sería una mala inversión" con la tabla delante es una de las aportaciones más valiosas de un ingeniero, y requiere haber calculado también los dos matices: que no todo el tiempo caído vale igual y que hay costes que no son pedidos perdidos.
Distingues SLI, SLO y SLA con precisión, con las tres reglas que evitan casi todos los errores —el SLO siempre más exigente que el SLA, el SLI medido desde donde está el usuario, y un SLO sin consecuencia no es un objetivo— y con la definición operativa que resuelve las dudas: eventos buenos / eventos válidos, donde el trabajo de verdad está en decidir qué es cada cosa.
Has elegido cuatro SLI recorriendo el viaje del cliente —disponibilidad, latencia, éxito del pago y frescura analítica— resolviendo por escrito los casos límite: el 404 es bueno, el 429 a un atacante ni siquiera es válido, y un rechazo por fondos insuficientes no es un fallo nuestro, decisión que hay que tomar en el código instrumentando rechazado_legitimo frente a error_tecnico. Y sabes por qué no hay SLI de CPU: porque a ningún cliente le importa.
Dominas el presupuesto de error: la tabla de nueves traducida a minutos, la ventana móvil de 28 días que evita el incentivo perverso del mes natural, y sobre todo para qué sirve de verdad — resolver con un número la tensión entre desplegar rápido y no romper nada, con una política de cuatro estados y la cláusula de credibilidad de no congelar cuando el fallo fue de la plataforma. Con el uso que casi nadie hace: un presupuesto consumido al 5 % es tan mala señal como uno agotado.
Sabes crear SLO en Cloud Monitoring desde Terraform, distinguir SLI basados en peticiones de basados en ventanas, y montar alertas de tasa de consumo con dos umbrales y dos ventanas cada uno — la larga para evitar falsas alarmas, la corta para que la alerta se apague cuando el problema se arregla. Y sabes que el campo documentation, que casi todo el mundo deja vacío, vale más a las 3 de la madrugada que la alerta misma.
Tienes las arquitecturas de alta disponibilidad por niveles con la tabla componente a componente de AlpinaShop, la observación de que buena parte de la plataforma ya es multirregión sin esfuerzo porque los servicios globales de Google lo son, y las dos piezas que sí son regionales y críticas. Sabes exactamente qué compras con la HA de Cloud SQL —replicación síncrona de disco, otra zona, conmutación en 1-2 minutos— y qué no cubre: un DELETE sin WHERE se replica al instante. Y sabes que Cloud Run multirregión son tres comandos, pero que no se activa porque la disponibilidad la determina el componente menos disponible.
Diseñas contra los modos de fallo: dependencias duras y blandas —con el fallo de diseño más común, tratar una blanda como dura—, tiempos de espera en toda llamada de red con la regla de que el interno siempre sea menor que el externo, reintentos con retroceso exponencial y jitter para no crear ondas sincronizadas, interruptores de circuito, degradación elegante en tres niveles con la marca "degradado": true sin la cual la degradación se vuelve silenciosa, y limitación de carga con vertido por prioridad.
Sabes que alta disponibilidad y recuperación ante desastres no son lo mismo, y el dato que reordena prioridades: la causa más frecuente de pérdida de datos es alguien ejecutando algo en el entorno equivocado, y contra eso la HA no sirve porque replica el error. Manejas RTO y RPO y las cuatro estrategias con su coste, eligiendo por comparación con el coste real de una hora de caída.
Tienes el plan de AlpinaShop con siete escenarios —donde el más probable, un despliegue defectuoso, ya estaba resuelto desde 07-02—, la configuración de copias con las cuatro líneas que hacen el trabajo (point_in_time_recovery, location = "eu", retención de logs y deletion_protection) y la exportación de configuración, incluido el plan de DR guardado fuera de Google Cloud.
Y has pagado la deuda crítica de 07-04: el ensayo de restauración, cronometrado, con verificación de integridad y con la aplicación arrancada contra la copia. Con el resultado que importa: cinco problemas encontrados —47 minutos en lugar de "unos minutos", el usuario de la aplicación inexistente, permisos que faltaban, secuencias desincronizadas y ningún procedimiento escrito— que habrían triplicado el tiempo de recuperación real. El ensayo no confirma que las copias funcionan: encuentra los cinco problemas que impiden que funcionen.
Por último, tienes la práctica operativa: guardias sostenibles para tres personas con la regla que más desgaste evita —si la acción es "mirarlo mañana", esa alerta no suena de noche—, runbooks enlazados desde las alertas que empiezan por la causa más probable y dicen cuándo escalar, post-mortem sin culpables con la razón práctica y no moral de esa cultura, y pruebas de caos a escala razonable con sus seis reglas, donde el experimento más subestimado es que todo el equipo sepa revertir un despliegue.
Todo lo de esta lección funciona porque hay tres personas que lo sostienen. Y ahí está el límite: los SLO viven en un panel que alguien mantiene, la política de presupuesto de error se respeta porque el equipo la acordó, los runbooks se actualizan porque Marta se acuerda, y nada impide técnicamente que mañana alguien cree una VM con IP pública en una región de Estados Unidos, o desactive un log de auditoría, o borre por error un proyecto entero.
Eso ya no es fiabilidad: es gobierno. Y es lo único que queda por construir. La organización de AlpinaShop tiene hoy cinco proyectos, tres carpetas, decenas de cuentas de servicio, cuatro repositorios, un perímetro a medias y ninguna política que gobierne el conjunto — más una lista de deudas de 07-04 que empieza por "políticas de organización sin aplicar" y "logs de auditoría sin retención inmutable".
La última lección del módulo cierra el círculo: estructura de la organización, políticas que se heredan y se aplican solas, inventario de todo lo que existe, auditoría de verdad con retención inmutable, y zonas de aterrizaje desplegadas con Terraform — para que lo que se ha construido siguiendo a AlpinaShop no dependa de que tres personas se acuerden.
Curso de Google Cloud Platform (GCP)
Módulo 1: Introducción a Google Cloud Platform
- ¿Qué es Google Cloud Platform?
- Configuración de tu cuenta de GCP
- Descripción general de la consola de GCP
- Proyectos, jerarquía de recursos y facturación
- Regiones, zonas y modelo de responsabilidad compartida
- Cloud Shell y la CLI de gcloud
Módulo 2: Servicios principales de GCP
- Compute Engine: máquinas virtuales en Google Cloud
- Cloud Storage: almacenamiento de objetos
- Cloud SQL: bases de datos relacionales gestionadas
- App Engine: plataforma como servicio
- Google Kubernetes Engine (GKE)
- Bases de datos NoSQL: Firestore, Bigtable y Spanner
- Cómo elegir el servicio de cómputo adecuado
Módulo 3: Redes y seguridad
- Redes VPC
- Balanceo de carga en la nube
- Cloud CDN
- Gestión de identidad y acceso (IAM)
- Cloud Armor
- Secretos y cifrado: Secret Manager y Cloud KMS
- Cloud DNS, certificados TLS y publicación segura de servicios
Módulo 4: Datos y análisis
- BigQuery: el almacén de datos analítico
- Cloud Dataflow: procesamiento de datos por lotes y en streaming
- Cloud Dataproc: Spark y Hadoop gestionados
- Cloud Pub/Sub: mensajería asíncrona
- Cloud Data Fusion: integración de datos sin código
- Orquestación de pipelines con Cloud Composer y Workflows
- Gobierno del dato y cuadros de mando con Dataplex y Looker Studio
Módulo 5: Aprendizaje automático e IA
- Vertex AI: la plataforma de machine learning de GCP
- AutoML: modelos a medida sin escribir código
- TensorFlow en GCP: entrenamiento y servicio de modelos
- API de lenguaje natural
- API de visión
- IA generativa en Vertex AI: modelos Gemini y embeddings
- MLOps: del modelo al producto con Vertex AI Pipelines
Módulo 6: DevOps y monitoreo
- Cloud Build: integración continua en GCP
- Cloud Source Repositories y gestión del código fuente
- Cloud Functions: funciones sin servidor
- Cloud Monitoring (antes Stackdriver): métricas, paneles y alertas
- Cloud Deployment Manager e infraestructura como código nativa
- Cloud Logging y Cloud Trace: logs, trazas y diagnóstico
- Terraform en GCP: infraestructura como código en la práctica
Módulo 7: Temas avanzados de GCP
- Híbrido y multinube con Anthos
- Computación sin servidor con Cloud Run
- Redes avanzadas: VPC compartida, peering y conectividad híbrida
- Mejores prácticas de seguridad
- Gestión y optimización de costos
- Fiabilidad: SLO, alta disponibilidad y recuperación ante desastres
- Gobierno a escala: organización, políticas y auditoría
