OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

node_exporter textfile collector: практические метрики на Bash и Python

Textfile collector в node_exporter позволяет быстро добавить кастомные метрики в Prometheus без собственного HTTP‑сервиса. Разбираем включение, формат, атомарную запись .prom‑файлов, cron с flock. Примеры на Bash и Python, типичные ошибки и отладку.
node_exporter textfile collector: практические метрики на Bash и Python

Textfile collector в node_exporter — один из самых быстрых путей добавить свои метрики в Prometheus. Он читает файлы в формате Prometheus exposition из директории и подмешивает их в выдачу /metrics самого экспортера. Это удобно для скриптов на Bash/Python, одноразовых измерений и интеграций с системными утилитами, где не хочется поднимать отдельный HTTP‑сервис.

Когда уместен textfile collector

Классический экспортер — это демон с HTTP-эндпоинтом. Но часто нужно просто: «собери вывод одной команды и отдай как метрику». Для такого сценария textfile collector идеален:

  • минимальные зависимости: достаточно положить файл в директорию и соблюсти формат;
  • надёжность: зависшая команда не валит HTTP-стек;
  • безопасность: нет нового TCP-порта, всё делает уже работающий node_exporter;
  • простота деплоя: cron или systemd-timer.

Типичные кейсы: количество доступных обновлений пакетов, срок истечения SSL‑сертификата домена, число банов в Fail2ban, контроль версии деплоя, наличие backup‑файлов, кастомные бизнес‑счётчики с локальной машины.

Включаем textfile collector

Проверьте, с какими флагами запущен node_exporter. Нужны два флага:

  • --collector.textfile — включает коллектор;
  • --collector.textfile.directory=/var/lib/node_exporter/textfile — директория с файлами метрик.

Создадим директорию и выдадим права пользователю, под которым работает экспортер (часто node_exporter):

sudo install -o node_exporter -g node_exporter -m 0750 -d /var/lib/node_exporter/textfile

Добавим drop-in к unit-файлу systemd, чтобы не переписывать пакетный сервис (дополните своими флагами, если они нужны):

sudo mkdir -p /etc/systemd/system/node_exporter.service.d
sudo tee /etc/systemd/system/node_exporter.service.d/textfile.conf > /dev/null << 'EOF'
[Service]
Environment=TEXTFILE_DIR=/var/lib/node_exporter/textfile
ExecStart=
ExecStart=/usr/bin/node_exporter --collector.textfile --collector.textfile.directory=${TEXTFILE_DIR}
EOF
sudo systemctl daemon-reload
sudo systemctl restart node_exporter
sudo systemctl status node_exporter

Проверьте, что коллектор активен и директория принята:

curl -s localhost:9100/metrics | grep ^node_textfile

Вы увидите метрики вида node_textfile_*, в том числе node_textfile_scrape_error и node_textfile_mtime_seconds. На собственном сервере или на наших VDS такой подход особенно удобен: минимум движущихся частей и быстрый онбординг.

Формат метрик: короткий ликбез

Файлы должны быть в формате Prometheus exposition (текстовый, одна метрика на строку). Рекомендуется расширение .prom. Пример:

# HELP sys_updates_available Количество доступных обновлений пакетов
# TYPE sys_updates_available gauge
sys_updates_available 3

# HELP cert_expiry_days Сколько дней до окончания SSL-сертификата
# TYPE cert_expiry_days gauge
cert_expiry_days{host="example.com"} 27

Базовые правила:

  • имя — латиница, цифры, _; начинать с буквы; используйте общий префикс (sys_, deploy_ и т.п.);
  • единицы в имени: _seconds, _bytes, _ratio, _count, _days;
  • метки в {k="v"}, строки экранируйте: \\ и \";
  • строки # HELP и # TYPE указывайте один раз на имя метрики;
  • избегайте высокой кардинальности меток.
Метрики пишите атомарно: сначала во временный файл, затем перемещайте в рабочий. Так node_exporter никогда не прочитает «наполовину записанный» файл.
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Bash: готовые рецепты

1) Количество доступных обновлений (APT/DNF)

Скрипт определяет пакетный менеджер и пишет число доступных обновлений. Работает быстро, не блокируя длительными апдейтами метаданных.

#!/usr/bin/env bash
set -euo pipefail

DIR="/var/lib/node_exporter/textfile"
OUT="${DIR}/sys_updates.prom"
TMP="$(mktemp)"

