# Monitoring stack — Garage cluster (vps01 + bigbox + vps02) + Vinograd WAN Стек мониторинга для S3-кластера Garage (репликация RF=3, WireGuard 10.8.0.0/24) и внешнего канала связи объекта «Винный город» (провайдер Ростелеком). Расположен на **bigbox** в `/opt/monitoring`. ## Архитектура ``` bigbox (10.8.0.2) ┌─────────────────────────────────────────────────┐ │ prometheus :9090 (host net) │ │ │ scrape ──► garage x3 :3903 (WG admin) │ │ ├──► node-exporter :9100 (bigbox) │ │ ├──► 10.8.0.1:3903 (vps01) │ │ ├──► 10.8.0.4:3903 (vps02) │ │ └──► blackbox :9115 (host net) → /health │ │ │ │ grafana :3001 (dashboards, alerts) │ │ loki :3100 (logs) │ │ promtail ── docker.sock ──► docker logs garage │ └─────────────────────────────────────────────────┘ │ │ ▼ WireGuard 10.8.0.0/24 ▼ caddy (vps02) vps01 (10.8.0.1) grafana.nixg.ru vps02 (10.8.0.4) → 87.242.100.206 → reverse_proxy 10.8.0.2:3001 ``` Прометеус и blackbox-exporter работают в **host network** (иначе не видят WireGuard-подсеть 10.8.0.0/24 хостa). Остальные — в bridge-сети monitoring. ## Сервисы и порты | Сервис | Порт | Доступ | Примечание | |------------------|--------|---------------------|-------------------------------------| | Prometheus | 9090 | локально/WG | host network, retention 30d (TSDB) | | Grafana | 3001 | браузер + публично | 3000 занят gitea → 3001 | | Loki | 3100 | локально | сбор логов, retention 7d | | Promtail | — | docker.sock | docker logs garage и др. | | blackbox-exporter| 9115 | host network | HTTP-пробы /health нод | | node-exporter | 9100 | WG | на каждой ноде (apt) | ## Публичный доступ ``` grafana.nixg.ru → 87.242.100.206 (vps02) → caddy → reverse_proxy 10.8.0.2:3001 ``` - Запись добавлена в `/opt/caddy/Caddyfile` на vps02: `grafana.nixg.ru { reverse_proxy 10.8.0.2:3001 }` - Caddy — docker-контейнер (`network_mode: host`), перезагрузка: `docker exec caddy caddy reload --config /etc/caddy/Caddyfile` - Сертификат Let's Encrypt выпускается автоматически. - Логин: **estorozhenko** (сменён с admin через UI), пароль — задан пользователем. - Read-only доступ: **it@vinogorod.ru** (роль **Viewer**, создан вручную в UI, пароль `1qazXSW2`). Только просмотр дашбордов, без правки. Учётка в `grafana-data` (переживает пересоздание контейнера). ## Garage admin API (метрики) В Garage v2.1 admin API (включая `/metrics` в Prometheus-формате) слушает **отдельный порт** `[admin] api_bind_addr`. На всех трёх нодах прописано: ```toml [admin] api_bind_addr = ":3903" # bigbox 10.8.0.2, vps01 10.8.0.1, vps02 10.8.0.4 admin_token = "..." metrics_token = "..." ``` Проверка метрик: ``` curl -H "Authorization: Bearer TOKEN" http://10.8.0.2:3903/metrics ``` `/health` возвращает 200 при кворуме (≥2 нод), 503 иначе. Порты 3903 и 9100 открыты только для WG-подсети: - vps01: `ufw allow from 10.8.0.0/24 to any port 3903` (и 9100 аналогично) - vps02/bigbox: файрвол не блокирует (policy ACCEPT) ## Запуск ``` cd /opt/monitoring docker compose up -d ``` Пароль Grafana — переменная `GRAFANA_ADMIN_PASSWORD` в `.env` (по умолчанию `admin`). Пользователь Grafana: `estorozhenko` (сменён пользователем вручную). ## Полезные команды ``` # статус кластера docker run --rm -v /opt/garage/garage.toml:/etc/garage.toml:ro \ -v /opt/garage/meta:/var/lib/garage/meta \ --entrypoint /garage dxflrs/garage:v2.1.0 status # метрики одной ноды (Prometheus-формат) curl -s -H "Authorization: Bearer TOKEN" http://10.8.0.2:3903/metrics | head # health curl -s -o /dev/null -w "%{http_code}" http://10.8.0.2:3903/health # таргеты прометея (все должны быть UP) curl http://127.0.0.1:9090/api/v1/targets # принудительная синхронизация mirror'а в gitea curl -X POST -H "Authorization: token GITEA_TOK" \ http://127.0.0.1:3000/api/v1/repos/estorozhenko/monitoring/mirror-sync ``` ## Алерты (prometheus/alerts.yml) | Алерт | Условие | Severity | |--------------------|--------------------------------|----------| | GarageNodeDown | `up{job="garage"} == 0` (2м) | critical | | GarageNoQuorum | `<2` нод up (2м) | critical | | GarageResyncErrors | `block_resync_errored_blocks > 0` (10м) | warning | | GarageNodeUnstable | `cluster_layout_node_connected == 0` (5м) | warning | | TProxyDown | `up{job="tproxy"} == 0` (2м) | critical | | TProxyBackendErrors| `increase(tproxy_backend_dial_failures_total[5m]) > 0` (10м) | warning | | VinogradRostelecomDown | `probe_success{job="vinograd_wan"} == 0` (2м) | critical | Примечание: метрики Garage из admin API (:3903) НЕ имеют префикса `garage_` — это `api_s3_request_counter`, `block_resync_*`, `cluster_*`. Префикс `garage_` только у `garage_build_info`, `garage_local_disk_*`, `garage_replication_factor`. ## Vinograd WAN — внешний канал «Винный город» (Ростелеком) Объект «Винный город» (г. Геленджик, ул. Туристическая, 25), канал Ростелеком (договор Бастион, Static IP). Адреса из «Реестра внешних каналов связи.ods» (закладка «Винный город»): | Адрес | Роль | |-------|------| | 83.239.50.145 | Шлюз (gateway) — поднимается от РТК | | 83.239.50.146 | Наше оборудование (CPE, Static IP, /30) | Мониторинг через **blackbox-exporter (ICMP-проба)** → Prometheus job `vinograd_wan`: - Интервал scrape: **30s** (графики скорости ответа каждые 30 секунд) - Метрики: - `probe_success{job="vinograd_wan"}` — доступность (1/0) - `probe_icmp_duration_seconds{job="vinograd_wan",phase="rtt"}` — RTT, сек - Лейблы `instance`: `vinograd-gw-83.239.50.145`, `vinograd-cpe-83.239.50.146` - Алерт: **VinogradRostelecomDown** (critical, 2м подряд недоступен) - Grafana: дашборд **Vinograd WAN** (RTT ms + availability), панели в фолдере Garage Retention: глобальный 30d (прометеевский TSDB) — данные хранятся минимум неделю, что покрывает требование «хранить неделю» с запасом (жёсткий 7d для одного job требовал бы отдельного инстанса Prometheus). Проверка вручную: ```bash # ICMP-проба через blackbox (debug) curl -s "http://127.0.0.1:9115/probe?target=83.239.50.146&module=icmp&debug=true" # данные в Prometheus curl -sG 'http://127.0.0.1:9090/api/v1/query' \ --data-urlencode 'query=probe_success{job="vinograd_wan"}' ``` > Статус 2026-09-08: шлюз 83.239.50.145 НЕ отвечает на ICMP (probe_success=0, > совпадает с алертом UptimeKuma 08:07 MSK). Оборудование 83.239.50.146 > отвечает ~13ms. Алерт VinogradRostelecomDown в состоянии FIRE до восстановления > канала — это корректное отражение реальной аварии. ## tproxy-server (vps03) — метрики WEB Proxy (этап 6, РЕШЕНО ✅) tproxy-server (Telegram Desktop WEB Proxy) развёрнут на **vps03** (77.67.89.154), admin-эндпоинты на loopback :8081: `/healthz`, `/readyz`, `/metrics` (Prometheus-формат, 13 счётчиков `tproxy_*`). > **2026-09-08: vps03 подключён к WireGuard (10.8.0.3)** — вместо SSH-туннеля. > Админ-эндпоинты tproxy остались на loopback, но туннель `tproxy-tunnel.service` > удалён не был (безвреден, оставлен на всякий случай). Prometheus берёт node-метрики > vps03 напрямую по WG: `10.8.0.3:9100`. - Локальный порт **18081** (не 8081 — тот на bigbox занят ICQ-веб-чатом!) - Prometheus скрейпит `127.0.0.1:18081` (job `tproxy`, host=vps03), но через relabel_configs в prometheus.yml лейбл `instance` заменён на **`vps03:8081`**, чтобы в Grafana легенды показывали hostname, а не IP туннеля. Аналогично relabel сделан для garage (`10.8.0.x:3903` → `vps01|bigbox|vps02:3903`) и node (`10.8.0.x:9100` → hostname). - Дашборд: 3 панели tproxy (sessions/streams, traffic /sec, backend errors) - Алерты: TProxyDown (critical), TProxyBackendErrors (warning) Подробности — в PLAN.md (этап 6) и EXPERIENCE.md. ## Node-дашборды (диски/память/сеть/доступность) — этап 7, РЕШЕНО ✅ На каждую ноду (vps01, vps02, vps03, bigbox) — **отдельный дашборд**, `grafana/dashboards/nodes/node-.json` (uid `node-`, title "Node "). Генерируются скриптом `/tmp/gen_nodes_dash.py` (в git не хранится; при желании перенести — в EXPERIENCE.md). Файлы дашбордов разложены по подпапкам (папки в UI Grafana совпадают): - `grafana/dashboards/` — garage-cluster.json → папка **Garage** - `grafana/dashboards/nodes/` — node-*.json → папка **nodes** - `grafana/dashboards/vinogorod/` — vinograd-wan.json → папка **vinogorod** Панели (все по `{host=""}`, job=node): 1. **Availability** — stat-панель `up{job="node",host=...}` (UP/DOWN) 2. **Disk free (GB)** — `node_filesystem_avail_bytes` (без tmpfs/overlay/squashfs) 3. **Memory available (GB)** — `node_memory_MemAvailable_bytes` 4. **Load** — `node_load1/5/15` 5. **Net RX/TX (bytes/s)** — `rate(node_network_{receive,transmit}_bytes_total[5m])` 6. **Net utilization (%)** — `(rx+tx)*8/speed*100` Физические интерфейсы: vps01=eth0, vps02=enp3s0, vps03=ens1, bigbox=eno1. Контейнерные (docker0/veth*/br-*) не включены. Тонкость node-exporter 1.9: на виртуалках vps01/vps02 `node_network_speed_bytes` = **-125000** (sysfs speed=-1 → умножается на 125000), поэтому для них скорость линка захардкожена **125000000 Б/с (1 Gbit/s)** в формуле утилизации. На физических (vps03/bigbox) используется сама метрика `node_network_speed_bytes` (=1.25e+08). ## Катастрофоустойчивость Проект хранится в **двух** git-репозиториях — на случай поломки bigbox: | Репозиторий | Роль | Адрес | |-------------|------|-------| | **gitverse.ru** (облако) | источник истины | `git@gitverse.ru:kpa39l/monitoring.git` | | **gitea на bigbox** | pull-mirror (8h) | `https://gitea.nixg.ru/estorozhenko/monitoring` | Порядок работы: правки коммитятся и пушутся в gitverse (источник), gitea подтягивает их mirror'ом (каждые 8ч или вручную mirror-sync). Если bigbox сломается — репозиторий остаётся в облаке gitverse. ## VESTI (2026-09-13) Проект /opt/vesti: web-интерфейс (:8400, systemd), publisher-контейнер (docker `vesti-publisher`, :8410/healthz), SOCKS5-туннель Telegram (:1080), SQLite БД (vesti.db). ### Метрики (textfile-коллектор node-exporter, job `node`, label `component="vesti"`) Скрипт `/opt/vesti/scripts/vesti-metrics.sh` (запуск: cron.d `vesti-metrics`, root, каждую минуту; альтернативно systemd-таймер `vesti-metrics.timer`) пишет `/var/lib/node_exporter/textfile_collector/vesti.prom`. node-exporter на bigbox: `ARGS="--web.listen-address=10.8.0.2:9100 --collector.textfile.directory=/var/lib/node_exporter/textfile_collector"`. | Метрика | Смысл | |---|---| | vesti_web_up | web :8400 (systemd) | | vesti_publisher_health | publisher :8410 /healthz | | vesti_publisher_docker | контейнер publisher (Up) | | vesti_telegram_tunnel | SOCKS5-туннель :1080 | | vesti_web_systemd | systemd-юнит web | | vesti_publisher_bot / _proxy / _channels | детали publisher | | vesti_db_ok | SQLite vesti.db доступна | | vesti_db_posts_total / _new_total / _published_total | счётчики БД | | vesti_db_last_run_ts | Unix-ts последнего рan краулера | ### Дашборд `grafana/dashboards/vesti/vesti.json` (uid `vesti`, папка **vesti**), 18 панелей. Провайдер `vesti-dashboards` в `grafana/provisioning/dashboards/dashboards.yml`. Проверка импорта: `sudo docker cp grafana:/var/lib/grafana/grafana.db /tmp/gf.db` + `SELECT uid,title FROM dashboard WHERE uid='vesti'`. ### Алерты (группа `vesti` в alerts.yml) | Алерт | Выражение | Удержание | Severity | |---|---|---|---| | VestiWebDown | `vesti_web_up{component="vesti"} == 0` | 2m | critical | | VestiPublisherDown | `vesti_publisher_health{component="vesti"} == 0` | 2m | critical | | VestiPublisherContainerDown | `vesti_publisher_docker{component="vesti"} == 0` | 2m | critical | | VestiTunnelDown | `vesti_telegram_tunnel{component="vesti"} == 0` | 2m | critical | | VestiDbDown | `vesti_db_ok{component="vesti"} == 0` | 2m | critical | | VestiDbStale | `time() - vesti_db_last_run_ts{component="vesti"} > 1800` | 5m | warning | Реализовано через встроенный Prometheus Alerting (`rule_files` в prometheus.yml → alerts.yml; правила видны в `/api/v1/rules`). Перечитывается рестартом прометеуса (`docker restart prometheus`).