Вижу линукс, не вижу препятствий
Вижу линукс, не вижу препятствий

Всем привет! Меня зовут Анатолий Зотов, я системный архитектор SOC в RED Security. Как центр мониторинга и реагирования на кибератаки, мы должны быстро и внятно видеть, что происходит с железом и ОС на хостах: не кончается ли место на диске, не улетела ли память и не уперся ли CPU в потолок. Как это реализовать, да еще и безопасно?

Когда мы только приступали к задаче, первой мыслью, конечно, был Zabbix, как и у половины планеты. Но у нас никто не горел желанием поднимать еще одну систему, раскатывать агенты, подбирать шаблоны и разбираться с нюансами. Времени, как обычно, не завезли. А вот Grafana у нас уже была, поэтому я подумал — а что, если просто использовать то, что уже есть? Так я и наткнулся на связку «node_exporter → Prometheus → Grafana».

Эта статья для тех, кто хочет быстро начать мониторить хосты и не готов тратить вечность на внедрение тяжелой системы, особенно если Grafana уже живет в вашей инфраструктуре. Если Grafana и Prometheus у вас еще нет, то дополнительно расскажу быстрый старт через Docker Compose.

Гайд будет о том, как: 

  • поставить на каждый хост node_exporter (один бинарник);

  • подключить Prometheus, который собирает метрики с хостов и хранит их в своей TSDB;

  • использовать Grafana для визуализации и алертов;

  • опционально включить TLS или mTLS, чтобы метрики не были доступны «всем, кто нашел порт 9100».

Подойдет или не подойдет, вот в чем вопрос…

Сразу отмечу границы связки «node_exporter → Prometheus → Grafana». Она хорошо зайдет, если вам нужно закрыть базовые метрики хостов: CPU, RAM, диск и файловые системы, сеть. Также подойдет, если хочется быстро получить понятную картину по инфраструктуре и у вас уже есть Grafana с Prometheus или их легко поднять. 

В наличии должны быть: 

  • Linux + systemd на целевых хостах;

  • OpenSSL (на хостах и на машине, где выпускаете сертификаты);

  • сетевой доступ Prometheus → хосты по 9100/tcp;

  • желательно, чтобы firewall разрешал 9100 только от Prometheus.

Но как единственная система мониторинга она не потянет, если вам нужны сложные проверки приложений и бизнес-логики из «коробки», автоматический discovery всего без настройки или централизованное управление конфигами агентов без вашей автоматизации. Придется либо дополнять стек, либо смотреть в сторону более «тяжелых» решений.

Если решение вам подходит и требования учтены — можно идти дальше. 

Схема и поток данных

В связке есть три основных компонента:

  • на каждом Linux-хосте работает node_exporter — он собирает системные метрики и отдает их по HTTP или HTTPS;

  • Prometheus регулярно ходит к хостам на эндпоинт /metrics, забирает данные по scrape-модели и сохраняет их в своей TSDB;

  • Grafana подключается к Prometheus и отвечает за визуализацию, дашборды и алертинг.

Если нужно шифрование и взаимная аутентификация, добавляется четвертый элемент — CA (центр сертификации), где выпускаются и управляются сертификаты для компонентов. 

После можно выбрать уровень защиты канала в зависимости от требований и того, сколько времени вы готовы потратить на настройку. Тут три варианта: 

  • HTTP —  быстрый старт и минимум действий, но метрики могут утечь, если порт окажется доступен не там, где планировалось;

  • HTTPS c TLS — трафик уже зашифрован, однако любой, кто оказался внутри сети, все еще сможет забирать метрики, если нет дополнительной авторизации;

  • HTTPS c mTLS — здесь node_exporter отдает метрики только для клиентов с сертификатом, подписанным вашим CA. Для внутренней инфраструктуры очень практично.

В статье покажу вариант с mTLS (рекомендую), а также дам короткую инструкцию «как сделать HTTP», если вам нужна максимально быстрая установка без лишней возни. Перейдем к главному.

Шаг 0. Скачиваем node_exporter

