Métricas esenciales para construir reglas de alerta
Node Exporter expone una gran cantidad de indicadores del sistema operaitvo. Antes de crear reglas de alerta, conviene identificar las métricas que reflejan de forma fiable el estado de salud de un servidor.
El siguiente conjunto de expresiones de PromQL sirve como punto de partida para detectar problemas de recursos:
# Porcentaje de CPU no ociosa
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[1m]))) * 100
# Proporción de memoria consumida
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100
# Ocupación de filesystem
(node_filesystem_size_bytes - node_filesystem_avail_bytes) / node_filesystem_size_bytes * 100
# Saturación de E/S de disco
rate(node_disk_io_time_seconds_total[5m])
# Carga por núcleo
node_load1 / count without(cpu, mode) (node_cpu_seconds_total{mode="idle"})
Para redes, se pueden supervisar tanto el rendimiento como la calidad del tráfico:
# Tasa de errores en recepción
rate(node_network_receive_errs_total[2m]) / rate(node_network_receive_packets_total[2m])
# Ancho de banda entrante
rate(node_network_receive_bytes_total[5m])
Diseño de umbrales para alertas de infraestructura
Un umbral fijo no es válido para todos los entornos. Lo recomendable es combinar límites absolutos con proyecciones basadas en tendencias, especialmente para el almacenamiento.
Predición de agotamiento de disco
La siguiente regla activa una alerta cuando el espacio disponible desciende por debajo de un porcentaje crítico y la tendencia indica que se agotará en las próximas horas:
groups:
- name: filesystem-forecast
rules:
- alert: FilesystemExhaustionForecast
expr: |
(
node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} /
node_filesystem_size_bytes{fstype!~"tmpfs|overlay"} * 100 < 15
and
predict_linear(
node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[1h],
2 * 60 * 60
) < 0
and
node_filesystem_readonly{fstype!~"tmpfs|overlay"} == 0
)
for: 30m
labels:
severity: warning
domain: storage
annotations:
summary: "Espacio en filesystem con tendencia a agotarse"
description: "El punto de montaje {{ $labels.mountpoint }} de {{ $labels.instance }} tiene menos del 15 % libre y podría llenarse en 2 horas."
Alerta de memoria baja
- alert: MemoryUnderPressure
expr: |
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) /
node_memory_MemTotal_bytes * 100 > 90
for: 10m
labels:
severity: warning
domain: memory
annotations:
summary: "Presión de memoria detectada"
description: "La instancia {{ $labels.instance }} consume más del {{ printf \"%.2f\" $value }} % de su memoria desde hace 10 minutos."
Saturación de CPU
- alert: CpuSaturationDetected
expr: |
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[2m]))) * 100 > 85
for: 10m
labels:
severity: warning
domain: cpu
annotations:
summary: "Uso elevado de CPU"
description: "La instancia {{ $labels.instance }} mantiene un uso de CPU del {{ printf \"%.2f\" $value }} % durante más de 10 minutos."
Clasificación de alertas por impacto
Definir niveles de severidad claros facilita el enrutamiento y evita que el equipo de operaciones se sature. La siguiente tabla propone un esquema sencillo:
| Severidad | Tiempo de respuesta | Impacto | Ejemplo |
|---|---|---|---|
| Critical | 15 min | Servicio caído o riesgo inminente | Filesystem con más del 95 % de uso, OOM |
| Warning | 1 h | Degradación perceptible | CPU por encima del 85 %, latencia de disco alta |
| Info | 4 h | Riesgo potencial | Tasa de errores de red levemente elevada |
Configuración de Alertmanager para múltiples canales
Alertmanager agrupa, inhibe y envía las alertas. El siguiente archivo muestra cómo separar las notificaciones críticas del resto:
# alertmanager.yml
global:
resolve_timeout: 10m
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alertas@example.com'
smtp_auth_username: 'alertas'
smtp_auth_password: 'secreto'
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 20s
group_interval: 4m
repeat_interval: 6h
receiver: 'default-receiver'
routes:
- match:
severity: critical
receiver: 'oncall-receiver'
repeat_interval: 15m
continue: false
- match:
severity: warning
receiver: 'chat-receiver'
receivers:
- name: 'default-receiver'
email_configs:
- to: 'soporte@example.com'
send_resolved: true
- name: 'oncall-receiver'
webhook_configs:
- url: 'https://pager.example.com/webhook'
send_resolved: true
sms_configs:
- to: '+34600111222'
text: '{{ template "sms.default.text" . }}'
- name: 'chat-receiver'
slack_configs:
- api_url: 'https://hooks.slack.com/services/YYY'
channel: '#ops-alerts'
send_resolved: true
Plantillas de notificación
Personalizar los mensajes mejora la legibilidad y acelera el diagnóstico. A continuación se muestran plentillas para SMS y correo:
templates:
- '/etc/alertmanager/templates/*.tmpl'
{{ define "sms.default.text" }}
[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.alertname }}
Instancias: {{ range .Alerts }}{{ .Labels.instance }} {{ end }}
Resumen: {{ range .Alerts }}{{ .Annotations.summary }}{{ end }}
{{ end }}
{{ define "email.default.html" }}
<html>
<head>
<title>Notificación de alertas</title>
<style>
.critical { background-color: #f8d7da; }
.warning { background-color: #fff3cd; }
.info { background-color: #d1e7dd; }
</style>
</head>
<body>
<h2>Estado: {{ .Status | toUpper }}</h2>
{{ range .Alerts }}
<div class="{{ .Labels.severity }}">
<h3>{{ .Labels.alertname }} - {{ .Labels.severity | toUpper }}</h3>
<p><strong>Instancia:</strong> {{ .Labels.instance }}</p>
<p><strong>Resumen:</strong> {{ .Annotations.summary }}</p>
<p><strong>Descripción:</strong> {{ .Annotations.description }}</p>
<p><strong>Inicio:</strong> {{ .StartsAt.Format "2006-01-02 15:04:05" }}</p>
</div>
{{ end }}
</body>
</html>
{{ end }}
Ejemplo completo de reglas y enrutamiento
El siguiente archivo reúne varias alertas de infraestructura y las organiza por tipo de recurso:
# infra_alerts.yml
groups:
- name: node-resource
interval: 30s
rules:
- alert: DiskAlmostFull
expr: |
(node_filesystem_size_bytes{fstype!="tmpfs"} -
node_filesystem_avail_bytes{fstype!="tmpfs"}) /
node_filesystem_size_bytes{fstype!="tmpfs"} * 100 > 95
for: 5m
labels:
severity: critical
team: infrastructure
annotations:
summary: "Filesystem casi lleno"
description: "{{ $labels.instance }}: {{ $labels.mountpoint }} al {{ printf \"%.2f\" $value }} % de capacidad."
- alert: MemoryExhaustionRisk
expr: |
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) /
node_memory_MemTotal_bytes * 100 > 95
for: 5m
labels:
severity: critical
team: infrastructure
annotations:
summary: "Riesgo de agotamiento de memoria"
description: "{{ $labels.instance }} utiliza el {{ printf \"%.2f\" $value }} % de memoria."
- alert: LoadAboveCores
expr: |
node_load1 / count without(cpu, mode) (node_cpu_seconds_total{mode="idle"}) > 2.5
for: 15m
labels:
severity: warning
team: infrastructure
annotations:
summary: "Carga por núcleo elevada"
description: "{{ $labels.instance }} mantiene una carga de {{ printf \"%.2f\" $value }} por núcleo."
- name: node-network
interval: 60s
rules:
- alert: NetworkReceiveErrors
expr: |
rate(node_network_receive_errs_total[2m]) /
rate(node_network_receive_packets_total[2m]) > 0.02
for: 20m
labels:
severity: warning
team: network
annotations:
summary: "Tasa de errores de red elevada"
description: "La interfaz {{ $labels.device }} de {{ $labels.instance }} presenta una tasa de error de {{ printf \"%.4f\" $value }}."
- alert: DiskIOSaturation
expr: |
rate(node_disk_io_time_seconds_total[5m]) > 0.75
for: 25m
labels:
severity: warning
team: storage
annotations:
summary: " saturación de E/S en disco"
description: "El disco {{ $labels.device }} de {{ $labels.instance }} está ocupado más del {{ printf \"%.2f\" ($value * 100) }} % del tiempo."
Enrutamiento con inhibición
Para evitar ruido, se puede inhibir una alerta de advertencia cuando ya existe una crítica para el mismo recurso:
# route.yml
route:
receiver: 'default-receiver'
group_by: ['alertname', 'cluster', 'instance']
routes:
- receiver: 'critical-oncall'
match:
severity: critical
team: infrastructure
continue: false
- receiver: 'warning-ops-chat'
match:
severity: warning
team: infrastructure
continue: false
- receiver: 'network-team'
match:
team: network
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'cluster', 'instance']
Estrategias avanzadas de alertado
Umbrales dinámicos con percentiles históricos
En lugar de valores fijos, se pueden comparar las métricas actuales contra percentiles de datos históricos. La siguiente expresión dispara cuando el espacio libre cae por debajo del 80 % del percentil 10 de la última semana:
(
node_filesystem_avail_bytes / node_filesystem_size_bytes * 100
) < (
quantile_over_time(0.1,
(node_filesystem_avail_bytes / node_filesystem_size_bytes * 100)[7d]
) * 0.8
)
Agregación y reducción de ruido
Agrupar por instancia y servicio evita recibir una notificación por cada etiqueta. También es útil añadir una cláusula for suficientemente larga para filtrar picos momentáneos.
Optimización de la evaluación y las consultas
Separar los grupos por frecuencia de evaluación mejora el rendimiento de Prometheus:
# prometheus.yml
global:
evaluation_interval: 1m
rule_files:
- "infra_alerts.yml"
groups:
- name: critical-fast
interval: 15s
rules: []
- name: normal
interval: 1m
rules: []
Para reducir la cardinalidad de las consultas, agregar por instancia y modo antes de aplicar la función de tasa:
# Antes: alta cardinalidad
rate(node_cpu_seconds_total[5m])
# Después: agregación previa
avg by(instance, mode) (rate(node_cpu_seconds_total[5m]))