count_updates_apt() {
  if command -v apt-get > /dev/null; then
    apt-get -s upgrade 2>/dev/null | awk '/^[0-9]+ upgraded/{print $1}'
    return 0
  fi
  return 1
}

count_updates_dnf() {
  if command -v dnf > /dev/null; then
    dnf check-update -q 2>/dev/null | awk 'BEGIN{c=0} /^[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\s/ {c++} END{print c}'
    return 0
  fi
  if command -v yum > /dev/null; then
    yum check-update -q 2>/dev/null | awk 'BEGIN{c=0} /^[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\s/ {c++} END{print c}'
    return 0
  fi
  return 1
}

UPDATES=0
if count_updates_apt >"${TMP}.cnt"; then
  UPDATES="$(cat "${TMP}.cnt")"
elif count_updates_dnf >"${TMP}.cnt"; then
  UPDATES="$(cat "${TMP}.cnt")"
else
  UPDATES=0
fi

{
  echo "# HELP sys_updates_available Количество доступных обновлений пакетов"
  echo "# TYPE sys_updates_available gauge"
  echo "sys_updates_available ${UPDATES}"
} > "${TMP}"

install -o node_exporter -g node_exporter -m 0640 "${TMP}" "${OUT}"
rm -f "${TMP}" "${TMP}.cnt"

Заведите cron с блокировкой, чтобы скрипт не наслаивался:

# /etc/cron.d/sys-updates-metrics
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/15 * * * * root flock -n /run/sys-updates-metrics.lock /usr/local/bin/sys_updates_metrics.sh

Пример cron с flock и Bash-скриптом, записывающим метрики

2) Сколько дней до окончания SSL‑сертификата

Проверим публичный сертификат домена через openssl и дадим метрику _days. Скрипт работает для TCP:443; список доменов хранится рядом. Если вы управляете сертификатами для сайтов — посмотрите наши SSL-сертификаты для автоматизации и единого управления.

#!/usr/bin/env bash
set -euo pipefail
DIR="/var/lib/node_exporter/textfile"
OUT="${DIR}/cert_expiry.prom"
TMP="$(mktemp)"
LIST="/etc/ssl/domains.txt"

now() { date +%s; }

get_expiry_days() {
  local host="$1"
  local exp_ts
  exp_ts="$(echo | openssl s_client -servername "$host" -connect "$host:443" 2>/dev/null | openssl x509 -noout -enddate | sed 's/notAfter=//')"
  if ; then
    echo -1
    return 0
  fi
  local exp_epoch
  exp_epoch="$(date -d "$exp_ts" +%s 2>/dev/null || true)"
  if ; then
    echo -1
    return 0
  fi
  local diff
  diff=$(( (exp_epoch - $(now)) / 86400 ))
  echo "$diff"
}

{
  echo "# HELP cert_expiry_days Сколько дней до окончания SSL-сертификата"
  echo "# TYPE cert_expiry_days gauge"
  while read -r h; do
     && continue
    d="$(get_expiry_days "$h" || echo -1)"
    echo "cert_expiry_days{host=\"$h\"} $d"
  done < "$LIST"
} > "$TMP"

install -o node_exporter -g node_exporter -m 0640 "$TMP" "$OUT"
rm -f "$TMP"

Пример /etc/ssl/domains.txt:

# один домен на строку
example.com
api.example.org

Планируйте обновления чаще раза в сутки, если хотите алерты с запасом времени.

3) Суммарное количество активных банов Fail2ban

Метрика показывает общее число IP в бане во всех jails. Можно добавить метку jail, но следите за кардинальностью. Ниже — агрегированная версия. Если настраиваете почтовые jails, пригодится разбор в материале Fail2ban для Postfix/Dovecot.

#!/usr/bin/env bash
set -euo pipefail
DIR="/var/lib/node_exporter/textfile"
OUT="${DIR}/fail2ban_bans.prom"
TMP="$(mktemp)"

get_total_bans() {
  local total=0
  while read -r jail; do
     && continue
    cnt="$(fail2ban-client status "$jail" 2>/dev/null | awk -F: '/Currently banned/ {gsub(/ /, "", $2); print $2}')"
    cnt="${cnt:-0}"
    total=$(( total + cnt ))
  done < <(fail2ban-client status 2>/dev/null | awk -F: '/Jail list/ {print $2}' | tr "," "\n" | sed 's/ //g')
  echo "$total"
}

TOTAL="$(get_total_bans || echo 0)"
{
  echo "# HELP fail2ban_bans_total Общее число активных банов во всех jails"
  echo "# TYPE fail2ban_bans_total gauge"
  echo "fail2ban_bans_total ${TOTAL}"
} > "$TMP"