Начнем с установки самого node_exporter. Актуальные релизы лежат на GitHub в репозитории Prometheus. По установке все стандартно: выбираем нужную версию и архитектуру под ваш хост, скачиваем архив, распаковываем и кладем бинарник в /usr/local/bin. Ниже покажу пример для linux-amd64:

# пример: linux-amd64
VERSION="1.10.2"
ARCH="linux-amd64"
curl -L -o /tmp/node_exporter.tar.gz "https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/node_exporter-${VERSION}.${ARCH}.tar.gz"
tar -xzf /tmp/node_exporter.tar.gz -C /tmp
sudo install -m 0755 "/tmp/node_exporter-${VERSION}.${ARCH}/node_exporter" /usr/local/bin/node_exporter
rm -rf "/tmp/node_exporter-${VERSION}.${ARCH}" /tmp/node_exporter.tar.gz

Если у хостов нет выхода в интернет — схема та же, просто скачайте архив на сервере с доступом наружу и перенесите его внутрь контура любым привычным способом.

Шаг 1. Готовим пользователя и каталоги

Дальше создаем отдельного системного пользователя под сервис: делаем группу, пользователя без shell-доступа и домашнего каталога, затем готовим директорию для конфигурации:

sudo groupadd -r node_exporter
sudo useradd -r -s /usr/sbin/nologin -g node_exporter -M node_exporter
sudo mkdir -p /etc/node_exporter
sudo chown root:node_exporter /etc/node_exporter
sudo chmod 750 /etc/node_exporter

Так мы ограничиваем права: конфигурацию может менять только root, а сам сервис получает доступ через свою группу.

Сам бинарник лучше оставить владельцем root:root, чтобы сервисный пользователь не мог его подменить. Это простой шаг, который закрывает лишний вектор риска.

sudo chown root:root /usr/local/bin/node_exporter
sudo chmod 755 /usr/local/bin/node_exporter

Шаг 2. Настраиваем CA и сертификаты (TLS/mTLS)

Если шифрование вам не нужно, можно сразу переходить к настройке systemd и запуску в HTTP-режиме.

Самый простой HTTP-режим без TLS (опционально) 

Если задача — запустить мониторинг максимально быстро и без шифрования, можно обойтись без --web.config.file и включить обычный HTTP. В systemd это будет выглядит так:

***
ExecStart=/usr/local/bin/node_exporter --web.listen-address=:9100
***

В таком режиме обязательно ограничьте доступ к порту 9100 через firewall и разрешите подключения только со стороны Prometheus — иначе метрики будут доступны всем, кто видит этот хост в сети.

Однако я рекомендую сценарий с mTLS, когда мы шифруем канал и добавляем взаимную аутентификацию. 

Вариант с TLS/mTLS

2.1. Создаем свой CA-сертификат и ключ (один раз)

На контроллере CA или админ-хосте генерируем корневой ключ и самоподписанный сертификат центра сертификации:

На контроллере/админ-хосте:

openssl genrsa -out ca.key 4096
chmod 600 ca.key
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/C=XX/ST=State/L=City/O=Example/OU=Monitoring/CN=PromCA" -out ca.crt
chmod 644 ca.crt

Важно: приватный ключ ca.key никуда за пределы контроллера не уходит. На хостах нужен только публичный ca.crt.

2.2. Делаем сертификат для node_exporter (на каждый хост)

Теперь для каждого хоста выпускаем отдельный серверный сертификат. Сначала на самом хосте генерируем приватный ключ и CSR (не забудьте указать реальные DNS-имя и IP-адрес):

sudo openssl genrsa -out /etc/node_exporter/node_exporter.key 4096

sudo chmod 600 /etc/node_exporter/node_exporter.key

HOST_FQDN="$(hostname -f)" # если делаете на контроллере замените

HOST_IP="192.0.2.10"   # пример, замените

sudo openssl req -new   -key /etc/node_exporter/node_exporter.key   -subj "/CN=${HOST_FQDN}"   -addext "subjectAltName = DNS:${HOST_FQDN}, IP:${HOST_IP}"   -addext "extendedKeyUsage = serverAuth"   -out /etc/node_exporter/node_exporter.csr

