23 KiB
Опыт эксплуатации: мониторинг Garage v2.1 через Prometheus
Дата: 2026-08-30 Ситуация: кластер Garage v2.1 (RF=3) на vps01 + bigbox + vps02, WireGuard 10.8.0.0/24. Задача: вывести статус кластера в браузер (Grafana + Prometheus + Loki).
Опыт: Vinograd WAN (Ростелеком) — ICMP-мониторинг внешнего канала (2026-09-08)
Ситуация: UptimeKuma алертил про 100% потерю пингов на шлюз 83.239.50.145 (канал «Винный город», РТК). Задача — мониторить ОБА адреса канала (шлюз + наше оборудование) в нашем стеке с графиками RTT каждые 30с.
18. ICMP-пробы через blackbox-exporter — модуль icmp
blackbox-exporter поддерживает ICMP-пробы (prober: icmp). Метрики:
probe_success— 1/0 (успех пробы)probe_icmp_duration_seconds{phase="rtt"}— RTT в секундахprobe_icmp_reply_hop_limit— TTL ответа
Нюансы:
- В контейнере (host-network, root) ICMP работает без доп. настроек — проверил
docker exec blackbox-exporter id→ root. В не-root окружении нуженsetcap cap_net_raw+epилиnet.ipv4.ping_group_range. - Важно про YAML: в
static_configsтаргеты — это список,labelsотносится к списку целиком, а НЕ к каждому элементу отдельно. Ошибка синтаксиса ловитсяpromtool check config.
19. Scrape job с интервалом 30s и relabel instance
- job_name: vinograd_wan
scrape_interval: 30s
metrics_path: /probe
params:
module: [icmp]
static_configs:
- targets: [83.239.50.145, 83.239.50.146]
relabel_configs:
# __address__ → instance: человекочитаемые имена для легенд Grafana
- source_labels: [__address__]
regex: '83\.239\.50\.145.*'
target_label: instance
replacement: vinograd-gw-83.239.50.145
...
# __address__ → реальный адрес blackbox (multi-target exporter pattern)
- target_label: __address__
replacement: 127.0.0.1:9115
scrape_interval: 30sна уровне job — работает (проверено: точки каждые 30с).- Regex с точками надо экранировать (
\.), иначе 83.239.50.145 совпадёт с .146. - relabel применяется по-порядку; сначала маппим instance, потом address → blackbox.
- Проверка таргетов:
curl http://127.0.0.1:9090/api/v1/targets→ vinograd_wan 2 targets UP.
20. Алерт на probe_success
- name: vinograd
rules:
- alert: VinogradRostelecomDown
expr: probe_success{job="vinograd_wan"} == 0
for: 2m
labels: {severity: critical}
for: 2mпри scrape 30s ≈ 4 пробы подряд.promtool check config→ 7 rules found.
21. Grafana dashboard provisioning и ретеншн
- Дашборд кладём в
grafana/dashboards/vinograd-wan.json— provisioner (updateIntervalSeconds: 30) сам импортирует в фолдере Garage; рестарт не нужен. Проверка: в grafana.db появился dashboard с uid=vinograd-wan. - Ретеншн «неделя»: retention в Prometheus глобальный (--storage.tsdb.retention.time=30d в этом стеке). Для 7 дней ровно нужен отдельный инстанс — здесь оставили 30d (перекрывает неделю с запасом). Не пытаться задать retention per-job — его нет.
22. Наблюдение: шлюз РТК не пингуется, но оборудование пингуется
- 83.239.50.146 (наше оборудование) — probe_success=1, RTT ~13ms.
- 83.239.50.145 (шлюз) — probe_success=0 (не отвечает на ICMP). Совпадает с алертом UptimeKuma. Это реальная авария, а не ошибка конфига: blackbox корректно видит недоступность шлюза.
Ключевые находки / грабли
1. Admin API Garage v2.1 слушает ОТДЕЛЬНЫЙ порт ([admin] api_bind_addr)
Самое важное. Если api_bind_addr НЕ задан в [admin], Garage слушает admin
(включая /metrics) на том же порту, что S3 API (3900). В этом случае
Authorization: Bearer TOKEN к /metrics на 3900 отвечает
400 Bad request: Unsupported authorization method — авторизация S3 не понимает
Bearer, а admin-роутер не подключён.
Решение: добавить в [admin] отдельный адрес:
[admin]
api_bind_addr = "10.8.0.2:3903" # свой WG-IP на каждой ноде
metrics_token = "..."
admin_token = "..."
После рестарта ноды /metrics и /health доступны на :3903.
2. metrics_token НЕ обязателен
Если metrics_token не задан — /metrics доступен без авторизации.
Если задан — используется как обычный Bearer token:
curl -H "Authorization: Bearer TOKEN" http://node:3903/metrics
(совпадает с синтаксисом admin_token; для метрик достаточно metrics_token).
3. Docker-образ dxflrs/garage:v2.1.0 — scratch без shell
- Нет
/bin/sh, нетls,docker exec garage ls— не работает. - CLI-бинарь лежит по пути
/garageвнутри образа, а НЕ в PATH. - Поэтому проверка статуса через docker run:
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
(без монтирования meta будет ошибка "Unable to read node key").
4. Перезапуск нод кластера — безопасно, но по очереди
docker compose restart garage на всех трёх нодах прошёл без потери кворума
и без пересборки layout — кластер остался HEALTHY (layout version 1 сохранился).
Порядок: внешние (vps02, vps01) → bigbox. Пауза ~8с между рестартами.
5. UFW на vps01 режет новые порты
Порт 3903 на vps01 был закрыт UFW (в отличие от 3901, открытого для Anywhere). Пришлось:
sudo ufw allow from 10.8.0.0/24 to any port 3903 proto tcp comment "garage admin metrics"
Аналогично пришлось открыть 9100 (node-exporter):
sudo ufw allow from 10.8.0.0/24 to any port 9100 proto tcp
На vps02 файрвол — nftables с policy ACCEPT, порт не блокировался.
6. Конфиг garage.toml на vps01/vps02 принадлежит root
Правка через SSH требует sudo (python не может писать напрямую):
ssh vps01 'sudo sed -i "s|^\[admin\]$|[admin]\napi_bind_addr = \"10.8.0.1:3903\"|" /opt/garage/garage.toml'
7. Docker bridge-сеть НЕ видит WireGuard-интерфейс хоста
Самое важное при разворачивании стека мониторинга: контейнеры в дефолтной bridge-сети (docker compose networks) не имеют доступа к WG-подсети 10.8.0.0/24 хоста. Prometheus не мог достать 10.8.0.x:3903, blackbox — тем более.
Решение: prometheus и blackbox-exporter запущены с network_mode: host.
Они видят и WG-интерфейс, и друг друга через 127.0.0.1. Grafana, Loki, Promtail
остались в bridge-сети (им WG не нужен).
8. /health Garage возвращает ТЕКСТ, а не метрику
curl http://node:3903/health → Garage is fully operational (текст, HTTP 200).
Нельзя использовать как metrics-таргет: Prometheus пытается распарсить текст
как float → strconv.ParseFloat: parsing "is".
Решение: health-проверка через blackbox-exporter (module http_2xx,
probe_success). В prometheus.yml job garage_health с params: module: [http_2xx],
relabel __address__ → 127.0.0.1:9115.
9. node-exporter — ставится apt'ом
Ubuntu (bigbox): prometheus-node-exporter 1.7.0; Debian 13 (vps01/vps02):
1.9.0. Работает сразу как systemd-сервис на :9100 (*:9100, все интерфейсы — WG
виден). На vps01 дополнительно открыть 9100 в ufw (см. п.5).
10. Права на volume-каталоги мониторинга
Контейнеры запускаются от не-root UID, а каталоги создавались от root:
- prometheus: UID 65534 (nobody) →
chown 65534:65534 prometheus-data - loki: UID 10001 →
chown 10001:10001 loki-data - grafana: UID 472 →
chown 472:472 grafana-data
Иначе: prometheus падает с Unable to create mmap-ed active query log,
loki — mkdir /loki/rules: permission denied.
11. Caddy на vps02 — docker, host network, конфиг /opt/caddy/Caddyfile
Grafana опубликована как grafana.nixg.ru (DNS → 87.242.100.206 = vps02):
grafana.nixg.ru {
reverse_proxy 10.8.0.2:3001
}
- Caddy в docker (
network_mode: host), конфиг смонтирован из /opt/caddy/Caddyfile. - Перезагрузка:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile - Сертификат Let's Encrypt выпускается автоматически (http-01), но первые ~30с после reload соединение может падать — это нормально.
- В логах caddy много ошибок renew для старых доменов — они имеют уже выпущенные сертификаты в caddy_data, работает всё.
Опыт: дашборд Grafana пустой (NO DATA) — 3 причины подряд (2026-09-02)
Дата: 2026-09-02. Ситуация: после этапа 6 (tproxy) пользователь сообщил, что дашборд Garage Cluster в Grafana полностью пустой (NO DATA).
12. NO DATA #1: у datasource не задан uid — Grafana генерит случайный
В grafana/provisioning/datasources/datasources.yml у Prometheus-датасорса
НЕ был задан uid. Grafana при провиженинге присваивает датасорсу случайный
UID (в БД видно для Loki: P8E80F9AEF21F6940), а все панели дашборда
ссылались на "uid": "Prometheus". Панели искали датасорс с таким uid — не
находили → NO DATA.
Решение: в datasources.yml добавить датасорсу явный uid: Prometheus
(совпадение 1-в-1 с uid в панелях). Дашборды провиженятся каждые 30с,
а datasources — только при старте Grafana → после правки docker compose restart grafana. Проверка из БД (docker cp grafana:/var/lib/grafana/grafana.db):
SELECT name, uid, url FROM data_source → uid стал Prometheus.
13. NO DATA #2: Prometheus на host-сети, а datasource URL http://prometheus:9090
Prometheus и blackbox запущены с network_mode: host (нужно для WG 10.8.0.0/24),
а Grafana — в bridge-сети. Внутри сети Grafana имя prometheus не
резолвится (проверено docker exec grafana getent hosts prometheus →
NO_RESOLVE), поэтому URL http://prometheus:9090 недостижим → панели без
данных. Loki в той же bridge-сети — резолвится нормально.
Решение: datasource URL заменить на http://172.28.0.1:9090 — IP хоста
со стороны bridge-сети Grafana (шлюз сети = адрес хоста). Определяется так:
docker inspect grafana --format '{{range .NetworkSettings.Networks}}{{.Gateway}} {{end}}'
# → 172.28.0.1
Проверено из контейнера: wget http://172.28.0.1:9090/-/healthy → OK.
ВАЖНО: шлюз bridge-сети docker стабилен (подсеть фиксированная),
но если пересоздать сеть — IP может смениться.
14. NO DATA #3 (частично): панели ссылались на НЕСУЩЕСТВУЮЩИЕ метрики
После починки datasource tproxy-панели ожили, а garage-панели (blocks, RPC node health, S3 req/sec) остались пустыми. Причина: панели были написаны под НЕСУЩЕСТВУЮЩИЕ имена метрик, которых в Prometheus нет:
garage_block_count— в реальностиblock_resync_queue_length/block_resync_errored_blocksgarage_rpc_node_health_is_up— в реальностиcluster_layout_node_connectedgarage_api_s3_request_counter— в реальностиapi_s3_request_counter
Метрики Garage из admin API (10.8.0.x:3903) не имеют префикса garage_:
api_s3_request_counter, api_s3_request_duration_*, block_resync_*,
cluster_* (connected_nodes, healthy, partitions_all_ok, layout_node_connected),
table_*, rpc_*. Префикс garage_ есть только у garage_build_info,
garage_local_disk_avail/total, garage_replication_factor.
Проверка реальных метрик: curl 'http://127.0.0.1:9090/api/v1/label/__name__/values'
и фильтр по job=garage. Панели переписаны на реальные метрики:
- Garage blocks (resync):
block_resync_queue_length+block_resync_errored_blocks - Garage node health (layout):
cluster_layout_node_connected(легенда{{ role_zone }}) - S3 requests /sec:
rate(api_s3_request_counter[5m])
15. Алерты ссылались на те же несуществующие метрики
В alerts.yml алерты GarageResyncErrors и GarageNodeUnstable использовали те же
фантомные имена (garage_block_resync_error_count, garage_rpc_node_health_is_up),
поэтому никогда не сработали бы. Исправлено на реальные:
block_resync_errored_blocks > 0 и cluster_layout_node_connected == 0.
После правки promtool check config → 6 rules found, все eval.
16. Relabel instance → hostname вместо IP (читаемые легенды)
По умолчанию instance = адрес скрейпа: у tproxy это 127.0.0.1:18081
(локальный конец SSH-туннеля), у garage 10.8.0.x:3903, у node 10.8.0.x:9100.
В Grafana легенды показывали IP — некрасиво и непонятно. Не путать: это НЕ
«мониторинг локального интерфейса», просто лейбл instance = транспорт туннеля.
Решение (на уровне Prometheus, не панелей) — relabel_configs в пром.yml:
relabel_configs:
- target_label: instance
replacement: vps03:8081 # для job tproxy
- source_labels: [__address__] # для garage/node — маппинг IP → hostname
regex: 10.8.0.2:3903
target_label: instance
replacement: bigbox:3903
Теперь легенды: vps01:3903, bigbox:3903, vps02:3903 (garage),
vps03:8081 (tproxy), vps01:9100 и т.д. (node). Правило: правим источник
(prom.yml relabel), а не легенды в каждой панели — консистентно везде
(панели, explore, алерты).
17. Grafana provisioning: дашборды — каждые 30с, datasources — только при старте
Дашборды перечитываются автоматически (updateIntervalSeconds, по умолчанию 30с),
а datasources провиженятся только при старте Grafana. После правки
datasources.yml — обязателен docker compose restart grafana.
Опыт: tproxy-server (WEB Proxy для Telegram Desktop) на vps03
Дата: 2026-08-31 Ситуация: развернули telegramdesktop/tproxy-server на vps03 (77.67.89.154, Debian 13): Caddy → tproxy-server:8080 → MTProxy:2398. Домен vps03.nixg.ru (HTTPS), порты 80/443 открыты из интернета.
A. Архитектура и порты
- Внешний вход:
https://vps03.nixg.ru:443→ Caddy →127.0.0.1:8080(tproxy-server) →127.0.0.1:2398(MTProxy). - tproxy-server config:
/etc/tproxy-server/config.json(600 root:tproxy). - Профили/секреты:
/etc/tproxy-server/profiles.json— массив профилей, можно несколько секретов (max_profiles: 32 в config). systemd читает их черезLoadCredential=profiles.json:/etc/tproxy-server/profiles.json(файл должен быть 600 root, иначе падает — тест TestLoadAcceptsSystemdCredentialReadPermissions). - Формат секрета: 16 байт = 32 hex (
openssl rand -hex 16), либо 17 байт с префиксомdd(fake-TLS-режим). - Добавить секрет: дописать профиль в profiles.json,
chmod 600,chown root:tproxy,systemctl restart tproxy-server→ в логеprofiles=N, readyz 200. - Отозвать секрет: убрать профиль из profiles.json + restart.
B. Admin-эндпоинты (только loopback 127.0.0.1:8081)
| Эндпоинт | Ответ |
|---|---|
| /healthz | всегда ok 200 (процесс жив) |
| /readyz | 200 ready / 503 backend unavailable — TCP-проба ко ВСЕМ backend-профилям (у нас 127.0.0.1:2398) |
| /metrics | Prometheus-формат (text/plain version 0.0.4), 13 счётчиков |
Метрики (все с префиксом tproxy_): sessions_live, streams_live,
backend_dials_in_flight, pending_bytes, pending_items, sessions_created_total,
sessions_closed_total, streams_opened_total, streams_rejected_total,
backend_dial_failures_total, bytes_up_total, bytes_down_total, limit_hits_total.
Ключевые для алертов: backend_dial_failures_total (рост = бэкенд недоступен),
limit_hits_total (DDOS/перегруз), sessions_live (активность).
C. Подключение к Prometheus (стек /opt/monitoring, bigbox) — РЕШЕНО через SSH-туннель
- vps03 НЕ в WG (10.8.0.0/24 = vps01/bigbox/vps02), поэтому прямого доступа
к 8081 с bigbox нет. Prometheus в стеке —
network_mode: host(видит внешние IP). - Изначальный план (nft-правило на vps03: разрешить TCP 8081 с publIP bigbox
178.176.197.2) не сработал: tproxy-server слушает
admin_listen: 127.0.0.1:8081, запрос с bigbox к77.67.89.154:8081вернулConnection refused(соединение упёрлось в отсутствующего наружу слушателя, а не в файрвол). - Решение: SSH-туннель bigbox → vps03 (паттерн systemd, как telegram-tunnel):
Prometheus скрейпит
# /etc/systemd/system/tproxy-tunnel.service (bigbox) User=estorozhenko ExecStart=/usr/bin/ssh -i /home/estorozhenko/.ssh/hostkeyVPS \ -L 127.0.0.1:18081:127.0.0.1:8081 -N \ -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \ -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new \ root@77.67.89.154 Restart=always127.0.0.1:18081. tproxy-server config/файрвол НЕ трогаются, метрики остаются на loopback. - ВНИМАНИЕ с портом: сначала взял
8081— но он на bigbox уже занят ICQ-веб-чатом (Converse.html на 0.0.0.0:8081), туннель конфликтовал и отдавал HTML чата вместо метрик. Порт на bigbox выбирать свободный (взял 18081). - readyz ходит ко ВСЕМ профилям: если добавить профиль с мёртвым бэкендом — readyz станет 503 (фича, учтена при алертах).
D. Грабли установки (уже в INSTALL_NOTES.md проекта tproxy-web)
- MTProxy-бинарь падал 203/EXEC:
chmod 755 /opt/MTProxy/objs/bin+ chown root:mtproxy + chmod 750. (см. /opt/hermes/tproxy-web/INSTALL_NOTES.md) go test ./...флакал на TestLoadAcceptsSystemdCredentialReadPermissions при запуске от root — обходится запуском тестов от не-root или пропуском.
# все три ноды отдают метрики:
curl -s -H "Authorization: Bearer TOKEN" http://10.8.0.1:3903/metrics | head
curl -s -H "Authorization: Bearer TOKEN" http://10.8.0.2:3903/metrics | head
curl -s -H "Authorization: Bearer TOKEN" http://10.8.0.4:3903/metrics | head
# → HTTP 200, ~90-110 КБ текста в Prometheus-формате
# кластер HEALTHY:
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: все таргеты UP (10/10):
curl http://127.0.0.1:9090/api/v1/targets
# garage x3, garage_health x3, node x3, prometheus self
# Grafana наружу:
curl -sk https://grafana.nixg.ru/api/health # → 200
# логи garage в Loki:
curl -G "http://127.0.0.1:3100/loki/api/v1/query_range" \
--data-urlencode 'query={container="garage"}' --data-urlencode 'limit=3'
Что осталось сделать / TODO
- Развернуть стек (docker compose up -d) в /opt/monitoring
- Node-exporter на всех 3 хостах (10.8.0.x:9100)
- Дашборд Garage в Grafana (provisioning + JSON)
up{job="garage"}в Prometheus, Grafana :3001- Логи garage через promtail → Loki → Grafana
- Опубликовать Grafana: grafana.nixg.ru через caddy на vps02
- Mirror репозитория мониторинга в gitea (estorozhenko/monitoring)
- Сделать разные admin_token / metrics_token (сейчас совпадают)
- Поменять пароль Grafana с дефолтного (сейчас: пользователь estorozhenko, пароль сменён пользователем вручную через UI)