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

  1. Gestión de incidencias
  2. Runbooks
  3. SLI, SLO y presupuesto de error
  4. Gestión del cambio
  5. Planificación de capacidad para el puente de mayo
  6. Costes
  7. Calendario de mantenimiento
  8. La madurez del equipo

  1. 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 -10

Minutos 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: Iker

Lo 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.

  1. 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.

  1. 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"

  1. 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.

  1. 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 55

Y 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.

  1. 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 -rn
rutas-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) €"'
desarrollo    2104 €
plataforma     892 €
soporte        118 €
__unallocated__ 341 €

__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 Sin estado, arranque en segundos
worker-notificaciones Procesamiento de cola, reintentable por diseño
informes-ocupacion 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.

  1. 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.

  1. 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 requests demasiado provoca desalojos; recortar la retención deja ciega la siguiente investigación.
  • Olvidar deshacer los ajustes del pico. Subir maxReplicas y 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 wait

El 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

Módulo 2: Componentes Principales de Kubernetes

Módulo 3: Gestión de Configuración y Secretos

Módulo 4: Redes en Kubernetes

Módulo 5: Almacenamiento en Kubernetes

Módulo 6: Conceptos Avanzados de Kubernetes

Módulo 7: Monitoreo y Registro

Módulo 8: Seguridad en Kubernetes

Módulo 9: Escalado y Rendimiento

Módulo 10: Ecosistema y Herramientas de Kubernetes

Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real

Módulo 12: Preparación para la Certificación de Kubernetes

© Copyright 2026. Todos los derechos reservados