CSR переносим на контроллер и подписываем нашим CA:

openssl x509 -req -in node_exporter.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out node_exporter.crt -days 3650 -sha256 -copy_extensions copy

После подписи на хосте должно остаться три файла:

  • /etc/node_exporter/node_exporter.key — приватный ключ (он уже создан и остаётся на хосте);

  • /etc/node_exporter/node_exporter.crt — серверный сертификат;

  • /etc/node_exporter/ca.crt — публичный сертификат вашего CA.

2.3. Выпускаем клиентский сертификат Prometheus (нужен для mTLS)

Для mTLS Prometheus тоже должен предъявлять сертификат при подключении к node_exporter. Для этого выпускаем клиентский сертификат с extendedKeyUsage=clientAuth . На контроллере:

openssl genrsa -out prometheus.key 4096

chmod 600 prometheus.key

openssl req -new -key prometheus.key -subj "/CN=prometheus" -out prometheus.csr

cat >prometheus_ext.cnf <<'EOF'

extendedKeyUsage=clientAuth

keyUsage=digitalSignature

EOF

openssl x509 -req   -in prometheus.csr   -CA ca.crt   -CAkey ca.key   -CAcreateserial   -out prometheus.crt   -days 3650 -sha256   -extfile prometheus_ext.cnf

rm -f prometheus_ext.cnf

Эти файлы затем понадобятся в scrape_config Prometheus: ca.crt, prometheus.crt, prometheus.key. На этом этапе у нас подготовленно все необходимое для защищённого соединения между Prometheus и node_exporter.

2.4 Включаем TLS/mTLS в node_exporter

Теперь осталось включить TLS на стороне node_exporter и, при необходимости, потребовать клиентский сертификат от Prometheus для mTLS. Для этого создаём конфигурационный файл /etc/node_exporter/web-config.yml:

tls_server_config:
  cert_file: "/etc/node_exporter/node_exporter.crt"
  key_file: "/etc/node_exporter/node_exporter.key"
  # Ниже как раз наш mTLS: требуем клиентский сертификат Prometheus, 
  # если node_exporter не будет требовать клиенсткий серт, то подключится сможет любой!
  client_ca_file: "/etc/node_exporter/ca.crt"
  client_auth_type: "RequireAndVerifyClientCert"

Здесь node_exporter использует свой сертификат для TLS и проверяет, что клиент (Prometheus) предъявляет сертификат, подписанный вашим CA. Дальше аккуратно выставляем права на файлы, чтобы приватные ключи не оказались доступны лишним пользователям:

sudo chown node_exporter:node_exporter /etc/node_exporter/node_exporter.key /etc/node_exporter/node_exporter.crt

sudo chmod 600 /etc/node_exporter/node_exporter.key /etc/node_exporter/node_exporter.crt

sudo chown root:node_exporter /etc/node_exporter/ca.crt /etc/node_exporter/web-config.yml

sudo chmod 644 /etc/node_exporter/ca.crt

sudo chmod 640 /etc/node_exporter/web-config.yml

Шаг 3. Создаем systemd-сервис

Теперь оформим node_exporter как обычный systemd-сервис. Создаем файл /etc/systemd/system/node_exporter.service со всем содержимым:

[Unit]

Description=Prometheus Node Exporter

After=network-online.target

Wants=network-online.target

[Service]

User=node_exporter

Group=node_exporter

ExecStart=/usr/local/bin/node_exporter  --web.listen-address=:9100   --web.config.file=/etc/node_exporter/web-config.yml

Restart=on-failure

RestartSec=5s

NoNewPrivileges=true

PrivateTmp=true

ProtectHome=true

ProtectSystem=full

[Install]

WantedBy=multi-user.target

Перечитываем конфигурацию systemd и запускаем сервис:

sudo systemctl daemon-reload

sudo systemctl enable --now node_exporter

sudo systemctl status node_exporter --no-pager

На этом этапе node_exporter уже установлен и запущен. Дальше остается настроить Prometheus, чтобы он начал забирать метрики.

