Runbooks de incident response para inferencia LLM: cada alerta a una acción concreta con Kafka y Keep
Este post cierra la trilogía de observabilidad que abrieron Observabilidad GPU para inferencia LLM (qué métricas) y Anatomía de las doce métricas DCGM y cinco vLLM (qué anomalía documentada por métrica). Aquí cada anomalía recibe su acción concreta y se encaja en la maquinaria de gestión de incidentes que compliance exige.
TL;DR
Las alertas de observabilidad GPU son inútiles sin un procedimiento codificado por cada una; el operador que las interpreta a mano cada vez opera por intuición. La combinación correcta tiene tres piezas indispensables. (1) Catálogo de runbooks: para cada una de las seis alertas críticas (GpuHbmNearOom, GpuThermalOrPowerThrottle, GpuXidErrorDetected, GpuEccDoubleBit, VllmKvCachePoolNearFull, VllmTtftP95OutOfSlo), severity, mitigación inmediata, evidencia que capturar antes de remediar, acción de resolución, criterio de cierre y trigger de postmortem. (2) Pipeline reproducible: Prometheus + DCGM → Alertmanager → Kafka como event bus (topics gpu.alerts.enriched, incidents.lifecycle, audit.actions con retención WORM) → Keep como workflow engine (workflows declarativos YAML versionados en git) → ejecutores Kubernetes jobs / scripts / ChatOps. (3) Encaje formal en gestión de incidentes según el corpus normativo: ISO/IEC 27035 fases identify → report → assess → respond → learn; ENS controles op.exp.7 (gestión de incidentes), op.exp.8 (registro de actividad), op.exp.10 (notificación a usuarios); NIS2 art. 23 con notificación temprana 24 h, notificación formal 72 h e informe final 1 mes; EU AI Act art. 73 para incidente grave de un sistema de alto riesgo, plazos 2 a 15 días según severity; ISO/IEC 42001 cláusula 10 (mejora continua del AIMS). La taxonomía de acción es mitigación inmediata (drain, throttle, scale-down: contiene el daño en segundos) → diagnóstico (captura de evidencia con nvidia-smi -q, dmesg, vLLM /metrics snapshot, traza OTel relacionada; sin esto el postmortem no es defensible) → resolución (restart, reset, RMA, rollback) → postmortem (RCA por 5-whys, plan de prevención, actualización del runbook). Kafka aporta el audit trail inmutable que ENS y EU AI Act exigen — cada acción ejecutada por Keep o por humano se publica como evento en audit.actions con timestamp, actor, decisión y evidencia, retenido WORM mínimo 6 meses. Keep aporta los workflows como código: este post incluye tres workflows completos (XID con drain + ticket Jira, ECC DBE con paginación inmediata y bloqueo del nodo en scheduler, canary rollback automático por TTFT P95 fuera de SLO). Cuatro anti-patrones cierran el material: alertas sin runbook (la mayoría), runbook sin captura de evidencia previa (perpetúa el incidente porque la causa raíz se pierde), escalada por antigüedad en vez de severity (operador junior gestiona ECC DBE), ausencia de gate humano para acciones destructivas (Keep ejecutando nvidia-smi --gpu-reset sin confirmación). Aplicable a un cluster genérico de 4×H100 SXM con Kafka y Keep ya desplegados.
Estás aquí: OBSERVE → DEPLOY (incident response cierra el bucle)
La analogía: la sala de control de un reactor nuclear
En una sala de control de central nuclear, el operador de turno nunca decide qué hacer al ver una alarma. La decisión está pre-tomada y codificada en un procedimiento escrito (SOP) que cubre cada alarma del panel: si suena la X, abrir libro X, leer los pasos 1-N, ejecutar exactamente, llamar al supervisor en el paso M, escalar al director de planta en el paso N+3. La razón es estricta: las alarmas críticas son raras pero catastróficas si se gestionan mal; un operador improvisando en una emergencia toma decisiones peores que uno aplicando un procedimiento revisado por expertos y validado por simulación.
El reactor no espera que el operador sea genio. Espera que conozca los procedimientos al pie de la letra y que el sistema de gestión de operaciones le entregue el procedimiento correcto al momento. Si los procedimientos no están escritos, no están versionados, o no están integrados con las alarmas que disparan, la sala de control opera por intuición. La diferencia entre ambas operaciones —procedimentada vs intuitiva— es la diferencia entre una central que opera 30 años sin incidentes y otra que entra en lista negra.
El incident response de un cluster de inferencia LLM funciona idéntico. Las alertas DCGM y vLLM que los posts anteriores listaron son las alarmas del panel. Cada una necesita su SOP escrito, versionado, integrado con la alerta que la dispara y revisado tras cada incidente. Sin esa codificación, el operador de turno improvisa en mitad de un fallo de ECC DBE a las 4 de la mañana; con ella, ejecuta los nueve pasos del runbook 12 y el incidente se cierra en 20 minutos.
La arquitectura del incident pipeline
Prometheus + DCGM. Recolecta las métricas descritas en los dos posts anteriores. PrometheusRules definen las seis alertas críticas con for: <duración> para evitar ruido.
Alertmanager. Recibe alertas crudas; deduplica, agrupa por labels ({cluster, node, gpu, model}), enruta. En vez de enviar directamente a PagerDuty o Slack, envía a Kafka vía webhook receiver — esto convierte la alerta en un evento del bus que múltiples consumidores procesan (Keep para acción, audit topic para compliance, dashboards para visualización).
Kafka como event bus. Tres topics canónicos:
gpu.alerts.enriched— alertas con contexto añadido (tenant, modelo, versión, owner del namespace, severity efectiva). Retención: 7 días, replication factor 3.incidents.lifecycle— eventos del ciclo del incidente:incident.opened,incident.acknowledged,action.proposed,action.executed,incident.escalated,incident.resolved,postmortem.attached. Retención: 90 días.audit.actions— registro inmutable de cada acción ejecutada (por Keep automáticamente o por humano confirmando). Retención: 6 meses mínimo con compaction off + tiered storage, almacenamiento WORM. Es el topic que ENSop.exp.8, EU AI Act art. 12 y NIS2 obligan a conservar.
Keep como workflow engine. Consume de gpu.alerts.enriched, dispara workflows YAML versionados en git, ejecuta acciones (llamadas HTTP, kubectl jobs, mensajes Slack, tickets Jira) y publica el resultado en incidents.lifecycle + audit.actions. La elección de Keep sobre Alertmanager solo (o sobre PagerDuty solo) es deliberada: Keep separa declaración del runbook (YAML legible y revisable) de distribución de notificación (PagerDuty). El runbook es código versionado; las notificaciones son detalles operativos.
Ejecutores. Lo que de verdad mueve el cluster:
- Kubernetes jobs:
kubectl drain,kubectl cordon,kubectl rollout undo. - NVIDIA API:
nvidia-smi --gpu-reset,dcgmi diag -r <level>. - ChatOps: confirmaciones humanas a través de Slack interactive messages antes de ejecutar acción destructiva.
- Tooling externo: ticket Jira, notificación PagerDuty, llamada a CMDB.
Las seis alertas críticas y sus runbooks
Para cada alerta: severity, mitigación inmediata (segundos), evidencia que capturar antes de remediar, acción de resolución, criterios de cierre, trigger de postmortem.
RB-01 · GpuHbmNearOom — HBM > 92 % sostenido
Severity: WARNING. Riesgo OOM en la siguiente asignación de PagedAttention.
Mitigación inmediata. Reducir admission temporalmente bajando max_num_seqs del motor afectado vía hot reload (si el motor lo soporta) o restart escalonado de réplicas. Disparar scale-out adicional vía KEDA si hay nodos GPU libres. No es necesario drenar el nodo.
Evidencia a capturar.
nvidia-smi --query-gpu=index,memory.used,memory.free,memory.total --format=csv
nvidia-smi -q -d ROW_REMAPPER | grep -i pending
curl http://vllm-pod:8000/metrics | grep -E "gpu_cache_usage|num_requests"
kubectl logs <pod> --tail=200 | grep -i "preempt\|swap"
Guardar snapshot en audit.actions con timestamp y incident_id.
Resolución. Si la causa es pico de tráfico: dejar al autoscaler escalar a régimen estable, monitorizar 30 min. Si la causa es regresión de modelo (canary v2 consume más KV cache que v1): rollback del canary (ver RB-06). Si es leak (la métrica crece sin que el tráfico crezca): restart del pod con captura de heap dump.
Cierre. gpu_cache_usage_perc < 80 % sostenido durante 15 min Y num_requests_waiting == 0.
Postmortem. No obligatorio salvo si el incidente duró > 30 min o tuvo impacto en SLO.
RB-02 · GpuThermalOrPowerThrottle — bit ≠ 0 ni Idle en CLOCK_THROTTLE_REASONS
Severity: WARNING (térmico) o CRITICAL (HW Power Brake sostenido, riesgo PDU).
Mitigación inmediata. Identificar el bit (decodificar bitmap). Si es 0x40 HW_THERMAL o 0x20 SW_THERMAL: drenar workload del nodo a otras réplicas si la temperatura no baja en 2 min, evitar nuevos pods en ese nodo (kubectl cordon). Si es 0x80 HW_POWER_BRAKE: alerta a infraestructura de DC inmediatamente (probable PDU sobrecomprometida — caso Dell KB 000220508 / Lenovo HT514380), reducir TDP de las GPUs del rack vía nvidia-smi -pl a un valor menor para liberar carga sobre el breaker.
Evidencia.
nvidia-smi --query-gpu=index,temperature.gpu,temperature.memory,power.draw,clocks_throttle_reasons.active --format=csv
ipmitool sdr | grep -i "fan\|temp\|inlet"
# Datos de PDU si están instrumentadas (modbus / SNMP)
Resolución.
- Térmico: revisar flujo de aire del rack, verificar rear-door HX, T_inlet, ventiladores DGX. Issue de infra, no de motor.
- Power Brake: revisar dimensionado de PDU rama, breaker, distribución 415 VAC. Probable redistribución de carga a otra rama o limitación temporal de TDP.
Cierre. CLOCK_THROTTLE_REASONS == 0x1 (solo Idle) o 0x0 durante 30 min con carga normal.
Postmortem. Obligatorio si fue HW Power Brake — implica infraestructura eléctrica del DC.
RB-03 · GpuXidErrorDetected — increase(DCGM_FI_DEV_XID_ERRORS[5m]) > 0
Severity: CRITICAL.
Mitigación inmediata. kubectl cordon del nodo (sin más nuevos pods). Si el XID es 31/48/79/94/95 (hardware o cascada): drenar los pods existentes del nodo. Si el XID es 13/43 (posible software): mantener pods pero bloquear nuevos, capturar trace y workload activo.
Evidencia.
# El XID concreto del dmesg
dmesg | grep -i xid | tail -30
nvidia-smi -q -d ERROR
nvidia-smi -q -d PCIE
# Estado de las páginas retiradas
nvidia-smi -q -d ROW_REMAPPER
# Workload que estaba ejecutándose
kubectl get pods -o wide | grep <node>
kubectl logs <pod> --previous --tail=500
Resolución.
- XID 13/43 (software exception / channel verif): si recurre solo con un modelo concreto, es bug del workload — issue al equipo de modelos. Si es transitorio, reiniciar el pod basta.
- XID 31 (MMU fault): suele ser cascada de un XID 48 previo. Reset de la GPU (
nvidia-smi --gpu-reset -i <index>) o reboot del nodo si reset no resuelve. - XID 48 / 95 (DBE / uncontained ECC): ver RB-04. El nodo entra en cuarentena.
- XID 79 (fallen off the bus): reboot del nodo. Si recurre tras reboot, abrir RMA de la GPU. ByteDance reporta 43 % de coocurrencia con errores PCIe — verificar también el slot y el cable.
- XID 94 / 145 / 149: catalogados en el Xid Catalog de NVIDIA con procedimiento específico.
Cierre. Smoke test del nodo pasado (dcgmi diag -r 3), 24 h sin nuevos XIDs, vuelta al pool.
Postmortem. Obligatorio. Incluir XID concreto, distribución de XIDs en el cluster, MTBE actualizado.
RB-04 · GpuEccDoubleBit — DCGM_FI_DEV_ECC_DBE_VOL_TOTAL > 0
Severity: CRITICAL — corrupción de datos en curso.
Mitigación inmediata. Drenar el nodo inmediatamente sin esperar evidencia adicional. Páginas guardia (PagerDuty / OpsGenie) ON-CALL primario. Marcar el nodo unschedulable y failed. El XID 48 tiene 100 % probabilidad de matar el job en curso según el dataset de Story of Two GPUs; cualquier inferencia ya está comprometida.
Evidencia (en paralelo a la mitigación).
nvidia-smi -q -d ECC
nvidia-smi -q -d ROW_REMAPPER # Pending: Yes esperado
dmesg | grep -E "Xid.*48|DBE|double-bit" | tail -50
# Captura completa del estado de la GPU
dcgmi diag -r 4 -i <gpu_index>
Resolución. Reset completo de la GPU (nvidia-smi --gpu-reset) o reboot del nodo si reset no completa. El reset activa el row remap. Tras el reboot:
nvidia-smi -q -d ROW_REMAPPER # Pending: No esperado
nvidia-smi -q -d ECC # contadores volátiles a 0
Si RETIRED_DBE > 8 páginas tras el remap: planificar reemplazo de GPU en próxima ventana — la degradación del silicio es progresiva. Documentado ~19 horas de downtime típico en el caso real publicado.
Cierre. Nodo en pool tras 48 h sin nuevos DBE.
Postmortem. Obligatorio. Si el incidente afectó a una request con datos personales / clasificados, evaluar notificación a DPO bajo GDPR art. 33 (no es necesariamente brecha, pero hay que evaluarlo).
RB-05 · VllmKvCachePoolNearFull — gpu_cache_usage_perc > 95 % sostenido 3 min
Severity: WARNING (riesgo de preempt-on-OOM, no de OOM real).
Mitigación inmediata. Activar scale-out del autoscaler bajando el umbral de KEDA temporalmente (de 0.85 a 0.75) durante 30 min. Si está en modo recompute, los preempts elevan TTFT pero no rompen requests; aceptable a corto plazo. Si está en modo swap, latencia se va al techo — preferible cortar tráfico nuevo (devolver 503 desde el router) durante 5 min.
Evidencia.
curl http://vllm-pod:8000/metrics | grep -E "gpu_cache|num_requests|num_preemptions"
kubectl get hpa vllm-llama70b
kubectl logs <pod> --tail=200 | grep -i preempt
Resolución. Si recurre regularmente: capacity planning revisado, posiblemente reducir max_num_seqs o subir réplicas estables. Ver Capacity planning.
Cierre. Pool < 85 % sostenido 30 min, sin preempts en último 15 min.
Postmortem. No obligatorio salvo recurrencia > 3 veces / semana.
RB-06 · VllmTtftP95OutOfSlo — TTFT P95 > 1.5 s durante 5 min
Severity: CRITICAL (violación de SLO contractual).
Mitigación inmediata. Diagnóstico rápido del régimen (en orden de probabilidad):
- Si hay canary v2 activo y el ratio
ttft_p95(v2)/ttft_p95(v1) > 1.30: rollback automático del canary vía Argo Rollouts (argo rollouts abort vllm-llama70b). - Si
num_requests_waiting > 5: scale-out vía KEDA. - Si
DRAM_ACTIVE > 90 %+gpu_cache_usage_perc > 90 %: cuello en HBM, palanca de quantization o reducción de contexto. - Si
CLOCK_THROTTLE_REASONS != 0: ver RB-02.
Evidencia.
# Snapshot del histograma
curl http://vllm-pod:8000/metrics | grep time_to_first_token
# Distribución por versión si hay canary
# Estado DCGM del momento
curl http://dcgm-exporter:9400/metrics | grep -E "PIPE_TENSOR|DRAM_ACTIVE|THROTTLE"
# Tráfico activo
kubectl top pods -n inference
Resolución. Depende del diagnóstico. Casos típicos:
- Canary regresión → rollback completo (ver Canary).
- Saturación de capacidad → escalar réplicas o aceptar 503 temporal con
Retry-After. - Prefill bound → activar/calibrar chunked prefill o disaggregated serving (ver Disaggregated serving).
Cierre. TTFT P95 dentro de SLO sostenido 30 min.
Postmortem. Obligatorio. Documentar causa raíz y palanca aplicada; actualizar runbook.
Workflows Keep YAML — tres ejemplos completos
Los runbooks son útiles solo si están codificados en el workflow engine. Keep permite declararlos en YAML versionados en git.
Workflow 1 — xid-detected.yaml
workflow:
id: xid-detected-drain
name: "XID error detected — cordon node and capture evidence"
description: "RB-03 implementation"
triggers:
- type: alert
filters:
- key: alertname
value: GpuXidErrorDetected
steps:
- name: capture-evidence
provider:
type: bash
with:
command: |
set -e
NODE="{{ alert.labels.node }}"
GPU="{{ alert.labels.gpu }}"
INC_ID="{{ alert.fingerprint }}"
mkdir -p /var/evidence/$INC_ID
kubectl debug node/$NODE -it --image=nvcr.io/nvidia/cuda:12.4.0-base-ubuntu22.04 -- \
bash -c "nvidia-smi -q -d ERROR,PCIE,ROW_REMAPPER > /host/var/evidence/$INC_ID/smi.txt"
kubectl describe node $NODE > /var/evidence/$INC_ID/node.txt
- name: cordon-node
provider:
type: kubernetes
with:
action: cordon
name: "{{ alert.labels.node }}"
if: "{{ alert.labels.severity == 'critical' }}"
actions:
- name: open-jira-ticket
provider:
type: jira
config: "{{ providers.jira-prod }}"
with:
project: GPUOPS
issuetype: Incident
summary: "RB-03: XID {{ alert.annotations.xid_code }} on {{ alert.labels.node }}/{{ alert.labels.gpu }}"
description: |
Severity: {{ alert.labels.severity }}
XID: {{ alert.annotations.xid_code }}
Evidence: /var/evidence/{{ alert.fingerprint }}
Runbook: https://runbooks.example.local/RB-03
- name: notify-slack
provider:
type: slack
config: "{{ providers.slack-gpu-incidents }}"
with:
message: |
:warning: *RB-03 triggered*
Node: `{{ alert.labels.node }}` GPU: `{{ alert.labels.gpu }}`
XID: `{{ alert.annotations.xid_code }}`
<{{ jira.url }}|Jira ticket>
- name: emit-audit
provider:
type: kafka
config: "{{ providers.kafka-audit }}"
with:
topic: audit.actions
message:
incident_id: "{{ alert.fingerprint }}"
action: "cordon_node"
actor: "keep-workflow"
workflow_id: "xid-detected-drain"
target: "{{ alert.labels.node }}"
timestamp: "{{ now }}"
Workflow 2 — ecc-dbe.yaml — paginación inmediata
workflow:
id: ecc-dbe-critical
name: "ECC double-bit — page on-call and quarantine node"
triggers:
- type: alert
filters:
- key: alertname
value: GpuEccDoubleBit
steps:
- name: cordon-immediately
provider:
type: kubernetes
with:
action: cordon
name: "{{ alert.labels.node }}"
- name: drain-workload
provider:
type: kubernetes
with:
action: drain
name: "{{ alert.labels.node }}"
options:
ignore-daemonsets: true
delete-emptydir-data: true
grace-period: 120
- name: page-oncall
provider:
type: pagerduty
config: "{{ providers.pagerduty-critical }}"
with:
service_key: "{{ env.PD_SERVICE_KEY }}"
severity: critical
summary: "RB-04 ECC DBE on {{ alert.labels.node }}/{{ alert.labels.gpu }} — node drained"
- name: emit-lifecycle
provider:
type: kafka
config: "{{ providers.kafka-incidents }}"
with:
topic: incidents.lifecycle
message:
incident_id: "{{ alert.fingerprint }}"
event: incident.opened
severity: critical
runbook: RB-04
requires_postmortem: true
- name: notify-dpo
provider:
type: email
with:
to: dpo@example.local
subject: "ECC DBE en GPU productiva — evaluación necesaria"
body: |
Incidente RB-04 ECC DBE detectado en {{ alert.labels.node }}.
Modelo afectado: {{ alert.labels.model }}.
Por favor evaluar si hubo procesamiento de datos personales/clasificados
durante la ventana de error y necesidad de notificación GDPR art. 33.
Workflow 3 — canary-rollback.yaml — TTFT P95 fuera de SLO
workflow:
id: canary-rollback-ttft
name: "Rollback canary when TTFT P95 ratio v2/v1 > 1.30"
triggers:
- type: alert
filters:
- key: alertname
value: VllmTtftP95OutOfSlo
- key: canary_active
value: "true"
steps:
- name: check-ratio
provider:
type: prometheus
config: "{{ providers.prom-prod }}"
with:
query: |
histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version="v2"}[5m])))
/
histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version="v1"}[5m])))
condition: result > 1.30
actions:
- name: argo-rollback
provider:
type: kubernetes
with:
action: exec
command:
- kubectl
- argo
- rollouts
- abort
- "{{ alert.labels.rollout }}"
- -n
- "{{ alert.labels.namespace }}"
- name: notify-and-audit
provider:
type: kafka
config: "{{ providers.kafka-audit }}"
with:
topic: audit.actions
message:
incident_id: "{{ alert.fingerprint }}"
action: canary_rollback
ratio: "{{ steps.check-ratio.result }}"
actor: keep-workflow
timestamp: "{{ now }}"
Cada workflow se guarda en repos/keep-workflows/ versionado en git, revisado por pull request, validado por CI (keep workflow validate). El runbook escrito vive como docs/runbooks/RB-XX.md enlazado desde el workflow YAML — los dos siempre evolucionan juntos.
El schema canónico de eventos Kafka
Para que los topics sean consumibles por compliance, postmortem tooling y dashboards sin que cada consumer tenga que adivinar el shape, se fija schema con Avro / Protobuf.
{
"name": "IncidentLifecycleEvent",
"type": "record",
"fields": [
{ "name": "incident_id", "type": "string" },
{ "name": "event", "type": { "type": "enum", "symbols": [
"incident.opened", "incident.acknowledged", "action.proposed",
"action.executed", "action.failed", "incident.escalated",
"incident.resolved", "postmortem.attached"
]}},
{ "name": "timestamp", "type": "string", "logicalType": "timestamp-millis" },
{ "name": "actor", "type": "string" },
{ "name": "severity", "type": { "type": "enum", "symbols": ["low","warning","critical"] } },
{ "name": "runbook", "type": ["null","string"], "default": null },
{ "name": "alert_name", "type": "string" },
{ "name": "labels", "type": { "type": "map", "values": "string" } },
{ "name": "annotations", "type": { "type": "map", "values": "string" } },
{ "name": "evidence_uri", "type": ["null","string"], "default": null },
{ "name": "requires_postmortem", "type": "boolean", "default": false }
]
}
Para audit.actions (WORM), un schema separado más exigente con campos no-modificables:
{
"name": "AuditAction",
"type": "record",
"fields": [
{ "name": "incident_id", "type": "string" },
{ "name": "action", "type": "string" },
{ "name": "actor", "type": "string" },
{ "name": "actor_type", "type": { "type": "enum", "symbols": ["human","workflow","scheduler"] } },
{ "name": "workflow_id", "type": ["null","string"], "default": null },
{ "name": "target", "type": "string" },
{ "name": "command", "type": ["null","string"], "default": null },
{ "name": "result", "type": { "type": "enum", "symbols": ["success","failure","partial"] } },
{ "name": "timestamp", "type": "string", "logicalType": "timestamp-millis" },
{ "name": "evidence_uri", "type": ["null","string"], "default": null },
{ "name": "approver", "type": ["null","string"], "default": null }
]
}
El topic se configura con cleanup.policy=delete, retention.ms=15552000000 (6 meses) y min.insync.replicas=2 con acks=all para garantizar durabilidad. Para retención más larga sin coste de Kafka, tiered storage a Ceph RGW o S3-compatible — el log nuevo en hot tier, el viejo en cold tier transparente al consumer.
Encaje formal en gestión de incidentes
Los runbooks no son una práctica de SRE aislada — encajan en cuatro marcos normativos que las plataformas LLM productivas tocan a diario.
ISO/IEC 27035 — gestión de incidentes de seguridad de la información
Define el ciclo formal en cinco fases: plan & prepare → detect & report → assess & decide → respond → lessons learned. Cada fase tiene salidas exigibles documentalmente. La traducción al stack:
- Plan & prepare: los runbooks RB-01 a RB-06 + los workflows Keep son parte del Information Security Incident Management Plan. Versionados en git, revisados anualmente.
- Detect & report: las alertas Prometheus que entran a Kafka son la materialización.
- Assess & decide: la severity en
gpu.alerts.enriched+ la lógica del workflow Keep. - Respond: ejecución de los
steps+actionsdel workflow. - Lessons learned: postmortem obligatorio para los runbooks que lo marcan; salida documentada en el repo de postmortems + actualización del runbook.
ENS (Esquema Nacional de Seguridad) — controles op.exp
op.exp.7Gestión de incidentes: el catálogo de runbooks + el pipeline Keep / Kafka materializan la “respuesta organizada y procedimentada”.op.exp.8Registro de actividad: el topicaudit.actionscon retención WORM 6 meses (mínimo nivel ALTO).op.exp.9Registro de la gestión de incidentes: el topicincidents.lifecyclecon el ciclo completo de cada incidente.op.exp.10Protección de los registros de actividad: WORM + cifrado en reposo + control de acceso (consumers compliance solo-lectura).
NIS2 — notificación a autoridad competente
Para entidades esenciales / importantes, el art. 23 fija tres plazos a partir del significant impact detectado:
- 24 horas: notificación temprana (“early warning”) al CSIRT nacional (INCIBE-CERT en España).
- 72 horas: notificación formal con assessment inicial.
- 1 mes: informe final con causa raíz, impacto, medidas correctivas.
Los datos para esos informes salen directamente de incidents.lifecycle + audit.actions con un consumer que genera el dossier en el formato requerido. Sin el pipeline auditable, los plazos NIS2 son inalcanzables.
EU AI Act — art. 73 (serious incident reporting)
Aplicable a sistemas de alto riesgo. Plazos:
- 2 días: para incidentes que provoquen fallecimiento o daño irreversible a personas o infraestructuras críticas.
- 10 días: para incidentes que produzcan disrupción seria de infraestructura crítica.
- 15 días: para el resto de “serious incidents”.
La definición de “serious incident” incluye fallos sistemáticos del modelo, brecha de fundamental rights, daño material o medioambiental. Los runbooks deben marcar qué alertas pueden derivar en serious incident (típicamente cualquier cosa que afecte la salida del modelo en un contexto de alto riesgo) y disparar un sub-workflow específico de evaluación legal.
ISO/IEC 42001 — AIMS cláusula 10 mejora continua
El postmortem obligatorio post-incidente alimenta la cláusula 10. La actualización del runbook tras cada incidente que descubre un patrón nuevo es la “acción correctiva con verificación de eficacia” que la norma exige. Ver ISO 42001 AIMS.
Cuatro anti-patrones
Anti-patrón 1 — alertas sin runbook. La alerta dispara, el operador junior de guardia mira el dashboard, busca en Confluence, no encuentra nada actualizado, llama al senior por Slack, espera 20 minutos. En ese tiempo el incidente ha crecido. Regla: ninguna alerta entra a producción sin runbook publicado y workflow Keep aprobado. CI valida que cada PrometheusRule con severity ≥ warning tiene su keep workflow correspondiente.
Anti-patrón 2 — runbook sin captura de evidencia previa. El workflow ejecuta nvidia-smi --gpu-reset en cuanto llega el XID, perdiendo el estado que habría diagnosticado la causa raíz. El siguiente XID idéntico exige rehacer el diagnóstico desde cero. Regla: steps antes de actions; toda evidencia se captura primero, las acciones destructivas después.
Anti-patrón 3 — escalada por antigüedad en vez de severity. El operador junior de guardia gestiona un ECC DBE porque “le toca”. Le falta contexto para entender row remap, retired pages o el riesgo de corrupción de datos. Regla: paginación por severity, no por rotación: RB-04 y RB-03 dispararon ON-CALL primario senior con escalada automática a infra/hardware si no acuse en 10 min.
Anti-patrón 4 — ausencia de gate humano para acciones destructivas. El workflow ejecuta kubectl drain automáticamente sobre cualquier alerta marcada como CRITICAL. En la primera falsa alarma (un transitorio que se autoresolvió en 30 s), Keep drenó un nodo productivo durante hora pico. Regla: acciones destructivas (drain, reset, RMA, rollback completo) exigen confirmación humana vía Slack interactive message, con timeout configurable. Excepción justificada: ECC DBE confirmado por > 1 medición — el riesgo de corrupción supera el de falsa alarma.
Aplicado a hardware on-premise típico
Para un cluster genérico de 4 nodos × 4×H100 SXM 80 GB con Kafka y Keep ya desplegados:
- Kafka: cluster de 3 brokers en nodos no-GPU del cluster K8s; topics
gpu.alerts.enriched,incidents.lifecycle,audit.actionsconfigurados con replication factor 3, min.insync.replicas 2. Audit con tiered storage a Ceph RGW para retención > 6 meses sin coste brutal. - Keep: 2 réplicas del operator + 1 réplica del worker en un namespace
keep; conectado a Prometheus (provider read), Kafka (provider read + write), Slack, PagerDuty, Jira, Kubernetes (provider con SA específico con permisosget/list/patch nodes,create jobs). - Workflows: ~25-40 YAML en el repo
infra/keep-workflows/, sincronizado con el cluster vía Flux o Argo CD. Validados por CI (keep workflow validate) en cada PR. - Volumen de eventos: para 16 GPUs en operación normal con alertas debounced, ~50-200 eventos/día en
gpu.alerts.enriched. En incidente típico, picos de 500-2000 eventos/día. - Compliance consumers: un consumer python en namespace
complianceque genera reportes NIS2 / ENS / EU AI Act semanalmente, leyendoaudit.actionsyincidents.lifecycle.
Lo que no hemos cubierto (próximos posts)
- Playbooks de postmortem — la mecánica de RCA con 5-whys, Ishikawa adaptado a LLM, integración con MLflow tracking de re-training si el postmortem produce dataset enriquecido.
- Chaos engineering para LLM — inyección controlada de XID errors, ECC simulados, latencia HBM artificial para validar runbooks antes del incidente real.
- Multi-cluster incident coordination — cómo coordinar Keep entre clusters geográficos cuando un incidente afecta a múltiples regiones.
- Integración con CMDB y procurement — el ciclo
RMA → ticket → ServiceNow → reposición de hardwareautomatizado vía workflow. - Forense LLM — extracción de la traza OTel completa de una request afectada por un incidente, redacted PII, conservación en evidence vault.
Ver también
- Anatomía de las doce métricas DCGM y cinco vLLM — la anomalía documentada por métrica que estos runbooks resuelven.
- Observabilidad GPU para inferencia LLM — la lista compacta y las seis alertas críticas.
- Tracing LLM con OpenTelemetry GenAI — la traza OTel que se captura como evidencia.
- Canary, blue-green y shadow — el mecanismo de rollback que RB-06 invoca.
- Autoscaling LLM en Kubernetes — la palanca de escalado que RB-01 y RB-05 invocan.
- Capacity planning — el head-room presupuestado para absorber incidentes sin SLO break.
- ISO/IEC 42001 AIMS para LLM on-premise — la cláusula 10 que estos postmortems materializan.
- Controles técnicos ENS × 42001 × EU AI Act — el mapeo de controles que estos runbooks satisfacen.
- EU AI Act: mapeo a arquitectura LLM — el art. 73 de incidentes graves que activa el sub-workflow legal.
- Cinco niveles de madurez — los runbooks codificados son requisito del nivel 3-4.
Referencias
- ISO/IEC 27035-1:2023 — Information security incident management — Principles and process.
- ISO/IEC 27035-2:2023 — Information security incident management — Guidelines to plan and prepare for incident response.
- ENS — Real Decreto 311/2022, Anexo II controles
op.exp.7aop.exp.10. - Directiva NIS2 (UE 2022/2555) — art. 23 (notificación de incidentes significativos).
- Reglamento EU AI Act (UE 2024/1689) — art. 73 (reporting of serious incidents).
- ISO/IEC 42001:2023 — AI management system — cláusula 10 (mejora continua).
- Keep project —
keephq.devygithub.com/keephq/keep(documentación de workflows YAML, providers). - Apache Kafka — Tiered Storage y
cleanup.policy(docs.confluent.io / kafka.apache.org). - Confluent — Schema Registry y best practices para eventos lifecycle.
- NVIDIA — Xid Errors Documentation y procedimientos de remediación.
- Google SRE Book — Effective Troubleshooting y Postmortem Culture.
- Atlassian — Incident Management Handbook (referencia para severity matrices).