Files
monitoring/EXPERIENCE.md
T

25 KiB
Raw Blame History

Опыт эксплуатации: мониторинг 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 корректно видит недоступность шлюза.

23. Read-only пользователь Grafana: provisioning НЕ работает (OSS), только UI (2026-09-08)

Задача: дать сотруднику IT Винограда read-only доступ к дашбордам (it@vinogorod.ru, роль Viewer). Попытки автоматизировать — провалились:

  • Файловое provisioning пользователей (grafana/provisioning/access-control/users.yml) в Grafana 11 OSS не обрабатывается: в логах старта только dashboards/datasources/alerting/plugins; access-control — фича Enterprise/Cloud (security.provisioning). Файл молча игнорируется, даже с валидным YAML.
  • API create: POST /api/users → 404 (в OSS недоступно). POST /api/login (JSON) → 401 даже при верном пароле; а Basic auth работает (curl -u estorozhenko:пароль /api/user → 200).
  • Итог: пользователя можно создать только в UI (Administration → Users → New user, роль Viewer). Пароль задаётся при создании.

Проверка после создания:

curl -s -u 'it@vinogorod.ru:1qazXSW2' http://127.0.0.1:3001/api/user   # → 200
curl -s -u 'estorozhenko:ПАРОЛЬ' http://127.0.0.1:3001/api/orgs/1/users # role=Viewer
curl -s -u 'it@vinogorod.ru:1qazXSW2' http://127.0.0.1:3001/api/users   # → 403 (read-only)

Ключевые находки / грабли

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_blocks
  • garage_rpc_node_health_is_up — в реальности cluster_layout_node_connected
  • garage_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):
    # /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=always
    
    Prometheus скрейпит 127.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)