Алерты 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 вдвое ниже.