Алерты node-exporter: сдвиг часов, CPU, память и диск
Published: 2026-02-20
node-exporter — рабочая лошадка мониторинга инфраструктуры Kubernetes. Каждый узел отдаёт сотни метрик хоста, и задача — выбрать из них правильное подмножество для алертинга: достаточно сигнала, но без шума, из-за которого алерты начинают игнорировать. Вот набор, который мы используем в продакшене, с обоснованием каждого порога.
Деплой node-exporter
node-exporter входит в состав kube-prometheus-stack:
yamlvalues:
nodeExporter:
enabled: true
serviceMonitor:
relabelings:
- sourceLabels: [__meta_kubernetes_pod_node_name]
targetLabel: instance
Релейблинг записывает в метку instance имя узла вместо IP:9100. Так описания алертов читаются легче: «Node k3s-worker-1 CPU высокий» вместо «Node 192.168.1.20:9100 CPU высокий».
Сдвиг часов
Дрейф часов опасен: JWT-токены истекают не вовремя, ломается проверка TLS-сертификатов, распределённые системы (etcd, Kafka) ошибаются в упорядочивании событий. Алерт должен прийти до того, как это станет проблемой:
yaml- alert: NodeClockSkew
expr: >
(node_timex_offset_seconds > 1
and deriv(node_timex_offset_seconds[5m]) >= 0)
or
(node_timex_offset_seconds < -1
and deriv(node_timex_offset_seconds[5m]) <= 0)
for: 15m
labels:
severity: warning
service: ne
event: 'Clock Skew |{{$labels.instance}}'
annotations:
description: >-
Сдвиг часов на {{$labels.instance}}: {{$value}}s и растёт 15 мин. Проверьте NTP.
Условие с deriv() гарантирует, что алерт сработает, только когда сдвиг растёт, а не когда NTP уже активно его корректирует. Без deriv() алерт срабатывал бы и тут же отменялся при каждой NTP-коррекции — постоянный флаппинг.
Проверка статуса NTP
bashtimedatectl status
chronyc tracking
ntpq -p
Частые причины: NTP-сервис отключён (например, после восстановления VM из снапшота), файрвол блокирует исходящий UDP 123, NTP-сервер недоступен из сети узла.
Использование памяти
yaml- alert: NodeHighMemoryUsage
expr: >
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 95
for: 10m
labels:
severity: warning
service: ne
event: 'High Memory |{{$labels.instance}}'
annotations:
description: Использование памяти на {{$labels.instance}} выше 95% более 10 мин.
Порог 95%, а не 80%. Linux отдаёт свободную RAM под page cache, поэтому 70–80% занятой памяти — норма. MemAvailable_bytes учитывает освобождаемый кэш. Алертить стоит только на последних 5%, когда в дело вступает OOM killer.
Мониторинг OOM kill
yaml- alert: NodeOOMKill
expr: increase(node_vmstat_oom_kill[5m]) > 0
for: 0s
labels:
severity: high
event: 'OOM Kill |{{$labels.instance}}'
annotations:
description: OOM kill произошёл на {{$labels.instance}} за последние 5 минут.
for: 0s — срабатывает немедленно. OOM kill уже произошёл, ждать подтверждения незачем.
Использование CPU
yaml- alert: NodeHighCpuUsage
expr: >
100 - (avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[15m])
) * 100) > 95
for: 10m
labels:
severity: warning
service: ne
event: 'High CPU |{{$labels.instance}}'
annotations:
description: Использование CPU на {{$labels.instance}} выше 95% более 10 мин.
rate(node_cpu_seconds_total{mode="idle"}[15m]), усреднённый по всем CPU, даёт долю простоя. Окно 15 минут сглаживает кратковременные всплески (пакетные задачи, паузы GC).
CPU steal (для VM)
yaml- alert: NodeHighCpuSteal
expr: >
avg by (instance) (
rate(node_cpu_seconds_total{mode="steal"}[15m])
) * 100 > 10
for: 10m
labels:
severity: warning
event: 'CPU Steal |{{$labels.instance}}'
annotations:
description: CPU steal на {{$labels.instance}} выше 10%. Гипервизор overcommit CPU на этом хосте.
CPU steal выше 10% даёт непредсказуемые задержки. Если он держится постоянно, виртуальную машину стоит перенести на менее загруженный физический хост.
Использование диска по файловым системам
yaml- alert: NodeFilesystemUsageHigh
expr: >
max by (instance, device) (
(
node_filesystem_size_bytes{fstype=~"ext.?|xfs"}
- node_filesystem_free_bytes{fstype=~"ext.?|xfs"}
) * 100
/
(
node_filesystem_avail_bytes{fstype=~"ext.?|xfs"}
+ node_filesystem_size_bytes{fstype=~"ext.?|xfs"}
- node_filesystem_free_bytes{fstype=~"ext.?|xfs"}
)
) > 95
for: 10m
labels:
severity: warning
service: ne
event: 'High Disk |{{$labels.instance}}|{{$labels.device}}'
annotations:
description: Устройство {{$labels.device}} на {{$labels.instance}} заполнено более чем на 95%.
fstype=~"ext.?|xfs" исключает tmpfs, overlay и виртуальные ФС, которые создают постоянный шум.
Прогноз заполнения диска
yaml- alert: NodeDiskFillingSoon
expr: >
predict_linear(
node_filesystem_avail_bytes{fstype=~"ext.?|xfs"}[1h],
4 * 3600
) < 0
for: 30m
labels:
severity: high
event: 'Disk Filling |{{$labels.instance}}|{{$labels.device}}'
annotations:
description: Диск {{$labels.device}} на {{$labels.instance}} заполнится менее чем за 4ч при текущей скорости записи.
inotify watches
На узлах со множеством .NET- или Node.js-приложений лимит inotify watches быстро исчерпывается:
yaml- alert: NodeInotifyWatchesHigh
expr: node_filesystem_files_free{mountpoint="/"} < 100000
for: 5m
labels:
severity: warning
event: 'Low Inotify |{{$labels.instance}}'
annotations:
description: Свободных inotify watches на {{$labels.instance}} меньше 100k. Увеличьте fs.inotify.max_user_watches.
Исправление:
bash# Временно
sysctl fs.inotify.max_user_watches=1048576
# Постоянно
echo "fs.inotify.max_user_watches=1048576" >> /etc/sysctl.d/99-inotify.conf
sysctl -p /etc/sysctl.d/99-inotify.conf
Сетевые ошибки
yaml- alert: NodeNetworkErrors
expr: >
rate(node_network_receive_errs_total{device!~"lo|veth.*|cilium.*"}[5m]) > 10
or
rate(node_network_transmit_errs_total{device!~"lo|veth.*|cilium.*"}[5m]) > 10
for: 5m
labels:
severity: warning
event: 'Network Errors |{{$labels.instance}}|{{$labels.device}}'
annotations:
description: Сетевые ошибки на {{$labels.instance}} интерфейс {{$labels.device}}.
device!~"lo|veth.*|cilium.*" исключает виртуальные интерфейсы, которые показывают ложные ошибки в некоторых версиях ядра.
Итоговый PrometheusRule
yamlapiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: node-exporter-alerts
namespace: observability
labels:
release: kube-prom-stack
spec:
groups:
- name: node-exporter
interval: 60s # 1 мин достаточно для метрик хоста
rules:
# ... все правила выше
interval: 60s для группы node-exporter — метрики хоста не нуждаются в вычислении каждые 30 секунд. По сравнению с интервалом 30s нагрузка на Prometheus вдвое ниже.