Files
vidconf/docs/deploy/monitoring.md
Max Ronzhin 356bc57a2a
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
docs(monitoring): описать контейнерный экспортер и модель безопасности
Новый раздел про container-exporter/docker-socket-proxy: зачем свой
экспортер вместо cAdvisor, какие метрики отдаёт, почему доступ к
докер-сокету через прокси безопасен. Дополнена таблица метрик backend
(vidconf_host_info).
2026-07-28 01:40:04 +03:00

181 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Мониторинг (Prometheus + Grafana, профиль compose `monitoring`)
Независимый compose-профиль — можно поднимать вместе
с любым пресетом инсталлятора (15) или отдельно.
## 1. Компоненты
| Сервис | Образ | Порт (хост) | Назначение |
|---|---|---|---|
| `prometheus` | `prom/prometheus:v3.13.1` | `9090` | сбор и хранение метрик, оценка правил алертинга |
| `postgres-exporter` | `quay.io/prometheuscommunity/postgres-exporter:v0.20.1` | — (внутренний) | метрики PostgreSQL |
| `redis-exporter` | `oliver006/redis_exporter:v1.87.0-alpine` | — (внутренний) | метрики Redis |
| `node-exporter` | `prom/node-exporter:v1.8.2` | — (внутренний) | метрики хоста: CPU, память, диск, сеть, load average |
| `docker-socket-proxy` | `tecnativa/docker-socket-proxy:v0.5.0` | — (внутренний) | read-only прокси к Docker Engine API для `container-exporter` |
| `container-exporter` | сборка из `deploy/monitoring/container-exporter/` | — (внутренний) | метрики CPU/память/сеть в разбивке по контейнерам |
| `grafana` | `grafana/grafana:13.1.0` | `3001` (внутри контейнера `3000`) | дашборды «Пайплайны пост-обработки» и «Хост и контейнеры» |
`node-exporter`/`docker-socket-proxy`/`container-exporter` не публикуют
порт на хост вообще (не только 127.0.0.1) — Prometheus и сам экспортер
ходят друг к другу по имени сервиса во внутренней сети compose,
публикация на хост для этого не нужна.
### 1.1. Метрики по контейнерам — свой экспортер вместо cAdvisor
**cAdvisor не используется**: Docker Engine на сервере `1gb` использует
containerd-снапшоттер (`docker info``driver-type:
io.containerd.snapshotter.v1`), а не классический overlay2-драйвер, и
cAdvisor (проверено на v0.49.2 и v0.52.1, с `--docker_only` и без, через
`--containerd`/`--containerd-namespace=moby` и без) не может определить
read-write layer контейнеров — падает с «failed to identify the
read-write layer ID», метрики есть только по корневому cgroup. Открытая
проблема совместимости cAdvisor с containerd image store, флагами не
чинится — см. `.forcc/JOURNAL.md`.
Вместо этого — свой минимальный экспортер (`container-exporter`,
`deploy/monitoring/container-exporter/exporter.py`, Python/aiohttp/
`prometheus_client`) поверх Docker Engine API: `GET /containers/json` +
`GET /containers/{id}/stats?stream=false`. Эти эндпойнты не зависят от
storage-driver'а — работают и с containerd-снапшоттером, и с overlay2.
Опрос контейнеров идёт параллельно в фоновой задаче (свой интервал,
не завязан на scrape Prometheus) — `stats?stream=false` не мгновенен
(Docker считает CPU-дельту по двум внутренним замерам с разницей ~1с),
линейный опрос дюжины контейнеров легко вышел бы за scrape-интервал.
Метрики (лейбл `name` — имя контейнера):
- `vidconf_container_cpu_percent` — % от одного ядра, как `docker stats`.
- `vidconf_container_memory_usage_bytes` / `_limit_bytes` — working set
(без переиспользуемого page cache) и лимит памяти.
- `vidconf_container_network_rx_bytes` / `_tx_bytes` — суммарно по
интерфейсам, счётчик со старта контейнера.
**Безопасность: доступ к `/var/run/docker.sock` равносилен root на
хосте.** Флаг `:ro` при монтировании сокета не спасает — он ограничивает
права на файл-ноду, а не на протокол: через сокет можно поднять
привилегированный контейнер и получить хост. Поэтому сокет НЕ
монтируется напрямую в `container-exporter` — между ними стоит
`docker-socket-proxy` (`tecnativa/docker-socket-proxy`), которому
разрешены только `GET`-запросы по контейнерам (`CONTAINERS=1`, `POST=0`
— дефолт образа, выписан явно). Даже полная компрометация
`container-exporter` (например, через уязвимость в его зависимостях) не
даёт управлять Docker — прокси физически не пропустит `POST`. Ни один
из двух сервисов не публикует портов наружу.
Если на другом сервере используется классический overlay2 и хочется
именно cAdvisor — можно добавить его по тому же образцу, что и
`node-exporter` (свой job в `prometheus.yml`, без публикации портов),
но тогда придётся решать ту же проблему монтирования сокета отдельно
(cAdvisor тоже требует доступ к Docker API).
Файлы: `deploy/monitoring/prometheus.yml`, `deploy/monitoring/alerts.yml`,
`deploy/monitoring/grafana/provisioning/` (datasource + провайдер
дашбордов), `deploy/monitoring/grafana/dashboards/pipelines.json`,
`deploy/monitoring/grafana/dashboards/host.json`,
`deploy/monitoring/container-exporter/` (код экспортера).
## 2. Запуск
```bash
docker compose -f deploy/docker-compose.yml --env-file .env --profile monitoring up -d
```
- Prometheus: http://localhost:9090
- Grafana: http://localhost:3001 (логин/пароль — `.env`,
`GRAFANA_ADMIN_USER`/`GRAFANA_ADMIN_PASSWORD`; `install.sh` генерирует
пароль при первой установке)
⚠️ На боевом сервере оба порта публикуются ТОЛЬКО на `127.0.0.1`
(`deploy/docker-compose.yml`) — открытые наружу admin-панели без
дополнительной аутентификации ранее уже приводили к компрометации сервисов
на этом проекте (см. `.forcc/deploy/SESSION2-FINDINGS.md`). С другой машины
используйте ssh-туннель:
```bash
ssh -L 9090:127.0.0.1:9090 -L 3001:127.0.0.1:3001 <user>@<host>
```
и открывайте `http://localhost:9090` / `http://localhost:3001` у себя.
Дашборды «Пайплайны пост-обработки» и «Хост и контейнеры» (папка VidConf
в Grafana) появляются сразу — источник данных и дашборды провижинятся из
файлов, без ручной настройки.
## 3. Метрики backend
`GET /metrics` (`backend/api/metrics.py`, без авторизации внутри
приложения — снаружи периметра закрыт явным `return 403` в
`deploy/nginx/nginx.conf`; Prometheus ходит в backend напрямую по docker-сети,
`backend:8000/metrics`, минуя nginx):
- `vidconf_http_request_duration_seconds` (histogram, `method`/`path`/`status`) —
латентность HTTP по шаблону маршрута.
- `vidconf_pipeline_sessions` (gauge, `status`) — число сеансов конференций в
каждом статусе `pipeline_status`
(recording→transcribing→summarizing→notified|failed).
- `vidconf_celery_queue_depth` (gauge, `queue`) — глубина очередей Celery
(`transcription`/`summarize`/`notify`/`celery`, redis `LLEN`), карта
очередей — `docs/deploy/scaling.md`.
- `vidconf_host_info` (gauge, всегда `1`, лейблы `cpus`/`ram_mb`/`gpu_name`/
`vram_mb`) — info-метрика обнаруженного `install.sh` железа (`HW_*` в
`.env`, ADR-004). Источник плашки с характеристиками сервера в дашборде
«Хост и контейнеры» — живые CPU/RAM/диск там же берутся из
node-exporter, а GPU node-exporter не знает вообще, поэтому только эта
метрика. `«—»` в лейбле — `install.sh` не запускался (dev) либо
видеокарта не обнаружена.
Job `llm` в `prometheus.yml` скрейпит `llm:8080/metrics`
(`LLAMA_ARG_ENDPOINT_METRICS=1`) — этот адрес резолвится ЛИБО сервисом
`llm` (CPU, профиль `llm`), ЛИБО `llm-gpu` (у него есть сетевой алиас `llm`,
см. `deploy/docker-compose.yml`) — профили `llm`/`llm-gpu` взаимоисключающи
по пресету, поэтому один job без дублирования.
**По умолчанию job `llm` закомментирован** в `prometheus.yml` (вместе с
алертом `LlmDown` в `alerts.yml`) — целевой набор профилей compose
(`media,monitoring`, см. `.env.example`) не включает `llm`/`llm-gpu`, и
таргет `llm:8080` не резолвится вовсе. Раскомментируйте job и алерт вместе,
если инсталляция запущена с профилем `llm`/`llm-gpu` (пресеты 35
`install.sh`) — иначе панель «LLM-сервер доступен» дашборда «Пайплайны
пост-обработки» будет показывать «No data».
## 4. Алерты (`deploy/monitoring/alerts.yml`)
| Алерт | Условие | severity |
|---|---|---|
| `PipelineFailed` | рост числа сеансов в статусе `failed` за 15 минут | critical |
| `QueueGrowing` | глубина очереди растёт 15 минут подряд и превышает 10 задач | warning |
| `LlmDown` | `up{job="llm"} == 0` дольше 2 минут | critical (закомментирован по умолчанию) |
| `HostMemoryLow` | свободно <10% RAM (≈800 МБ из 8 ГБ) дольше 10 минут | warning |
| `HostDiskLow` | свободно <10% диска (≈5 ГБ из 50 ГБ) дольше 15 минут | warning |
| `HostCpuHigh` | загрузка CPU >90% дольше 15 минут подряд | warning |
Пороги трёх алертов по железу подобраны под конкретный сервер `1gb`
(8 ГБ RAM, 4 CPU, 50 ГБ диска) — при смене сервера пересчитать
(`deploy/monitoring/alerts.yml`, группа `vidconf-host`).
`LlmDown` актуален только на инсталляциях с профилем `llm`/`llm-gpu`
(пресеты 35) — по умолчанию (профили `media,monitoring`, без AI) правило
закомментировано в `alerts.yml` вместе с job `llm` в `prometheus.yml`.
Раскомментируйте оба, если поднимаете профиль `llm`/`llm-gpu`.
Проверка (после раскомментирования job/алерта, на инсталляции с профилем
`llm`/`llm-gpu`) — искусственно завалить пайплайн и убедиться, что алерт
срабатывает:
```bash
# Стек с профилями media, transcribe, llm, monitoring уже поднят,
# идёт активная суммаризация (сеанс в статусе summarizing).
docker compose -f deploy/docker-compose.yml --env-file .env stop llm
# Подождать > 2 минут → Prometheus (http://localhost:9090/alerts)
# должен показать LlmDown в состоянии firing, следом — QueueGrowing
# (очередь summarize перестаёт разбираться) и, если сеанс не восстановится
# за 15 минут (recover_stuck_summaries переставит задачу, workers/celery_app.py),
# PipelineFailed.
docker compose -f deploy/docker-compose.yml --env-file .env start llm
```
## 5. Хранение
`prometheus_data`/`grafana_data` — именованные тома, переживают
пересоздание контейнеров. Ретеншен Prometheus — дефолт образа (15 дней);
для прод-инсталляций с длинным горизонтом донастраивается флагом
`--storage.tsdb.retention.time` (не задан в `deploy/docker-compose.yml`
осознанный dev/small-prod дефолт, донастраивается отдельно при необходимости).