install -o node_exporter -g node_exporter -m 0640 "$TMP" "$OUT"
rm -f "$TMP"

Cron: как запускать безопасно

  • используйте flock для эксклюзивного запуска;
  • настраивайте переменную PATH в crontab или скрипте;
  • выполняйте от пользователя, который может писать в директорию textfile (или используйте install -o -g при атомарном перемещении);
  • разводите скрипты по разным .prom-файлам — не мешайте несвязанные метрики.

Шаблон записи в /etc/cron.d/:

*/5 * * * * root flock -n /run/<name>.lock /usr/local/bin/<script>.sh

Если предпочитаете systemd timers — логика та же: один сервис на скрипт, таймер с интервалом, блокировка внутри скрипта.

Python: аккуратно и «правильно»

В Python удобно использовать официальный пакет prometheus_client, у него есть функция write_to_textfile. Она пишет атомарно и правильно формирует HELP/TYPE.

#!/usr/bin/env python3
import argparse
import os
from prometheus_client import CollectorRegistry, Gauge, Counter, write_to_textfile

parser = argparse.ArgumentParser()
parser.add_argument('--out', default='/var/lib/node_exporter/textfile/app_metrics.prom')
args = parser.parse_args()

registry = CollectorRegistry()

# Гейдж: версия релиза и билд-номер как метки
release_info = Gauge('deploy_release_info', 'Текущий релиз приложения', ['project', 'env', 'version'], registry=registry)
release_info.labels(project='site', env='prod', version=os.getenv('APP_VERSION', 'unknown')).set(1)

# Счётчик: миграции БД (пример фикса завершённых миграций)
migrations = Counter('db_migrations_total', 'Сколько миграций БД выполнено', ['project'], registry=registry)
migrations.labels(project='site').inc(0)

write_to_textfile(args.out, registry)

Python-пример с prometheus_client и write_to_textfile

Если зависимости ставить нельзя, можно писать «сырое» содержимое вручную:

#!/usr/bin/env python3
import os
import time
out = '/var/lib/node_exporter/textfile/simple.prom'
content = []
content.append('# HELP app_heartbeat_seconds Время генерации метрик, epoch')
content.append('# TYPE app_heartbeat_seconds gauge')
content.append('app_heartbeat_seconds {}'.format(int(time.time())))

tmp = out + '.tmp'
with open(tmp, 'w', encoding='utf-8') as f:
    f.write('\n'.join(content) + '\n')
os.replace(tmp, out)

Советы для Python-метрик:

  • одна директория — несколько файлов. Придерживайтесь префикса и тематики;
  • группируйте метрики одной темы в одном файле, чтобы HELP/TYPE не дублировались несколькими скриптами;
  • добавляйте таймауты сетевым вызовам и ограничивайте время работы.

Права доступа и безопасность

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

  • директория /var/lib/node_exporter/textfile: 0750, владелец и группа — пользователь экспортера;
  • файлы .prom: 0640;
  • используйте install -o -g -m или chown/chmod после атомарного перемещения.

SELinux: если включён, дайте контекст на директорию аналогично остальным данным экспортера (через semanage fcontext и restorecon в вашей политике). Это за пределами типовой инструкции, но важно для RHEL‑подобных систем.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Атомарность и свежесть файлов

Почему атомарно? node_exporter читает файл целиком. Если вы пишете прямо в целевой путь и в этот момент случился контекстный переключатель, экспортер может прочитать половину файла и выбросить ошибку формата; метрики пропадут, node_textfile_scrape_error станет 1.

Пишите всегда в mktemp, затем install или mv. Для «жизненного цикла» добавьте чистильщик старых файлов, если метрики должны исчезать при остановке скрипта:

# удалять .stale.prom старше 10 минут
find /var/lib/node_exporter/textfile -name '*.stale.prom' -mmin +10 -type f -delete

Вместо удаления можно держать «индикатор свежести» — метрику времени генерации в секундах. В алертах условие: «если индикатор устарел > X минут — тревога».

Типичные ошибки

  • Дублируете # HELP/# TYPE для одного имени в разных файлах. Решение: один файл — одна группа HELP/TYPE, или разные имена/префиксы.
  • Пишете метки с пробелами и без экранирования кавычек. Экранируйте \\ и \".
  • Слишком много меток (кардинальность растёт). Оставляйте стабильные значения: env, project, instance.
  • Скрипт виснет, cron запускает следующий — конфликт. Используйте flock и таймауты у команд.
  • Неверные права на директорию/файлы — экспортер не читает. Смотрите node_textfile_scrape_error, логи сервиса и права.