Шаг 4. Проверяем доступность метрик с Prometheus

Перед тем как подключать все в конфиг, убеждаемся, что Prometheus действительно может достучаться до node_exporter. Проверяем это с хоста, где работает Prometheus. Если у вас включены TLS и mTLS, запрос должен выглядеть так:

curl --cacert ca.crt --cert prometheus.crt --key prometheus.key   https://192.0.2.10:9100/metrics

Если всё настроено корректно, вы увидите большой текстовый вывод с метриками. Если TLS/mTLS не используются, достаточно обычного HTTP:

curl http://192.0.2.10:9100/metrics

Важно: если mTLS включен, но не дает --cert и --key, сервер обязан отказать в подключении. Это простой способ проверить, что проверка клиентского сертификата действительно работает.

Шаг 5. Разворачиваем Grafana и Prometheus через Docker Compose

Если Prometheus и Grafana уже есть в инфраструктуре, этот шаг можно пропустить. Ниже — минимальный вариант, который можно развернуть за несколько минут.

5.1. Собираем структуру директорий

Я обычно использую такую структуру:

monitoring/
  docker-compose.yml
  prometheus/
    prometheus.yml
    targets/
      hosts.yml
    pki/
      ca.crt
      prometheus.crt
      prometheus.key

Если используете TLS/mTLS, в prometheus/pki/ должны лежать три файла: ca.crt, prometheus.crt, prometheus.key.

5.2. Готовим prometheus.yml

Создайте файл monitoring/prometheus/prometheus.yml и положите в него конфигурацию ниже. Здесь три «джоба»: сам Prometheus, метрики Grafana и node_exporter. Список хостов для node_exporter вынесен в отдельный targets-файл, чтобы добавлять и убирать хосты без правок основного конфига: 

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["prometheus:9090"]
  - job_name: "grafana"
    metrics_path: /metrics
    static_configs:
      - targets: ["grafana:3000"]
  - job_name: "node_exporter"
    scheme: https
    file_sd_configs:
      - files:
          - /etc/prometheus/targets/hosts.yml
    tls_config:
      ca_file: /etc/prometheus/pki/ca.crt
      cert_file: /etc/prometheus/pki/prometheus.crt
      key_file: /etc/prometheus/pki/prometheus.key
      insecure_skip_verify: false

Если TLS не используется, логика та же: меняете scheme: https на scheme: http и удаляете блок tls_config. Все остальное можно оставить как есть. 

5.3. Заполняем targets-файл

Теперь создайте файл monitoring/prometheus/targets/hosts.yml. В нем перечисляем хосты с node_exporter и, если нужно, сразу добавляем лейблы:

- targets:
    - "192.0.2.10:9100"
    - "192.0.2.11:9100"
  labels:
    group: "Automation"
    environment: "prod"

Эти лейблы потом удобно использовать в фильтрах Grafana и в алертах.

5.4. Поднимаем стек через docker-compose.yml

Файл monitoring/docker-compose.yml лежит в корне каталога monitoring/. Вводим:  

services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      GF_USERS_ALLOW_SIGN_UP: "false"
      GF_AUTH_ANONYMOUS_ENABLED: "false"
      GF_METRICS_ENABLED: "true"
    volumes:
      - grafana-data:/var/lib/grafana
    networks: [ monitoring ]
    ports:
      - "3000:3000"
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=15d"
      - "--web.enable-lifecycle"
    volumes:
      - ./prometheus:/etc/prometheus:ro
      - prometheus-data:/prometheus
    networks: [ monitoring ]
    ports:
      - "9090:9090"
networks:
  monitoring:
volumes:
  grafana-data:
  prometheus-data:

Запускаем через:

docker compose up -d

5.5. Проверяем, что все поднялось

После запуска мы должны увидеть:

Если что-то не поднимается, первым делом смотрим логи:

docker logs grafana
docker logs prometheus

5.6. Перечитываем targets после изменений

Если вы добавили или убрали хосты в monitoring/prometheus/targets/hosts.yml, самый простой вариант — перезаписать конфигурацию Prometheus через reload-эндпоинт:

