Когда нужно мониторить десяток нод, а не сотню — поднимать полноценный observability-стек с Loki, Tempo, Mimir и тремя репликами Prometheus — оверкилл. У меня есть отдельная VPS с 1 vCPU и 2 GB RAM, на которой крутится Prometheus + Grafana + alertmanager и собирает метрики с 14 серверов. Работает второй год, ни разу не падала по OOM.
Стек
Всё в docker compose, ничего из родных пакетов. Хост-система — Debian 12, docker 27.3, compose v2.
services:
prometheus:
image: prom/prometheus:v2.55.1
container_name: prometheus
restart: unless-stopped
user: "65534:65534"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./rules:/etc/prometheus/rules:ro
- prom-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=45d'
- '--storage.tsdb.retention.size=8GB'
- '--web.enable-lifecycle'
- '--storage.tsdb.wal-compression'
ports:
- "127.0.0.1:9090:9090"
mem_limit: 900m
grafana:
image: grafana/grafana-oss:11.3.0
container_name: grafana
restart: unless-stopped
volumes:
- grafana-data:/var/lib/grafana
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GF_PASS}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
ports:
- "127.0.0.1:3000:3000"
mem_limit: 400m
alertmanager:
image: prom/alertmanager:v0.27.0
container_name: alertmanager
restart: unless-stopped
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- am-data:/alertmanager
ports:
- "127.0.0.1:9093:9093"
mem_limit: 128m
volumes:
prom-data:
grafana-data:
am-data:
mem_limit тут не для красоты — на 2 ГБ хосте без них Prometheus при перезаливе блоков спокойно сжирает 1.5 ГБ и убивает grafana по OOM.
Конфиг Prometheus
Главное — не пихать в один Prometheus сотни таргетов, и не лить туда метрики с интервалом 5 секунд. По дефолту я ставлю 30s и не страдаю.
global:
scrape_interval: 30s
evaluation_interval: 30s
external_labels:
site: lab1
rule_files:
- /etc/prometheus/rules/*.yml
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
scrape_configs:
- job_name: 'node'
static_configs:
- targets:
- 10.8.0.2:9100
- 10.8.0.3:9100
- 10.8.0.4:9100
labels:
env: prod
relabel_configs:
- source_labels: [__address__]
regex: '([^:]+):.*'
target_label: instance
replacement: '$1'
- job_name: 'nginx'
metrics_path: /metrics
static_configs:
- targets:
- 10.8.0.2:9113
Доступ к нодам — через WireGuard (10.8.0.0/24), наружу 9100 порт не торчит. Это, наверное, самое важное правило: никаких public-таргетов без TLS+basic-auth.
node_exporter без лишнего
На таргетах ставлю node_exporter с урезанным набором коллекторов — иначе сжирает CPU на старых VPS.
node_exporter \
--collector.disable-defaults \
--collector.cpu \
--collector.diskstats \
--collector.filesystem \
--collector.loadavg \
--collector.meminfo \
--collector.netdev \
--collector.netstat \
--collector.systemd \
--collector.uname \
--collector.vmstat \
--collector.time \
--web.listen-address=10.8.0.2:9100
Без --collector.disable-defaults он по дефолту тянет ещё штук тридцать модулей, включая textfile, mdadm, bonding — большинство из которых на VPS бесполезны.
Алёрты, которые реально нужны
Не плодите алёрты на каждую метрику. У меня для 14 нод всего 9 правил, и из них в среднем срабатывает 1-2 в неделю.
groups:
- name: basic
rules:
- alert: NodeDown
expr: up{job="node"} == 0
for: 3m
labels:
severity: critical
annotations:
summary: "Нода {{ $labels.instance }} не отвечает"
- alert: DiskFull
expr: |
(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}
/ node_filesystem_size_bytes) * 100 < 10
for: 10m
labels:
severity: warning
- alert: HighLoad
expr: node_load5 > (count by (instance)(node_cpu_seconds_total{mode="idle"})) * 1.5
for: 15m
- alert: OOMKillRecent
expr: increase(node_vmstat_oom_kill[10m]) > 0
labels:
severity: critical
Для алёртов в Telegram использую самописный webhook на Go в 80 строк — alertmanager-bot мне показался жирноватым.
Grafana — минимум дашбордов
Не качайте 50 готовых дашбордов с grafana.com. Качайте Node Exporter Full (id 1860), удаляйте оттуда половину панелей, оставляйте то, что реально смотрите. Каждая лишняя панель — лишний PromQL-запрос при каждом рефреше, и через год Prometheus начнёт упираться в CPU на ровном месте.
Резервные копии
Снапшот Prometheus делается одной командой:
curl -XPOST http://127.0.0.1:9090/api/v1/admin/tsdb/snapshot
Снапшот лежит в data/snapshots/, заливаем в S3-совместимое хранилище через restic. Для grafana — просто rsync /var/lib/grafana/grafana.db. Дашборды я ещё держу в git как JSON, через grafana-cli или ручной экспорт.
Итог
Реальный observability на одной VPS — это нормально для парка до 20-30 нод. Когда упрётесь в 1.5-2 ГБ памяти под Prometheus — пора смотреть в сторону VictoriaMetrics или remote_write в централизованное хранилище. До этого момента — не усложняйте.