Отладка и валидация

Проверьте файлы руками:

ls -l /var/lib/node_exporter/textfile
sed -n '1,120p' /var/lib/node_exporter/textfile/sys_updates.prom

Проверьте формат метрик promtool (утилита из Prometheus):

promtool check metrics /var/lib/node_exporter/textfile/sys_updates.prom

Посмотрите в выдачу экспортера, появились ли метрики:

curl -s localhost:9100/metrics | grep -E 'sys_updates_available|cert_expiry_days|fail2ban_bans_total'

Практика именования и единицы измерения

  • Счётчики — *_total и инкрементируйте, а не устанавливайте;
  • Гейджи — текущие значения (температуры, количество банов, апдейты);
  • Единицы — в имени: _seconds, _milliseconds, _bytes, _days;
  • Не добавляйте histogram/summary вручную — textfile не агрегирует наблюдения, используйте gauge/counter.

Алерты для кастомных метрик

Как только метрики появились, добавляйте алерты в правила Prometheus. Вдохновиться можно и нашими примерами правил для Node и Nginx. Простейшие шаблоны:

groups:
- name: custom-textfile-alerts
  rules:
  - alert: ManyPendingUpdates
    expr: sys_updates_available > 50
    for: 2h
    labels:
      severity: warning
    annotations:
      summary: "Много обновлений на хосте"
      description: "Доступно {{ $value }} обновлений. Проверьте репозитории и план обновления."

  - alert: CertificateExpiringSoon
    expr: cert_expiry_days{host!~""} >= 0 and cert_expiry_days <= 14
    for: 1h
    labels:
      severity: critical
    annotations:
      summary: "Сертификат истекает скоро ({{ $labels.host }})"
      description: "Срок истекает через {{ $value }} дней. Обновите сертификат заранее."

  - alert: Fail2banEmpty
    expr: fail2ban_bans_total == 0
    for: 24h
    labels:
      severity: info
    annotations:
      summary: "Fail2ban не банит никого"
      description: "Проверьте, работает ли Fail2ban и актуальны ли jails. Возможно, всё тихо — и это хорошо."

Правила даны как ориентир; адаптируйте пороги и метки под свою инфраструктуру.

Организационные практики

  • Один репозиторий с каталогом scripts/metrics/, стандартизируйте шаблон: логер, таймауты, flock, атомарная запись.
  • Код-ревью даже на «маленькие скрипты» — метрики влияют на алерты, а алерты на ваш сон.
  • Единый стиль имён: <domain>_<subject>_<unit>, например sys_updates_available, cert_expiry_days, deploy_release_info.
  • Версионируйте HELP/TYPE и договоритесь, в каком файле живёт какая группа метрик.

Итог

Textfile collector — «карманный нож» инженера: быстро, надёжно, предсказуемо. Включили коллектор, настроили права, завели пару скриптов — и у вас уже есть полезные сигналы для алертов: апдейты пакетов, срок SSL, статус Fail2ban. Дальше — стандартизируйте подход, добавьте Python там, где хочется типовой сериализации, и не забывайте про атомарную запись и кардинальность меток.

Поделиться статьей

Вам будет интересно

DMARC: rua, p=quarantine и p=reject — как включить без потери доставляемости OpenAI Статья написана AI (GPT 5)

DMARC: rua, p=quarantine и p=reject — как включить без потери доставляемости

Пошаговое руководство для админов: что такое DMARC и агрегированные отчёты rua, как правильно оформить rua=mailto, собрать XML-отч ...
Floating IP для Nginx: keepalived VRRP, healthcheck и быстрый failover OpenAI Статья написана AI (GPT 5)

Floating IP для Nginx: keepalived VRRP, healthcheck и быстрый failover

Пошагово строим отказоустойчивый фронтенд на двух серверах Nginx с общим floating IP. Настраиваем keepalived (VRRP), HTTP health c ...
SYNPROXY в nftables: защита VDS от TCP SYN flood пошагово OpenAI Статья написана AI (GPT 5)

SYNPROXY в nftables: защита VDS от TCP SYN flood пошагово

SYN flood забивает TCP-очереди и conntrack на VDS, съедая CPU. SYNPROXY в nftables отсекает мусор до TCP-стека. Разбираем принцип, ...