# Опыт эксплуатации: мониторинг Garage v2.1 через Prometheus > Дата: 2026-08-30 > Ситуация: кластер Garage v2.1 (RF=3) на vps01 + bigbox + vps02, WireGuard 10.8.0.0/24. > Задача: вывести статус кластера в браузер (Grafana + Prometheus + Loki). ## Ключевые находки / грабли ### 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]` отдельный адрес: ```toml [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, работает всё. ## Опыт: 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) - 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), job 'tproxy' в prometheus.yml, targets ['77.67.89.154:8081']. - ВАЖНО: admin-эндпоинты слушают только loopback. Открывать наружу ТОЛЬКО по источнику (ip saddr bigbox), не публиковать всем. - 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 - [x] Развернуть стек (docker compose up -d) в /opt/monitoring - [x] Node-exporter на всех 3 хостах (10.8.0.x:9100) - [x] Дашборд Garage в Grafana (provisioning + JSON) - [x] `up{job="garage"}` в Prometheus, Grafana :3001 - [x] Логи garage через promtail → Loki → Grafana - [x] Опубликовать Grafana: grafana.nixg.ru через caddy на vps02 - [x] Mirror репозитория мониторинга в gitea (estorozhenko/monitoring) - [ ] Сделать разные admin_token / metrics_token (сейчас совпадают) - [ ] Поменять пароль Grafana с дефолтного (сейчас: пользователь estorozhenko, пароль сменён пользователем вручную через UI)