Todo lo que hemos construido hasta ahora tiene una fecha de puesta en marcha. Lo que viene después no la tiene: es el día a día de quien mantiene la plataforma viva, y es exactamente lo que ningún tutorial cuenta. Los tutoriales terminan cuando el pod pasa a Running. La realidad empieza ahí.
La realidad son las tres de la madrugada de un sábado con una alerta en el móvil. Es la reunión en la que alguien de finanzas pregunta por qué la factura de la nube ha subido un 60 % en cuatro meses. Es el desarrollador que quiere desplegar un viernes a las seis de la tarde. Es la conversación incómoda sobre si se puede prometer un 99,99 % de disponibilidad o solo un 99,9 %. Es la actualización del clúster que hay que hacer antes de que la versión pierda soporte, y que nadie quiere tocar porque funciona.
Esta lección cierra el módulo con esa parte: gestión de incidencias, runbooks, presupuesto de error como herramienta de decisión, gestión del cambio, planificación de capacidad, costes y calendario de mantenimiento. Y cierra también el recorrido completo de Rutas Norte.
Advertencia de cumplimiento normativo. Los procedimientos de gestión de incidencias descritos aquí afectan a la continuidad del servicio y, cuando una incidencia implica acceso indebido, pérdida o exposición de datos personales, activan obligaciones de notificación con plazos legales estrictos. La clasificación de severidades, los canales de comunicación y las plantillas de análisis posterior deben ser revisados por el responsable de cumplimiento normativo de la organización, que debe además determinar en qué casos una incidencia constituye una violación de seguridad notificable.
Contenido
- Gestión de incidencias
- Runbooks
- SLI, SLO y presupuesto de error
- Gestión del cambio
- Planificación de capacidad para el puente de mayo
- Costes
- Calendario de mantenimiento
- La madurez del equipo
- Gestión de incidencias
1.1. Niveles de severidad
La severidad no la determina lo alarmante que suene el problema, sino el impacto en el usuario y en el negocio. Definirlo por adelantado evita la discusión de "¿esto es grave?" justo cuando no hay tiempo para tenerla.
| Sev | Definición | Ejemplos en Rutas Norte | Respuesta | Comunicación |
|---|---|---|---|---|
| 1 | Servicio caído o inutilizable para la mayoría de usuarios; o pérdida/exposición de datos | www.rutasnorte.example no responde; postgres-reservas sin primario; no se puede comprar ningún billete |
Inmediata, 24×7. Se despierta a quien haga falta | Cliente + dirección, cada 30 min |
| 2 | Funcionalidad crítica degradada, o parcialmente caída | Los pagos fallan en el 30 % de los intentos; latencia p95 por encima de 2 s; pre bloqueado en plena publicación del puente |
Inmediata en horario ampliado (7:00-24:00) | Interna + dirección |
| 3 | Funcionalidad no crítica afectada; sin impacto directo en la venta | informes-ocupacion falla dos noches seguidas; Grafana inaccesible; una réplica de PostgreSQL caída con las otras dos sanas |
Siguiente día laborable | Interna |
| 4 | Molestia o riesgo latente; sin impacto actual | Certificado que caduca en 20 días; disco al 70 %; alerta ruidosa que hay que ajustar | Se planifica como tarea | Ninguna |
Dos reglas que evitan mucho daño:
- Ante la duda, se sube la severidad. Bajarla cuando se entiende el problema es barato. Descubrir a las dos horas que era una sev 1 tratada como sev 3 es caro.
- Cualquiera puede declarar una incidencia. No hace falta permiso ni jerarquía. Un falso positivo cuesta veinte minutos; una incidencia real no declarada cuesta horas.
1.2. Los roles durante una incidencia
En incidencias de severidad 1 y 2 se separan explícitamente tres roles. Con equipos pequeños una persona puede llevar dos, pero nunca se combinan mando y técnico.
| Rol | Qué hace | Qué NO hace |
|---|---|---|
| Mando de incidencia | Coordina, decide, mantiene la cronología, decide cuándo escalar y cuándo declarar resuelto | No toca el teclado. No diagnostica |
| Comunicación | Informa a negocio, dirección y clientes; actualiza la página de estado; filtra las interrupciones | No decide técnicamente |
| Técnicos | Diagnostican y aplican mitigaciones, informando al mando de todo lo que hacen | No hablan con el negocio, no deciden la comunicación |
Por qué separarlos no es burocracia, es una lección que se aprende sufriéndola. La persona que está depurando no puede a la vez contestar en tres canales, hablar con dirección y llevar la cronología: si lo intenta, hace las cuatro cosas mal. Sin alguien decidiendo, se acaba con tres personas aplicando mitigaciones contradictorias sobre el mismo sistema —uno reinicia pods, otro hace rollout undo, un tercero escala a mano—, que es un patrón real y frecuente. Sin comunicación dedicada, o no se informa a nadie, o los técnicos se pasan la incidencia respondiendo "¿ya está?" en vez de arreglándola. Y el mando, al no tener las manos en el teclado, mantiene la perspectiva: es quien se da cuenta de que llevan cuarenta minutos persiguiendo una hipótesis que no lleva a nada.
1.3. El guion de los primeros diez minutos
graph TB
A[Alerta o aviso] --> B{¿Impacto real<br/>en usuarios?}
B -->|No| C[Sev 3 o 4<br/>tarea planificada]
B -->|Sí| D[Declarar incidencia<br/>abrir canal dedicado]
D --> E[Asignar mando<br/>quien la declara, si no hay otro]
E --> F[Min 0-2: alcance<br/>¿qué falla, para quién, desde cuándo?]
F --> G[Min 2-4: ¿qué ha cambiado?<br/>despliegues, config, infraestructura]
G --> H[Min 4-7: MITIGAR<br/>no diagnosticar todavía]
H --> I[Min 7-10: comunicar<br/>estado + próxima actualización]
I --> J{¿Mitigado?}
J -->|Sí| K[Ahora sí: diagnosticar<br/>con calma]
J -->|No| L[Escalar: más gente,<br/>proveedor, mayor severidad]
Minutos 0-2. Alcance. Qué falla, para quién y desde cuándo. Las respuestas se escriben en el canal:
curl -s http://alertmanager.rutas-norte.example/api/v2/alerts \
| jq -r '.[] | select(.status.state=="active")
| "\(.labels.alertname)\t\(.labels.severidad)\t\(.startsAt)"' | sort -k3
curl -sG http://prometheus.rutas-norte.example/api/v1/query --data-urlencode 'query=
sum(rate(api_reservas_peticiones_total{codigo=~"5.."}[5m]))
/ sum(rate(api_reservas_peticiones_total[5m]))' | jq -r '.data.result[0].value[1]'Minutos 2-4. ¿Qué ha cambiado? El 70-80 % de las incidencias las causa un cambio reciente. Es la pregunta de mayor rendimiento del proceso entero.
git -C manifiestos log --since='6 hours ago' --oneline -- overlays/pro
argocd app history api-reservas-pro | tail -5
kubectl get events -A --sort-by=.lastTimestamp | tail -30
kubectl -n rutas-norte-pro get pods --sort-by=.status.startTime | tail -10Minutos 4-7. Mitigar, no diagnosticar. Este es el punto donde más equipos se equivocan. El impulso natural es entender qué pasa. El objetivo correcto es que deje de doler, y entender después.
| Mitigación | Cuándo | Comando |
|---|---|---|
| Revertir el último despliegue | Hubo despliegue en las últimas horas | git revert + argocd app sync |
| Abortar el canario | Hay un Rollout en progreso |
kubectl argo rollouts abort api-reservas |
| Apagar una bandera | El fallo es de una funcionalidad concreta | Cambio en el servicio de banderas |
| Escalar manualmente | La saturación es la causa | kubectl scale --replicas=N |
| Reiniciar el componente afectado | Fuga de memoria o estado corrupto | kubectl rollout restart deploy/X |
| Degradar deliberadamente | Un dependiente externo falla | Bandera de modo degradado |
Minutos 7-10. Comunicar. Un mensaje corto y honesto, con la próxima actualización comprometida:
[SEV 2] Fallos intermitentes al confirmar reserva
Inicio: 11:42 · Detectado: 11:47 · Estado: mitigando
Impacto: aproximadamente el 25 % de los intentos de compra fallan.
La consulta de horarios funciona con normalidad.
Acción: revirtiendo el despliegue de api-reservas 2.8.1 (11:38).
Próxima actualización: 12:15
Mando: Marta · Comunicación: IkerLo que hace útil este mensaje: dice el impacto en términos de usuario, no de infraestructura; dice qué se está haciendo; y compromete una hora concreta para la siguiente actualización, lo que corta de raíz las interrupciones.
1.4. La comunicación al negocio
| Audiencia | Qué necesita | Qué NO necesita |
|---|---|---|
| Dirección | Impacto en ventas, tiempo estimado, si hay riesgo de datos | CrashLoopBackOff, PromQL, nombres de pods |
| Atención al cliente | Qué decir al cliente y qué alternativa ofrecerle | Detalles técnicos |
| Clientes | Que se conoce el problema y que se está resolviendo | La causa técnica |
| Equipo técnico | Todo | — |
Reglas: se comunica antes de que pregunten; se dice lo que se sabe y explícitamente lo que no; nunca se promete una hora de resolución que no se puede garantizar (se promete la próxima actualización, no la resolución); y las malas noticias se dan pronto.
1.5. El análisis posterior sin culpables
Se hace en las 48 horas siguientes a toda incidencia de severidad 1 y 2, y a cualquiera de la que se pueda aprender algo.
Sin culpables significa una cosa muy concreta: se parte de que todo el mundo actuó razonablemente con la información y las herramientas que tenía en ese momento. Si alguien ejecutó un comando destructivo, la pregunta no es por qué lo hizo, sino por qué el sistema permitió que un comando así fuera fácil de ejecutar por error. Si alguien no vio la alerta, la pregunta no es por qué no la vio, sino por qué la alerta no era visible.
El motivo no es amabilidad, es eficacia: en cuanto se busca culpables, la gente deja de contar lo que pasó realmente, y sin eso no hay análisis posible.
La plantilla:
# Análisis posterior — INC-2026-041
## Resumen
Una frase: qué pasó, a quién afectó, cuánto duró.
## Impacto
- Duración: 11:42 – 12:31 (49 min)
- Usuarios afectados: ~2.400 intentos de compra fallidos
- Impacto económico estimado: ~11.000 € en ventas no completadas
- Datos: ninguna pérdida ni exposición ← si hubiera, activa el protocolo legal
## Cronología (horas exactas)
- 11:38 · Despliegue de api-reservas 2.8.1 (promoción aprobada)
- 11:42 · Comienzan los errores 500 en POST /reservas
- 11:47 · Se dispara ApiReservasTasaErrorAlta (for: 5m)
- 11:49 · Marta declara SEV 2 y asume el mando
- 11:53 · Se identifica el despliegue de 11:38 como sospechoso
- 11:58 · git revert + sync forzado
- 12:06 · Tasa de error de vuelta a la normalidad
- 12:31 · Se declara resuelta tras 25 min de observación
## Causa raíz
La versión 2.8.1 añadía una consulta a `reglas_precio` sin índice sobre
(linea, franja_horaria). Con el volumen de producción, cada confirmación
de reserva hacía un recorrido secuencial de 1,2 M de filas, agotando el
pool de conexiones y provocando errores 500 en cascada.
## Por qué no se detectó antes
1. `pre` tiene 40.000 filas en `reglas_precio`; producción, 1,2 M.
La consulta era instantánea en pre.
2. El análisis del canario se saltó: la promoción se hizo con
`promote --full` para llegar antes de la congelación del puente.
3. No hay alerta sobre consultas lentas de PostgreSQL.
## Qué funcionó bien
- La alerta se disparó 5 min después del inicio del problema.
- La reversión por Git tardó 8 min de decisión a servicio restablecido.
- La separación de roles funcionó: nadie interrumpió a los técnicos.
## Acciones de seguimiento
| # | Acción | Tipo | Responsable | Fecha |
|---|---|---|---|---|
| 1 | Índice sobre reglas_precio(linea, franja_horaria) | Correctiva | Ane | 06/08 |
| 2 | Poblar `pre` con un volumen representativo de producción (anonimizado) | Preventiva | Iker | 20/08 |
| 3 | Prohibir `promote --full` en pro salvo autorización del mando | Preventiva | Marta | 13/08 |
| 4 | Alerta sobre pg_stat_statements: consultas > 1 s | Detección | Ane | 20/08 |
| 5 | Runbook «latencia de la API disparada» | Respuesta | Jon | 27/08 |Cuatro criterios para que las acciones sirvan de algo: responsable con nombre (no "el equipo"), fecha concreta, priorizadas (mejor tres que se hacen que doce que no), y revisadas en la reunión semanal hasta cerrarse.
La acción número 2 de este ejemplo es la más valiosa y la más costosa: el problema de fondo no fue la consulta, fue que pre no se parecía a producción. Ese tipo de acción es la que evita familias enteras de incidencias futuras.
- Runbooks
Un runbook es el procedimiento escrito para un síntoma concreto. Su valor real es que permite que alguien que no es experto en ese componente responda correctamente a las tres de la mañana.
2.1. La plantilla
# [Nombre del síntoma]
**Alerta:** NombreDeLaAlerta · **Severidad:** N · **Actualizado:** fecha
## Síntoma
Qué se observa, tal como lo ve quien recibe la alerta.
## Impacto
Qué significa para el usuario y para el negocio. Determina la urgencia.
## Comprobación
Comandos exactos para confirmar el problema y acotar su alcance.
## Mitigación
Qué hacer YA para que deje de doler. Ordenado del más seguro al más agresivo.
## Resolución
Cómo arreglarlo de verdad, sin prisa, después de mitigar.
## Escalado
Si en X minutos no está mitigado: a quién avisar y con qué información.Reglas de un runbook útil: comandos copiables y ejecutables tal cual, no descripciones; mitigación antes que diagnóstico; enlazado desde la anotación de la alerta que lo dispara; y revisado tras cada uso, porque un runbook desactualizado es peor que ninguno.
2.2. Runbook: disco de postgres-reservas casi lleno
# Disco de postgres-reservas casi lleno
**Alerta:** PostgresDiscoAlto · **Severidad:** 2 (crítica si >95 %) · **Act.:** 2026-07-30
## Síntoma
El volumen de datos o de WAL de postgres-reservas supera el 85 % de uso.
## Impacto
Al 100 %, PostgreSQL DEJA DE ACEPTAR ESCRITURAS. No se pueden comprar
billetes. Sev 1 inmediata. Del 85 % al 100 % pueden pasar horas o minutos
según la carga.
## Comprobación
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- df -h /var/lib/postgresql/data /var/lib/postgresql/wal
# ¿Datos o WAL? Es la pregunta que decide todo lo demás.
# Si es WAL: ¿está fallando el archivado?
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- psql -U postgres -tc \
"SELECT last_archived_wal, last_failed_wal, last_failed_time FROM pg_stat_archiver;"
# Si es WAL: ¿hay un slot de replicación abandonado reteniendo segmentos?
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- psql -U postgres -tc \
"SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retenido FROM pg_replication_slots;"
# Si son datos: ¿qué ocupa?
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- psql -U postgres reservas -tc \
"SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;"
## Mitigación
1. **Expandir el volumen** (lo más seguro; la clase rutasnorte-rapida
permite expansión en línea, ver 05-05):
kubectl -n rutas-norte-pro patch pvc postgres-reservas-1 \
-p '{"spec":{"resources":{"requests":{"storage":"300Gi"}}}}'
kubectl -n rutas-norte-pro get pvc postgres-reservas-1 -w
2. **Si es un slot abandonado** (activo=false y varios GB retenidos):
confirmar con el equipo que esa réplica ya no existe y eliminarlo:
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- psql -U postgres -c \
"SELECT pg_drop_replication_slot('nombre_del_slot');"
⚠ Eliminar un slot ACTIVO rompe esa réplica. Verificar antes.
3. **Si es el archivado fallando**: revisar credenciales del almacén y
conectividad. Mientras no se archive, el WAL se acumula sin parar.
4. **NUNCA** borrar ficheros de pg_wal a mano. Corrompe el clúster.
## Resolución
- Datos: revisar retención de tablas históricas; particionar `reservas`
por fecha; VACUUM FULL en ventana de mantenimiento si hay hinchazón.
- WAL: reparar el archivado; ajustar max_wal_size si el pico es normal.
- Revisar la tendencia de crecimiento y planificar capacidad a 6 meses.
## Escalado
Si en 20 min no baja del 90 %, o si supera el 95 %: elevar a SEV 1,
avisar al mando de guardia y preparar la restauración de 11-02 por si
el clúster entra en modo de solo lectura.2.3. Runbook: latencia de la API disparada
# Latencia de api-reservas disparada
**Alerta:** ApiReservasLatenciaAlta (p95 > 500 ms, 10 min) · **Sev:** 2 · **Act.:** 2026-08-06
## Síntoma
El percentil 95 de latencia de api-reservas supera los 500 ms de forma
sostenida. Umbral del SLO: 300 ms.
## Impacto
La compra de billetes se percibe lenta; la conversión cae. Por encima de
2 s, los clientes abandonan y la tienda-web empieza a agotar tiempos de espera.
## Comprobación
# 1. ¿Es toda la API o una ruta concreta?
curl -sG http://prometheus.rutas-norte.example/api/v1/query --data-urlencode 'query=
histogram_quantile(0.95, sum by (ruta, le) (rate(api_reservas_duracion_segundos_bucket[5m])))' \
| jq -r '.data.result[] | "\(.metric.ruta)\t\(.value[1])"' | sort -k2 -rn
# 2. ¿Es carga (más peticiones) o degradación (mismas peticiones, más lentas)?
curl -sG http://prometheus.rutas-norte.example/api/v1/query --data-urlencode 'query=
sum(rate(api_reservas_peticiones_total[5m]))'
# 3. ¿Está el HPA escalando o está en el techo?
kubectl -n rutas-norte-pro get hpa api-reservas
# 4. ¿Estrangulamiento de CPU o presión de memoria?
kubectl -n rutas-norte-pro top pods -l app.kubernetes.io/name=api-reservas
# 5. ¿Es la base de datos?
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- psql -U postgres reservas -tc \
"SELECT substr(query,1,60), calls, round(mean_exec_time::numeric,1) AS ms
FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;"
# 6. ¿Se agotan las conexiones del pooler?
kubectl -n rutas-norte-pro logs deploy/postgres-reservas-pooler-rw --tail=50 | grep -i "pool\|wait"
# 7. ¿Es la pasarela de pagos externa?
curl -sG http://prometheus.rutas-norte.example/api/v1/query --data-urlencode 'query=
histogram_quantile(0.95, sum by (le) (rate(api_reservas_pagos_duracion_segundos_bucket[5m])))'
## Mitigación
| Hallazgo | Acción |
|---|---|
| HPA en maxReplicas | Subir maxReplicas temporalmente y verificar que hay nodos |
| Despliegue reciente | Revertir (ver runbook de despliegue fallido) |
| Consulta lenta nueva | Apagar la bandera de la funcionalidad implicada |
| Pool de conexiones agotado | Subir default_pool_size del Pooler (margen hasta ~150) |
| Pasarela de pagos lenta | Activar modo degradado: reserva sin pago inmediato |
| Estrangulamiento de CPU | Comprobar que no se ha introducido limits.cpu (ver 11-01) |
## Resolución
Corregir la causa concreta: índice ausente, consulta N+1, dimensionado
del pool, requests de CPU mal calculadas. Añadir el caso al conjunto de
pruebas de carga de k6 para que no vuelva.
## Escalado
Si p95 > 2 s durante más de 10 min: elevar a SEV 1. Si la causa es la
pasarela externa: contactar con el proveedor y activar modo degradado.2.4. Runbook: un despliegue que ha salido mal
# Despliegue fallido de api-reservas
**Disparador:** alerta tras despliegue, o Rollout abortado · **Sev:** 2 · **Act.:** 2026-08-06
## Síntoma
Tras un despliegue: errores 5xx, latencia alta, pods reiniciando o un
AnalysisRun de Argo Rollouts en Failed.
## Impacto
Depende del avance del despliegue. Con canario abortado, el impacto ya
está contenido. Con promote --full, afecta al 100 % de los usuarios.
## Comprobación
kubectl argo rollouts get rollout api-reservas -n rutas-norte-pro
git -C manifiestos log -3 --oneline -- overlays/pro
argocd app history api-reservas-pro | tail -5
kubectl -n rutas-norte-pro logs -l app.kubernetes.io/name=api-reservas \
--tail=100 --since=15m | grep -i error | head -30
## Mitigación (ordenada por velocidad)
1. **Bandera** (5 s) — si el cambio está tras una bandera, apagarla.
2. **Abortar el Rollout** (10 s) — si sigue en progreso:
kubectl argo rollouts abort api-reservas -n rutas-norte-pro
3. **Revertir en Git** (3-4 min) — SIEMPRE, aunque se haya hecho 1 o 2,
porque selfHeal volvería a poner la versión mala:
cd manifiestos
git revert --no-edit $(git log -1 --format=%H -- overlays/pro)
git push origin main
argocd app sync api-reservas-pro
argocd app wait api-reservas-pro --health --timeout 300
4. ⚠ **Si el despliegue incluía migración de esquema NO compatible**,
NO revertir sin consultar. Puede corromper datos. Escalar de inmediato.
## Verificación posterior
Ejecutar las capas 1-9 de la verificación de 11-01. No declarar resuelto
hasta 15 min sin alertas.
## Resolución
Análisis posterior obligatorio. Preguntas clave: ¿por qué no lo detectó
`pre`? ¿por qué no lo detectó el canario? ¿se saltó alguna puerta?
## Escalado
Si tras revertir el problema persiste, la causa NO era el despliegue:
volver al runbook de latencia o abrir investigación general (07-06).2.5. Runbook: nodo perdido
# Nodo perdido o NotReady
**Alerta:** NodoNoDisponible (NotReady > 5 min) · **Sev:** 3, o 2 si son varios · **Act.:** 2026-07-15
## Síntoma
Uno o más nodos en NotReady o desaparecidos del clúster.
## Impacto
Normalmente ninguno: los pods se reprograman solos. Es SEV 2 si caen
varios a la vez, si afecta a una instancia de postgres-reservas, o si el
clúster se queda sin capacidad para reprogramar.
## Comprobación
kubectl get nodes -o wide
kubectl describe node <nodo> | grep -A15 Conditions
kubectl get pods -A -o wide --field-selector spec.nodeName=<nodo>
kubectl get pods -A --field-selector status.phase=Pending # ¿hay dónde reprogramar?
kubectl -n rutas-norte-pro get cluster postgres-reservas # ¿afecta al estado?
## Mitigación
1. **Un solo nodo, resto sano**: normalmente no hay que hacer nada. Karpenter
aprovisiona uno nuevo en 1-3 min. Verificar con `kubectl get nodes -w` que
no quedan pods en Pending.
2. **Pods Pending por falta de capacidad**: comprobar que Karpenter puede
escalar (límites de la clase de nodo, cuotas de la nube, IPs de la VPC)
con `kubectl -n karpenter logs deploy/karpenter --tail=50`.
3. **Un PDB bloquea el desalojo** (ver coherencia HPA/PDB de 11-01):
`kubectl -n rutas-norte-pro get pdb`.
4. **Era una instancia de postgres-reservas**: verificar que el operador
promocionó y que se está reconstruyendo la réplica.
5. **NotReady que no se reemplaza**: forzar el drenaje y borrarlo:
kubectl drain <nodo> --ignore-daemonsets --delete-emptydir-data --force
kubectl delete node <nodo>
## Resolución
Identificar si fue interrupción de instancia interruptible (normal y esperado,
ver 10-06), fallo de hardware, agotamiento de recursos del kubelet o problema
de red. Si se repite en la misma zona, sospechar de la zona y considerar
excluirla temporalmente.
## Escalado
Si caen 3 o más nodos en 10 min, o si una zona entera desaparece: SEV 2,
avisar al mando, y evaluar el procedimiento de conmutación de 11-05.2.6. Enlazar los runbooks desde las alertas
El runbook solo sirve si aparece en el momento del aviso. La anotación runbook de las reglas de 07-04 es lo que lo consigue:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: rutas-norte-alertas
namespace: rutas-norte-pro
spec:
groups:
- name: api-reservas
rules:
- alert: ApiReservasLatenciaAlta
expr: |
histogram_quantile(0.95, sum by (le) (
rate(api_reservas_duracion_segundos_bucket[5m]))) > 0.5
for: 10m
labels: { severidad: "2", equipo: desarrollo, servicio: api-reservas }
annotations:
resumen: "Latencia p95 de api-reservas por encima de 500 ms"
descripcion: "p95 actual: {{ $value | humanizeDuration }}"
runbook: "https://wiki.rutasnorte.example/runbooks/api-latencia-alta"
panel: "https://grafana.rutas-norte.example/d/api-reservas"
- alert: PostgresDiscoAlto
expr: |
(1 - kubelet_volume_stats_available_bytes{persistentvolumeclaim=~"postgres-reservas.*"}
/ kubelet_volume_stats_capacity_bytes{persistentvolumeclaim=~"postgres-reservas.*"}) > 0.85
for: 5m
labels: { severidad: "2", equipo: plataforma }
annotations:
resumen: "Volumen {{ $labels.persistentvolumeclaim }} al {{ $value | humanizePercentage }}"
runbook: "https://wiki.rutasnorte.example/runbooks/postgres-disco-lleno"Y una política sencilla que mantiene la calidad: toda alerta que despierta a alguien debe tener runbook. Si no lo tiene, o se escribe o la alerta no debería despertar a nadie.
- SLI, SLO y presupuesto de error
En 07-04 vimos qué son. Aquí van a servir para tomar decisiones, que es lo único que los distingue de un adorno en un panel.
3.1. Los indicadores de Rutas Norte
Un SLI mide lo que el usuario experimenta, no lo que hace la infraestructura. "CPU al 80 %" no es un SLI; "el 99,7 % de las búsquedas responden en menos de 400 ms" sí.
| SLI | Definición precisa | Por qué este |
|---|---|---|
| Disponibilidad de compra | % de POST /reservas con respuesta no-5xx |
Es la transacción que genera ingresos |
| Latencia de búsqueda | % de GET /horarios respondidas en < 400 ms |
Es lo primero que hace el usuario; si es lento, se va |
| Disponibilidad de la web | % de peticiones a www con respuesta 2xx o 3xx |
Sin web no hay venta |
| Frescura de informes | % de días con informes-ocupacion completado antes de las 07:00 |
Operaciones planifica la flota con ellos |
# SLI de disponibilidad de compra, ventana de 30 días
sum(rate(api_reservas_peticiones_total{ruta="/reservas", metodo="POST", codigo!~"5.."}[30d]))
/
sum(rate(api_reservas_peticiones_total{ruta="/reservas", metodo="POST"}[30d]))3.2. Fijar el objetivo con criterio de negocio
El error clásico es elegir el número por estética: "99,99 % suena serio". Cada nueve adicional multiplica aproximadamente por diez el coste.
| SLO | Caída al mes | Coste relativo | Qué exige |
|---|---|---|---|
| 99,0 % | 7 h 18 min | × 1 | Un clúster, buenas prácticas |
| 99,5 % | 3 h 39 min | × 2 | Alta disponibilidad multi-zona |
| 99,9 % | 43 min | × 4 | Guardia, runbooks, despliegues seguros |
| 99,95 % | 21 min | × 8 | Guardia 24×7 activa, redundancia regional |
| 99,99 % | 4 min | × 20 | Activo-activo multi-región, automatización total |
Las tres preguntas que fijan el número: ¿qué cuesta cada minuto caído? (en Rutas Norte, unos 90 €/min un día normal y unos 900 €/min durante el puente de mayo); ¿qué percibe el usuario? (nadie nota tres minutos al mes, todo el mundo nota siete horas); y ¿qué hace la competencia y qué se ha prometido por contrato?
Los SLO de Rutas Norte:
| SLI | SLO | Ventana | Justificación |
|---|---|---|---|
| Disponibilidad de compra | 99,9 % | 30 días móviles | 43 min/mes ≈ 3.900 € en día normal. Aceptable. El salto a 99,95 % exigiría guardia 24×7 activa, que cuesta más de lo que ahorra |
| Latencia de búsqueda | 99,0 % < 400 ms | 30 días móviles | El 1 % de búsquedas lentas no mueve la conversión de forma medible |
| Disponibilidad de la web | 99,9 % | 30 días móviles | Igual que la compra: sin web no hay venta |
| Frescura de informes | 95 % de los días | 90 días | Operaciones tolera un retraso ocasional |
Nótese que ninguno es 100 %. Un SLO del 100 % significa que cualquier cambio es inaceptable, y eso paraliza el producto.
3.3. El presupuesto de error como herramienta de decisión
Presupuesto de error = 100 % − SLO. Con un SLO del 99,9 % en 30 días, el presupuesto es de 43 minutos y 12 segundos de indisponibilidad al mes.
Y aquí está la idea que lo cambia todo: ese presupuesto es un recurso que se puede gastar deliberadamente. No es un fallo tenerlo consumido en parte; es para lo que existe.
# Presupuesto consumido en la ventana de 30 días (0 = intacto, 1 = agotado)
(1 - (
sum(rate(api_reservas_peticiones_total{ruta="/reservas",metodo="POST",codigo!~"5.."}[30d]))
/ sum(rate(api_reservas_peticiones_total{ruta="/reservas",metodo="POST"}[30d]))
)) / (1 - 0.999)La política de Rutas Norte, aprobada por dirección y por el equipo:
| Presupuesto consumido | Qué se puede hacer | Quién decide |
|---|---|---|
| < 50 % | Ritmo normal. Se puede asumir más riesgo: canarios más rápidos, cambios de infraestructura | El equipo |
| 50-75 % | Ritmo normal, pero cada despliegue de riesgo alto requiere canario completo sin promote --full |
El equipo |
| 75-100 % | Alerta: solo correcciones y funcionalidades de bajo riesgo. Prioridad a las acciones de análisis posteriores pendientes | Plataforma |
| > 100 % (agotado) | Congelación de funcionalidades. El equipo dedica el 100 % de su tiempo a fiabilidad hasta recuperar el presupuesto. Sin excepciones sin aprobación de dirección | Dirección |
Lo valioso de esta política es que convierte una discusión de opiniones en una decisión basada en un número. Cuando producto pide una funcionalidad más y plataforma dice que el sistema está frágil, la conversación deja de ser "yo creo que" y pasa a ser "el presupuesto está al 130 %, y esto es lo que acordamos hacer en ese caso".
Y funciona en las dos direcciones, que es lo que la hace justa: si el presupuesto está al 20 % a mitad de mes, plataforma no puede bloquear un despliegue alegando riesgo. El sistema está siendo más fiable de lo que se prometió, y eso significa que se puede ir más deprisa.
Alertas sobre el presupuesto: se alerta por tasa de consumo, no por umbral absoluto. La expresión combina una ventana larga y una corta para evitar falsos positivos, y se dispara cuando el ritmo agotaría en dos días el presupuesto de treinta.
- alert: PresupuestoErrorConsumoRapido
expr: |
(1 - (sum(rate(api_reservas_peticiones_total{ruta="/reservas",codigo!~"5.."}[1h]))
/ sum(rate(api_reservas_peticiones_total{ruta="/reservas"}[1h])))) > 14.4 * 0.001
and
(1 - (sum(rate(api_reservas_peticiones_total{ruta="/reservas",codigo!~"5.."}[5m]))
/ sum(rate(api_reservas_peticiones_total{ruta="/reservas"}[5m])))) > 14.4 * 0.001
for: 2m
labels: { severidad: "2", equipo: plataforma }
annotations:
resumen: "Consumo rápido del presupuesto de error de compra"
runbook: "https://wiki.rutasnorte.example/runbooks/presupuesto-error"
- Gestión del cambio
4.1. Ventanas y congelaciones
| Tipo de cambio | Cuándo se permite | Aprobación |
|---|---|---|
| Corrección de bajo riesgo | Cualquier día laborable, 9:00-16:00 | Revisión de código |
| Funcionalidad con canario | Lunes a jueves, 9:00-15:00 | Puerta de promoción de 11-03 |
| Migración de esquema | Martes o miércoles, 10:00-12:00 | Plataforma + desarrollo |
| Cambio de infraestructura | Martes o miércoles, 9:00-12:00 | Plataforma + aviso 48 h |
| Actualización del clúster | Ventana programada trimestral | Plan escrito + ensayo en nopro |
| Corrección de incidencia | Siempre, sin ventana | Mando de incidencia |
Congelaciones programadas:
| Periodo | Duración | Qué se congela |
|---|---|---|
| Puente de mayo | Del 29 abril 18:00 al 5 mayo 9:00 | Todo salvo correcciones de sev 1 y 2 |
| Semana Santa | Viernes anterior a lunes posterior | Ídem |
| Agosto (semanas 32-33) | 2 semanas | Cambios de infraestructura; el resto con aprobación |
| Navidad | 22 dic a 7 ene | Todo salvo correcciones críticas |
Una congelación no es "no se trabaja": es "no se cambia producción". Durante la congelación del puente de mayo el equipo hace lo que no puede hacer el resto del año: escribir runbooks, mejorar pruebas, revisar alertas ruidosas, saldar deuda técnica que no toca producción.
4.2. Por qué desplegar los viernes por la tarde es mala idea
No es superstición, es aritmética del tiempo de detección y de la capacidad de respuesta. Los problemas no aparecen al instante: fugas de memoria, hinchazón de tablas, agotamiento de conexiones y llenado de disco tardan horas o días, y un despliegue del viernes a las 18:00 puede fallar el sábado a las 4:00. El fin de semana hay menos gente y peor disponible: el tiempo de restauración de 12 minutos de 11-03 se mide en horario laborable, y el sábado por la mañana es fácilmente el triple. El tráfico del fin de semana es distinto: en Rutas Norte el sábado se venden más billetes de ocio que cualquier día laborable, así que es cuando peor sienta un fallo. Nadie está vigilando, porque quien despliega se va a casa. Y hay un efecto perverso: sabiendo todo esto, quien despliega el viernes a las 18:00 lo hace con prisa y saltándose comprobaciones, para irse.
La regla de Rutas Norte: último despliegue a producción, jueves a las 15:00, y el viernes se dedica a observar lo desplegado durante la semana. La excepción son las correcciones de incidencia, que no tienen ventana porque el coste de no hacerlas es mayor.
- Planificación de capacidad para el puente de mayo
5.1. Los números de partida
| Métrica | Día normal | Puente de mayo (previsión) | Factor |
|---|---|---|---|
Peticiones/s a api-reservas (media) |
85 | 850 | ×10 |
| Pico de peticiones/s | 240 | 2.400 | ×10 |
| Reservas/día | 3.100 | 24.000 | ×7,7 |
| Consultas/s a PostgreSQL | 320 | 3.400 | ×10,6 |
| Ancho de banda de salida | 45 Mbps | 480 Mbps | ×10,7 |
El pico no está uniformemente repartido: se concentra entre las 10:00 y las 13:00 del miércoles y jueves previos al puente, cuando la gente compra el billete de vuelta.
5.2. El cálculo, componente a componente
api-reservas. De la prueba de carga de 09-06: una réplica con 200m de CPU sostiene unas 70 peticiones/s con p95 de 180 ms. Para 2.400 peticiones/s:
2400 / 70 = 34,3 réplicas necesarias
+ 20 % de margen de seguridad = 41 réplicas
maxReplicas actual: 40 → subir a 55Y hay que verificar que la aritmética cierra en toda la cadena, incluidas las conexiones a PostgreSQL, que fueron el cuello de botella de 11-02:
41 × 200m CPU = 8,2 vCPU de requests + tienda-web (0,75) + workers + sistema ≈ 14 vCPU
Nodos de 4 vCPU con ~3,2 útiles → 5 nodos mínimo, 8 con margen
Karpenter: el límite de la clase de nodo debe permitir al menos 12
41 réplicas × 20 conexiones = 820 solicitadas
PgBouncer max_client_conn 1000 ✔ (margen del 18 %)
default_pool_size 40 + reserve 10 = 50 reales contra 197 disponibles ✔ResourceQuota del namespace (03-04): con 41 réplicas de la API más el resto, la cuota actual de 20 vCPU se queda corta. Hay que subirla a 32 antes del puente, o el HPA pedirá pods que la cuota rechazará: un fallo silencioso y desconcertante.
PostgreSQL: 3.400 consultas/s frente a las 320 habituales. La prueba de carga mostró que el primario aguanta hasta 4.200 consultas/s antes de degradarse, con las lecturas de informes ya derivadas a réplicas. Suficiente, pero sin margen para una consulta nueva mal indexada, que es exactamente lo que ocurrió en el incidente INC-2026-041.
5.3. La prueba de carga que lo valida
// pruebas/carga-puente-mayo.js — se ejecuta en rutas-norte-pre
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const tasaFallos = new Rate('fallos_negocio');
const duracionCompra = new Trend('duracion_compra_completa');
export const options = {
scenarios: {
// Perfil realista: rampa de subida, meseta de 20 min y bajada.
puente_mayo: {
executor: 'ramping-arrival-rate',
startRate: 85,
timeUnit: '1s',
preAllocatedVUs: 300,
maxVUs: 2000,
stages: [
{ target: 85, duration: '2m' }, // línea base
{ target: 850, duration: '5m' }, // rampa
{ target: 850, duration: '20m' }, // meseta sostenida
{ target: 2400, duration: '3m' }, // pico
{ target: 2400, duration: '5m' }, // pico sostenido
{ target: 85, duration: '5m' }, // bajada
],
},
},
thresholds: {
'http_req_duration{tipo:busqueda}': ['p(95)<400'],
'http_req_duration{tipo:reserva}': ['p(95)<800'],
'http_req_failed': ['rate<0.005'],
'fallos_negocio': ['rate<0.01'],
},
};
const BASE = __ENV.BASE_URL || 'https://api-pre.rutasnorte.example';
export default function () {
group('flujo completo de compra', () => {
const busqueda = http.get(`${BASE}/horarios?linea=BIL-SAN&fecha=2026-05-01`,
{ tags: { tipo: 'busqueda' } });
check(busqueda, { 'búsqueda 200': (r) => r.status === 200 });
sleep(Math.random() * 3 + 1); // el usuario mira los resultados
if (Math.random() < 0.12) { // solo el 12 % reserva: embudo real
const inicio = Date.now();
const reserva = http.post(`${BASE}/reservas`,
JSON.stringify({ linea: 'BIL-SAN', fecha: '2026-05-01', plazas: 2 }),
{ headers: { 'Content-Type': 'application/json',
'Idempotency-Key': `k6-${__VU}-${__ITER}` },
tags: { tipo: 'reserva' } });
duracionCompra.add(Date.now() - inicio);
tasaFallos.add(!check(reserva, { 'reserva 201': (r) => r.status === 201 }));
}
});
}Resultados de la prueba del 15 de abril de 2026:
| Métrica | Objetivo | Resultado | Veredicto |
|---|---|---|---|
| p95 búsqueda | < 400 ms | 287 ms | ✔ |
| p95 reserva | < 800 ms | 612 ms | ✔ |
| Tasa de error HTTP | < 0,5 % | 0,08 % | ✔ |
| Réplicas alcanzadas | < 55 | 47 | ✔ |
| Tiempo de reacción del HPA | < 2 min | 95 s | ✔ |
| CPU máxima de PostgreSQL | < 80 % | 71 % | ✔ |
| Espera en cola de PgBouncer | 0 | 0 | ✔ |
| Nodos aprovisionados por Karpenter | — | 11 (en 3 min) | ⚠ ver nota |
La nota: Karpenter tardó tres minutos en aprovisionar los nodos del pico. Durante ese tiempo hubo pods en Pending y el p95 subió a 1,1 s. Acción tomada: sobreaprovisionar con pods de baja prioridad (PriorityClass negativa, de 06-05) que Karpenter desaloja instantáneamente cuando llega carga real, dejando nodos ya calientes.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata: { name: relleno-capacidad }
value: -10 # prioridad negativa: los desaloja cualquier carga real
globalDefault: false
description: "Reserva capacidad caliente. Se desaloja al llegar carga real."
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: relleno-capacidad, namespace: plataforma }
spec:
replicas: 6 # 6 la semana del puente; 2 el resto del año
selector: { matchLabels: { app: relleno-capacidad } }
template:
metadata: { labels: { app: relleno-capacidad } }
spec:
priorityClassName: relleno-capacidad
terminationGracePeriodSeconds: 0
containers:
- name: pausa
image: registry.k8s.io/pause:3.9
resources: { requests: { cpu: "1", memory: 2Gi } }5.4. La lista de preparación del puente
| # | Acción | Cuándo |
|---|---|---|
| 1 | Prueba de carga completa en pre con el perfil del puente |
3 semanas antes |
| 2 | Subir maxReplicas de los HPA según el cálculo |
2 semanas antes |
| 3 | Ampliar la ResourceQuota del namespace | 2 semanas antes |
| 4 | Verificar límites de Karpenter y cuotas de la nube | 2 semanas antes |
| 5 | Subir relleno-capacidad de 2 a 6 réplicas |
1 semana antes |
| 6 | Revisar y probar todos los runbooks | 1 semana antes |
| 7 | Confirmar el turno de guardia con nombres y teléfonos | 1 semana antes |
| 8 | Restauración de copia probada | 1 semana antes |
| 9 | Congelación de cambios | 29 abril 18:00 |
| 10 | Revisión diaria de métricas durante el puente | Diario |
| 11 | Deshacer 2, 3 y 5 | 1 semana después |
El punto 11 se olvida sistemáticamente y es el que hace que la factura del año siguiente arranque un 20 % más alta.
- Costes
6.1. Por qué la factura se dispara
| Causa | Mecanismo | Magnitud típica |
|---|---|---|
requests sobredimensionadas |
Se reserva 1 vCPU y se usan 80m. Se paga lo reservado | 30-50 % de desperdicio |
| Entornos que nadie apaga | dev y pre a plena capacidad 168 h/semana usándose 40 |
60 % del coste de no-producción |
| Autoescalado sin techo | El maxReplicas puesto "por si acaso" se alcanza durante un ataque o un bucle |
Picos de factura inexplicables |
| Recursos huérfanos | PV de pods borrados, balanceadores de Services eliminados a medias, IPs elásticas sin asignar | 5-15 %, creciendo con el tiempo |
| Retención de logs y métricas | Guardar todo a 30 días "por si acaso" | A menudo supera el coste de cómputo |
| Tráfico entre zonas | Pods repartidos hablando entre sí; cada byte entre zonas se factura | 3-8 %, invisible hasta que se mide |
| Volúmenes sobredimensionados | Un volumen de 500 GB con 40 GB usados cuesta lo mismo que uno lleno | Variable |
| Todo bajo demanda | No usar instancias interrumpibles para cargas tolerantes | Hasta 70 % de sobrecoste en esas cargas |
Y una causa de fondo que las engloba: nadie ve la factura de lo que despliega. Un desarrollador que pone requests: 2 en lugar de 200m no recibe ninguna señal.
6.2. Visibilidad con OpenCost
helm install opencost opencost/opencost -n plataforma \
--set opencost.prometheus.internal.enabled=true \
--set opencost.prometheus.internal.serviceName=prometheus-operated \
--set opencost.prometheus.internal.namespaceName=monitorizacion# Coste por namespace en los últimos 7 días
curl -sG http://opencost.plataforma:9003/allocation/compute \
-d window=7d -d aggregate=namespace -d accumulate=true \
| jq -r '.data[0] | to_entries[] | "\(.key)\t\(.value.totalCost | . * 100 | round / 100) €"' \
| sort -k2 -rnrutas-norte-pro 1842.30 €
rutas-norte-pre 612.45 €
monitorizacion 384.10 €
rutas-norte-dev 298.72 €
plataforma 156.90 €
argocd 41.20 €Que monitorizacion cueste más que rutas-norte-dev sorprende siempre, y es normal: Prometheus con retención larga y muchas series es caro.
| Herramienta | Modelo | Cuándo |
|---|---|---|
| OpenCost | Código abierto, CNCF | Empezar. Cubre lo esencial |
| Kubecost | Comercial, con nivel gratuito | Informes, presupuestos, alertas de coste, recomendaciones |
| Herramientas del proveedor | Incluidas | Bien para la factura global; malas para el detalle por pod |
6.3. Reparto por equipo y entorno
Aquí es donde las etiquetas de 02-07 dejan de ser una convención y se vuelven un instrumento de gestión.
# Coste por equipo
curl -sG http://opencost.plataforma:9003/allocation/compute \
-d window=30d -d aggregate=label:rutasnorte.example%2Fequipo -d accumulate=true \
| jq -r '.data[0] | to_entries[] | "\(.key)\t\(.value.totalCost | round) €"'__unallocated__ es el indicador de calidad del etiquetado: 341 € que nadie puede explicar. La política de Kyverno de 11-05 que exige la etiqueta rutasnorte.example/equipo existe precisamente para llevar ese número a cero.
El reparto informativo funciona mejor que la facturación interna real. Un informe mensual por equipo, comparado con el mes anterior, cambia comportamientos sin la burocracia de cobrar de verdad. Basta con que alguien vea que su servicio pasó de 300 a 700 euros.
6.4. Las palancas, ordenadas por rentabilidad
| # | Palanca | Ahorro típico | Esfuerzo | Riesgo |
|---|---|---|---|---|
| 1 | Dimensionar bien con las recomendaciones del VPA (09-02) | 25-40 % | Bajo | Bajo |
| 2 | Apagar no-producción de noche y fines de semana | 60 % de dev+pre |
Bajo | Nulo |
| 3 | Instancias interrumpibles para cargas tolerantes | Hasta 70 % en esas cargas | Medio | Bajo si hay PDB |
| 4 | Recursos huérfanos: volúmenes, balanceadores, IPs | 5-15 % | Bajo, una vez | Nulo |
| 5 | Retención de logs y métricas | 20-40 % de observabilidad | Bajo | Medio: menos histórico |
| 6 | Volúmenes sobredimensionados | Variable | Medio | Bajo |
| 7 | Compromisos de uso a 1-3 años | 20-40 % de la base estable | Bajo | Compromiso financiero |
Palanca 1: dimensionar bien. El VPA en modo recomendación, sin aplicar cambios, dice qué se desperdicia:
kubectl -n rutas-norte-pro get vpa api-reservas \
-o jsonpath='{.status.recommendation.containerRecommendations[0]}' | jq{"containerName":"api","lowerBound":{"cpu":"142m","memory":"198Mi"},
"target":{"cpu":"186m","memory":"241Mi"},"upperBound":{"cpu":"312m","memory":"402Mi"}}Con requests: 200m y target: 186m, api-reservas está bien dimensionada. El caso interesante fue worker-notificaciones, con requests: 1 de CPU y un target de 120m: un desperdicio del 88 % multiplicado por sus réplicas.
Palanca 2: apagar de noche. La más rentable en relación esfuerzo/ahorro.
apiVersion: batch/v1
kind: CronJob
metadata:
name: apagar-nopro
namespace: plataforma
spec:
schedule: "0 20 * * 1-5" # 20:00 de lunes a viernes (otro CronJob enciende a las 7:30)
jobTemplate:
spec:
template:
spec:
serviceAccountName: gestor-horarios
restartPolicy: OnFailure
containers:
- name: apagar
image: registry.rutasnorte.example/utiles/kubectl:1.30
command:
- /bin/sh
- -c
- |
for NS in rutas-norte-dev rutas-norte-pre; do
# Guardamos las réplicas actuales en un ConfigMap para restaurarlas.
kubectl -n "$NS" get deploy -o json \
| jq -r '.items[] | "\(.metadata.name)=\(.spec.replicas)"' > /tmp/r.txt
kubectl -n "$NS" create configmap replicas-guardadas \
--from-file=/tmp/r.txt --dry-run=client -o yaml | kubectl -n "$NS" apply -f -
kubectl -n "$NS" scale deploy --all --replicas=0
done
# postgres-reservas de pre NO se apaga: restaurarlo cuesta
# más tiempo del que se ahorra en dinero.Con el encendido a las 7:30, dev y pre funcionan 57 horas semanales en lugar de 168: un ahorro del 66 % en esos entornos.
Palanca 3: instancias interrumpibles. Continuando lo decidido en 10-06:
| Carga | ¿Interrumpible? | Razón |
|---|---|---|
api-reservas |
Parcialmente (50 %) | Con PDB y reparto topológico, tolera interrupciones; se mantiene una base bajo demanda |
tienda-web |
Sí | Sin estado, arranque en segundos |
worker-notificaciones |
Sí | Procesamiento de cola, reintentable por diseño |
informes-ocupacion |
Sí | Trabajo por lotes nocturno, con backoffLimit |
postgres-reservas |
No | Una interrupción del primario provoca conmutación. Bajo demanda siempre |
| Prometheus | No | Perder el pod pierde datos no persistidos |
Palanca 4: recursos huérfanos. El barrido que conviene hacer trimestralmente:
kubectl get pv --no-headers | awk '$5=="Released" || $5=="Available" {print $1, $2, $5}'
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,GB:Size,Creado:CreateTime}' --output table
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].PublicIp'
aws elbv2 describe-load-balancers --query 'LoadBalancers[].LoadBalancerName'En el primer barrido de Rutas Norte aparecieron 14 volúmenes huérfanos (1,8 TB, unos 180 €/mes) de entornos efímeros de 11-03 borrados antes de que existiera el CronJob de limpieza.
Palanca 5: retención. La política revisada de Rutas Norte:
| Dato | Antes | Después | Justificación |
|---|---|---|---|
| Logs de aplicación | 30 días, todos | 7 días completos + 90 días solo nivel>=warn |
Los logs de depuración de hace tres semanas no se consultan nunca |
| Métricas en Prometheus | 30 días a resolución completa | 15 días completa + 1 año agregada en almacenamiento remoto | El histórico se consulta agregado |
| Trazas | 30 días | 7 días, con muestreo al 10 % | Suficiente para investigar |
| Copias de PostgreSQL | 30 días | 30 días | No se toca. Sujeto a cumplimiento normativo |
Esta última fila importa: la retención de copias con datos personales no es una palanca de coste, es una decisión de cumplimiento. Reducirla para ahorrar es un error grave.
6.5. El antes y el después de la factura
| Concepto | Antes (mar. 2026) | Después (jul. 2026) | Ahorro | Palanca |
|---|---|---|---|---|
Cómputo pro |
2.840 € | 2.190 € | −650 € | Dimensionado (1) + interrumpibles (3) |
Cómputo dev+pre |
1.420 € | 510 € | −910 € | Apagado nocturno (2) + dimensionado (1) |
| Observabilidad | 890 € | 520 € | −370 € | Retención (5) |
| Almacenamiento | 640 € | 445 € | −195 € | Huérfanos (4) + volúmenes (6) |
| Tráfico de red | 310 € | 285 € | −25 € | Reparto por zona ajustado |
| Balanceadores | 180 € | 95 € | −85 € | Consolidación en un solo Ingress |
| Planos de control | 146 € | 146 € | 0 € | — |
| Total mensual | 6.426 € | 4.191 € | −2.235 € | −34,8 % |
Casi 27.000 euros anuales, con unas tres semanas-persona de trabajo y sin degradar ningún SLO. Los datos del periodo confirman que la disponibilidad de compra se mantuvo en el 99,94 %, por encima del objetivo.
La advertencia obligatoria: optimizar costes tiene un punto a partir del cual empieza a costar fiabilidad. Reducir requests por debajo de lo que la carga necesita provoca desalojos; llevar todo a instancias interrumpibles provoca interrupciones simultáneas; recortar la retención de métricas deja sin datos el análisis de una incidencia. La factura no es el SLO. Cuando entren en conflicto, gana el SLO, y esa decisión conviene tenerla escrita antes de que ocurra.
- Calendario de mantenimiento
Nada de esto es opcional; lo único opcional es si se hace planificado o de urgencia.
| Tarea | Periodicidad | Duración | Responsable | Si no se hace |
|---|---|---|---|---|
| Revisión de alertas disparadas y ruido | Semanal | 30 min | Plataforma | Fatiga de alertas: se ignoran todas |
| Revisión de acciones de análisis posteriores | Semanal | 30 min | Mando rotatorio | Las mismas incidencias se repiten |
| Revisión de vulnerabilidades (Trivy, imágenes en ejecución) | Semanal | 1 h | Seguridad | Se acumulan hasta ser inabordables |
| Revisión de costes por equipo | Mensual | 1 h | Plataforma + finanzas | La factura crece sin explicación |
| Actualización de complementos (Helm) | Mensual | 2-4 h | Plataforma | Se acumulan saltos de versión imposibles |
| Rotación de secretos de aplicación | Trimestral | 2 h | Plataforma | Credenciales de años, sin caducidad |
| Prueba de restauración de copias | Trimestral | 4 h | Plataforma | La copia no existe (11-02) |
| Revisión de RBAC y accesos | Trimestral | 3 h | Seguridad | Permisos de gente que ya no está |
| Revisión y prueba de runbooks | Trimestral | 3 h | Todo el equipo | Runbooks desactualizados = peor que nada |
| Revisión de SLO y presupuesto de error | Trimestral | 2 h | Plataforma + producto | Objetivos que ya no reflejan el negocio |
| Actualización menor del clúster | Trimestral | 1 día | Plataforma | Se pierde el soporte de la versión |
| Prueba de carga con el perfil de pico | Semestral | 1 día | Plataforma + desarrollo | Sorpresas en temporada alta |
| Simulacro de recuperación ante desastres | Anual | 2 días | Todo el equipo | El plan de 11-05 no funciona (y no se sabe) |
| Revisión de retención y cumplimiento normativo | Anual | 1 día | Plataforma + cumplimiento | Riesgo legal |
| Renovación de certificados no automatizados | Según caduquen | — | Plataforma | Caída por certificado caducado |
Sobre los certificados: los de cert-manager se renuevan solos, pero siempre queda alguno fuera del automatismo (certificados de cliente hacia la pasarela de pagos, certificados internos de la malla, certificados de firma). Conviene un inventario con alertas a 30, 14 y 7 días.
El presupuesto de mantenimiento. Sumando las tareas, el mantenimiento consume aproximadamente el 20-25 % de la capacidad del equipo de plataforma. Es un número que hay que decir en voz alta y defender ante quien planifica: un equipo al 100 % en funcionalidades nuevas es un equipo que está acumulando deuda operativa a crédito, y esa deuda se cobra en forma de incidencias.
- La madurez del equipo
La madurez de una plataforma no se mide por las herramientas que usa. Se mide por lo que ocurre cuando algo va mal.
| Nivel | Cómo se reconoce |
|---|---|
| Reactivo | Se entera por los clientes. Nadie sabe qué se desplegó. Los cambios se hacen a mano. Cada incidencia se afronta desde cero. Alguien concreto es imprescindible |
| Instrumentado | Hay métricas y alertas, pero muchas son ruido. Hay runbooks, algunos actualizados. Los despliegues son automáticos pero los incidentes, artesanales |
| Gestionado | Los SLO están definidos y se usan para decidir. Toda alerta que despierta tiene runbook. Los análisis posteriores generan acciones que se cierran. La capacidad se planifica. Los costes se atribuyen |
| Optimizado | El presupuesto de error gobierna la planificación. Los simulacros son rutina. La mayoría de las incidencias se mitigan solas. El equipo dedica más tiempo a prevenir que a apagar fuegos |
Rutas Norte pasó de reactivo a gestionado en unos catorce meses, y no fue la tecnología lo que lo permitió, sino cuatro cambios de hábito: análisis posteriores sin culpables en todas las incidencias, que al sexto revelaron que la mitad compartían causa de fondo; runbooks escritos por quien sufrió la incidencia, inmediatamente después; el presupuesto de error como argumento compartido, que convirtió la tensión entre producto y plataforma en una conversación con datos; y el calendario de mantenimiento defendido como trabajo real, con hueco en la planificación y no en los ratos libres.
Tres señales de que un equipo madura de verdad: las incidencias se acortan antes de dejar de ocurrir (no se eliminan los fallos, se responde mejor); baja el número de personas imprescindibles, porque que solo una sepa resolver algo es un riesgo y no una virtud; y aumenta el tiempo dedicado a prevenir, que es el indicador definitivo de haber salido del modo apagafuegos.
Errores Comunes y Consejos
- Diagnosticar antes de mitigar. Entender la causa raíz es importante, pero no mientras los usuarios están sufriendo. Primero que deje de doler.
- El mando de incidencia con las manos en el teclado. Pierde la perspectiva y deja de coordinar. En sev 1 y 2, el mando no toca nada.
- Análisis posteriores que buscan culpables. La gente deja de contar lo que pasó y el análisis pierde todo su valor.
- Acciones de seguimiento sin responsable ni fecha. No se hacen. Mejor tres acciones con nombre y fecha que doce en una lista.
- Alertas sin runbook que despiertan a alguien. O se escribe el runbook, o la alerta no debería despertar a nadie.
- SLO al 99,99 % sin haber calculado lo que cuesta. Cada nueve multiplica el coste por diez aproximadamente. Elige el número con criterio de negocio.
- Presupuesto de error como panel decorativo. Si no cambia lo que hace el equipo cuando se agota, no sirve para nada.
- Optimizar costes sin vigilar los SLO. Reducir
requestsdemasiado provoca desalojos; recortar la retención deja ciega la siguiente investigación. - Olvidar deshacer los ajustes del pico. Subir
maxReplicasy la cuota para el puente y no bajarlos después es la vía más habitual de que la factura suba permanentemente. - Consejo: cronometra los runbooks. Un runbook que en la práctica lleva cuarenta minutos cuando dice diez necesita revisión.
- Consejo: haz que la guardia rote de verdad. Si siempre responde la misma persona, el conocimiento no se distribuye y esa persona acaba quemada.
- Consejo: celebra las incidencias bien gestionadas. Un equipo que solo recibe atención cuando algo sale mal acaba ocultando problemas.
Ejercicios
Ejercicio 1: gestionar una incidencia
Sábado, 09:14. Llega la alerta ApiReservasTasaErrorAlta (12 % de errores 5xx). Es el primer fin de semana de julio, con mucho tráfico de ocio. El último despliegue a pro fue el jueves a las 14:30. Estás de guardia y hay otra persona localizable. Describe: (a) la severidad que declaras y por qué; (b) los roles que asignas; (c) los comandos exactos de tus primeros cinco minutos; (d) el mensaje de comunicación que envías a los diez minutos.
Ejercicio 2: decidir con el presupuesto de error
Estamos a 18 de septiembre. El presupuesto de error de la disponibilidad de compra (SLO 99,9 %, ventana de 30 días) está consumido al 118 %: dos incidencias sev 2 se llevaron 51 minutos. Producto pide desplegar el nuevo comparador de precios, que lleva tres semanas listo y que dirección quiere para la campaña de otoño. Plataforma tiene cuatro acciones de análisis posterior pendientes. Aplica la política del apartado 3.3: ¿qué decides, quién decide, qué propones como alternativa y qué condiciones pondrías para levantar la congelación?
Ejercicio 3: plan de reducción de costes
La factura mensual de un clúster es: cómputo pro 3.100 €, cómputo no-producción 1.900 €, observabilidad 1.200 €, almacenamiento 800 €, red 400 €, balanceadores 300 €. Datos adicionales: el VPA recomienda una media del 45 % de las requests actuales; dev y pre funcionan 24×7; Prometheus retiene 45 días a resolución completa; hay 22 volúmenes en estado Released; ninguna carga usa instancias interrumpibles. Propón un plan ordenado por rentabilidad, con el ahorro estimado de cada palanca, el riesgo asociado y qué medirías para comprobar que no se degrada el servicio.
Soluciones
Solución 1.
(a) Severidad 2. Un 12 % de errores es degradación seria de una funcionalidad crítica (la compra), pero no caída total: el 88 % de los intentos funciona. Sería sev 1 si superara el 50 % o si no se pudiera comprar en absoluto. El contexto de fin de semana de julio con mucho tráfico refuerza la urgencia y justifica llamar a la segunda persona.
(b) Como estamos dos: yo asumo mando y comunicación; la persona localizable, el rol técnico. Si escala a sev 1, se llama a una tercera persona y se separan mando y comunicación. Lo que no se hace es que el mando se ponga a depurar.
(c) Primeros cinco minutos:
# Alcance
curl -s http://alertmanager.rutas-norte.example/api/v2/alerts \
| jq -r '.[] | select(.status.state=="active") | "\(.labels.alertname)\t\(.startsAt)"'
kubectl -n rutas-norte-pro get pods -l app.kubernetes.io/name=api-reservas
kubectl -n rutas-norte-pro get hpa api-reservas
# ¿Qué ha cambiado? (el despliegue fue hace 43 h: probablemente NO es la causa)
git -C manifiestos log --since='48 hours ago' --oneline -- overlays/pro
kubectl get events -A --sort-by=.lastTimestamp | tail -20
# ¿Qué falla exactamente?
kubectl -n rutas-norte-pro logs -l app.kubernetes.io/name=api-reservas \
--tail=100 --since=20m | grep -i error | head -20
# Sospechosos habituales de un fin de semana con tráfico alto
kubectl -n rutas-norte-pro get cluster postgres-reservas
kubectl -n rutas-norte-pro exec postgres-reservas-1 -- df -h /var/lib/postgresql/data
kubectl -n rutas-norte-pro logs deploy/postgres-reservas-pooler-rw --tail=30 | grep -i waitEl razonamiento clave: como el despliegue fue hace 43 horas y el problema empieza ahora, la hipótesis más probable no es un cambio de código sino saturación por el tráfico del fin de semana (HPA en el techo, conexiones agotadas, disco lleno) o un problema acumulativo (fuga de memoria, hinchazón de tablas). Se van al runbook de latencia y al de disco.
(d) Comunicación a los diez minutos:
[SEV 2] Fallos intermitentes al comprar billetes
Inicio: 09:14 · Estado: investigando
Impacto: aproximadamente el 12 % de los intentos de compra falla y hay
que reintentar. La búsqueda de horarios funciona con normalidad.
Acción: investigando saturación por el tráfico del fin de semana;
el HPA está escalando. Descartado el despliegue del jueves.
Próxima actualización: 09:40
Mando: [nombre] · Técnico: [nombre]Solución 2. Decisión: no se despliega el comparador de precios. El presupuesto está agotado (118 %), lo que activa la congelación de funcionalidades de la política. La decisión corresponde a dirección, no al equipo, precisamente porque una excepción tiene coste de negocio y debe asumirla quien tiene esa responsabilidad. El papel de plataforma es presentar el dato, la política acordada y las alternativas, no bloquear unilateralmente.
Alternativa que se propone: durante las próximas dos semanas el equipo cierra las cuatro acciones pendientes, y el comparador se despliega detrás de una bandera de funcionalidad apagada (11-04). Así el código llega a producción sin cambiar el comportamiento, se valida técnicamente, y la activación queda lista para el momento en que el presupuesto se recupere. Esto satisface parcialmente a producto sin consumir presupuesto.
Condiciones para levantar la congelación: (1) las cuatro acciones cerradas y verificadas; (2) el presupuesto en la ventana de 30 días móviles por debajo del 100 %, lo que ocurrirá automáticamente al salir las incidencias de la ventana si no hay nuevas; (3) el despliegue del comparador debe hacerse con canario completo, sin promote --full, con análisis de la métrica de conversión; (4) revisión conjunta de si el SLO del 99,9 % sigue siendo el adecuado, porque dos incidencias sev 2 en un mes pueden indicar que el objetivo es demasiado exigente para la inversión actual, o que hay un problema de fondo sin resolver.
Solución 3. Total actual: 7.700 €/mes.
| Orden | Palanca | Cálculo | Ahorro/mes | Riesgo |
|---|---|---|---|---|
| 1 | Apagar no-producción noches y fines de semana | 1.900 × 0,66 | ~1.254 € | Nulo. Solo hay que gestionar el arranque matinal |
| 2 | Dimensionar según VPA (aplicar con margen, no al 45 % exacto) | (3.100 + 646) × ~0,30 | ~1.124 € | Bajo-medio. Aplicar con margen del 20 % sobre target y vigilar desalojos |
| 3 | Retención de Prometheus de 45 a 15 días + agregado remoto | 1.200 × 0,50 | ~600 € | Medio. Se pierde histórico fino; mitigar con almacenamiento remoto agregado |
| 4 | Instancias interrumpibles en cargas tolerantes | ~40 % del cómputo apto × 0,65 | ~550 € | Bajo con PDB y topologySpread correctos. Nunca para la base de datos |
| 5 | Eliminar 22 volúmenes Released |
Estimado | ~150 € | Nulo, previa verificación de que no contienen datos necesarios |
| 6 | Consolidar balanceadores en un solo Ingress | 300 × 0,50 | ~150 € | Bajo |
| Total | ~3.828 € (−50 %) |
Orden justificado: primero lo de riesgo nulo y esfuerzo bajo (1, 5), después lo de mayor ahorro absoluto (2), y por último lo que exige más cuidado (3, 4).
Qué medir para comprobar que no se degrada el servicio: (a) el presupuesto de error de todos los SLO antes y después de cada palanca, que es el indicador definitivo; (b) desalojos por presión de memoria y reinicios OOMKilled tras aplicar la palanca 2; (c) latencia p95 y tiempo de reacción del HPA tras la palanca 4, porque las interrupciones aumentan la rotación de pods; (d) tiempo de resolución de incidencias tras la palanca 3, que es donde se notaría la falta de histórico. Regla de aplicación: una palanca cada vez, con dos semanas de observación entre ellas, para poder atribuir cualquier degradación a su causa.
Conclusión
Hemos recorrido el día a día de quien mantiene una plataforma viva. La gestión de incidencias con severidades definidas de antemano, roles separados —y el mando sin tocar el teclado—, el guion de los primeros diez minutos que prioriza mitigar sobre diagnosticar, y el análisis posterior sin culpables cuyas acciones tienen responsable y fecha. Los runbooks, con su plantilla y cuatro casos reales de Rutas Norte enlazados desde las anotaciones de las alertas. Los SLI y SLO elegidos con criterio de negocio, y el presupuesto de error convertido en una política que decide qué hace el equipo el mes que viene. La gestión del cambio, con sus ventanas, su congelación del puente de mayo y las razones aritméticas por las que no se despliega los viernes por la tarde. La planificación de capacidad, con los números del ×10 y la prueba de carga que los valida. Los costes, con las palancas ordenadas por rentabilidad y una reducción real del 35 % sin tocar ningún SLO. Y el calendario de mantenimiento, que consume una cuarta parte del equipo y hay que defender como trabajo real.
Con esto se cierra el módulo 11 y, con él, el recorrido de Rutas Norte. Empezamos con una empresa que vendía billetes de autobús y unos manifiestos sueltos. Le dimos Pods, Deployments y Services; configuración y secretos; redes, Ingress y TLS; almacenamiento persistente con copias; StatefulSets, operadores y planificación avanzada; observabilidad con métricas, logs y alertas; seguridad desde la imagen hasta el RBAC; autoescalado para el puente de mayo; Helm, Kustomize, GitOps y un clúster gestionado. Y en este último módulo lo hemos visto todo funcionar junto: una aplicación web completa en producción con sus trece objetos, una base de datos con estado con su conmutación medida y su restauración cronometrada, una canalización que va del git push a producción sin tocar nunca kubectl apply, despliegues canarios que abortan solos cuando Prometheus dice que algo va mal, una flota de clústeres gobernada desde un repositorio, y la operación diaria que sostiene todo lo anterior.
Si has llegado hasta aquí, hay algo que conviene decir con claridad: ya tienes el conocimiento que miden las certificaciones de Kubernetes. El CKA pregunta por clústeres, nodos, redes, almacenamiento y resolución de problemas: es lo que hemos hecho en los módulos 1 a 7 y en las incidencias de este. El CKAD pregunta por Pods, Deployments, configuración, sondas y trabajos: es el módulo 2 al 6 y toda la lección 11-01. El CKS pregunta por endurecimiento, políticas, seguridad de imágenes y auditoría: es el módulo 8 completo.
Lo que falta no es conocimiento, es formato. Los exámenes son prácticos, cronometrados, con una terminal y la documentación oficial como única ayuda, y penalizan sin piedad la lentitud con kubectl. Es un tipo de destreza distinto del que se necesita para operar una plataforma real: allí lo importante es decidir bien; aquí, además, hay que hacerlo rápido y sin dudar de la sintaxis.
De eso trata el módulo 12, Preparación para la Certificación de Kubernetes: qué mide exactamente cada examen, cómo se distribuye el tiempo, qué atajos de kubectl marcan la diferencia entre terminar y no terminar, qué buscar en la documentación permitida y cómo prepararse con simulacros cronometrados. Empezaremos por el CKA.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