curl -X POST http://localhost:9090/-/reload

(или можно перезапустить контейнер Prometheus).

Шаг 6. Подключаем Prometheus в Grafana и добавляем дашборды

Когда Prometheus уже собирает метрики, остается подключить его к Grafana. Сначала  интерфейсе Grafana добавляем источник данных:  Connections → Data sources → Add data source → Prometheus. 

Если все развернуто через docker-compose и сервисы в одной сети, в качестве URL указываем:

http://prometheus:9090

После импортируем готовые дашборды для node_exporter. На Grafana.com их много, есть вполне удачные шаблоны с CPU, памятью, дисками и сетью. Дальше уже творческая часть.

Алертинг: заготовки под CPU/RAM/DISK

Здесь оставляю место под ваши «боевые» правила — порог/длительность/labels. Ниже — один пример запроса для CPU, который удобно показывать на графике (и уже потом превращать в alert).

CPU — пример PromQL для процента загрузки + обогащение nodename

(

  1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

) * 100

* on(instance) group_left(nodename)

node_uname_info{job="node_exporter"}

Этот запрос считает процент загрузки CPU по instance и дополнительно подтягивает nodename, чтобы в графике было видно не только адрес, но и имя хоста.

Пороговые значения и длительность лучше подбирать по реальной статистике: сначала несколько дней понаблюдать, а уже потом включать жесткие алерты.

Частые ошибки и важные уточнения 

По опыту, большинство проблем всплывает на этапе TLS/mTLS. Ниже — типовые симптомы и куда смотреть в первую очередь: 

x509: certificate signed by unknown authority

Обычно это говорит о том, что Prometheus не видит ваш ca.crt или использует другой CA. Проверьте путь в tls_config.ca_file и убедитесь, что это тот же сертификат, которым подписан node_exporter.

... doesn't contain any IP SANs

Это значит, что вы скрейпите хост по IP, но в сертификате node_exporter нет IP в subjectAltName. Либо добавьте IP в SAN при выпуске сертификата, либо переходите на скрейп по DNS-имени, которое уже есть в SAN.

mTLS включен, но targets в статусе Down

Чаще всего Prometheus не передает клиентский сертификат. Проверьте, что в scrape_config указаны cert_file и key_file и что сертификат действительно выпущен с extendedKeyUsage=clientAuth и подписан тем же CA, который указан в client_ca_file на стороне node_exporter.

permission denied при чтении ключа

Проверьте владельца и права на /etc/node_exporter/node_exporter.key. И отдельно — от какого пользователя реально стартует сервис. Если это node_exporter, у него должны быть права на чтение ключа.

Команда systemctl status node_exporter — это первый и самый быстрый способ проверить работу сервиса и нет ли очевидных ошибок.

Дополнительно отмечу: 

  • ca.key храните отдельно и максимально аккуратно. В идеале — в секрет-хранилище или вообще офлайн. Утечка CA — это перевыпуск всего.

  • Бинарник node_exporter лучше оставить root:root, чтобы сервисный пользователь не мог его подменить.

  • Ограничьте доступ к 9100/tcp только со стороны Prometheus.

  • Следите за сроком действия сертификатов. Иначе в какой-то момент все «просто перестанет работать», причем обычно в самый неудобный день.

Что бы я сделал следующим шагом

После базового запуска логично двигаться дальше:

  • Автоматизировать выпуск и продление сертификатов — через Ansible, CFSSL, step-ca или то, что уже используется в вашей инфраструктуре.

  • Добавить discovery хостов. Если у вас уже есть file_sd_configs, это хороший старт для интеграции с inventory или CMDB.

  • Настроить полноценные алерты по CPU, RAM и DISK и отправку уведомлений в удобный канал — почту, мессенджер или систему инцидентов.

  • Развернуть реверс-прокси на 80/443 (например, NGINX), чтобы закрыть прямой доступ и не открывать наружу порты 9090 и 3000.

Удачи! И пусть ваши диски не заканчиваются в пятницу вечером 🙂