Compare commits
5 Commits
v0.0.8
...
7f5c88869a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f5c88869a | |||
| 54fd1a26d7 | |||
| 456cc58b25 | |||
| dccc369d0b | |||
| 77ee26014d |
@@ -98,7 +98,7 @@ SMTP_TIMEOUT_S=30
|
|||||||
# --- Версия инстанса (релиз v0.0.1) ---
|
# --- Версия инстанса (релиз v0.0.1) ---
|
||||||
# install.sh копирует значение из корневого файла VERSION при каждой
|
# install.sh копирует значение из корневого файла VERSION при каждой
|
||||||
# установке/обновлении — руками менять не нужно.
|
# установке/обновлении — руками менять не нужно.
|
||||||
VIDCONF_VERSION=0.0.8
|
VIDCONF_VERSION=0.0.9
|
||||||
|
|
||||||
# --- Профили compose. Дефолт ниже (`media,monitoring`) — только для ручного
|
# --- Профили compose. Дефолт ниже (`media,monitoring`) — только для ручного
|
||||||
# `docker compose up` БЕЗ install.sh: медиа (LiveKit+coturn) + мониторинг,
|
# `docker compose up` БЕЗ install.sh: медиа (LiveKit+coturn) + мониторинг,
|
||||||
|
|||||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -3,6 +3,26 @@
|
|||||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
||||||
проект придерживается [семантического версионирования](https://semver.org/lang/ru/).
|
проект придерживается [семантического версионирования](https://semver.org/lang/ru/).
|
||||||
|
|
||||||
|
## [0.0.9] — 2026-07-28
|
||||||
|
|
||||||
|
Метрики CPU/RAM/диска хоста в Grafana; дефолтные подвкладки конференций и
|
||||||
|
пользователей в админке.
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
- Мониторинг железа: `node-exporter` (CPU/RAM/диск/сеть/load average хоста)
|
||||||
|
— профиль compose `monitoring`, без публикации портов наружу. Новый
|
||||||
|
дашборд Grafana «Хост и контейнеры». Три новых алерта Prometheus:
|
||||||
|
`HostMemoryLow`, `HostDiskLow`, `HostCpuHigh` (пороги — под сервер `1gb`:
|
||||||
|
8 ГБ RAM, 4 CPU, 50 ГБ диска). `cAdvisor` (разбивка по контейнерам)
|
||||||
|
сознательно не включён — несовместим с containerd-снапшоттером Docker
|
||||||
|
Engine на `1gb`, см. `docs/deploy/monitoring.md`.
|
||||||
|
|
||||||
|
### Изменено
|
||||||
|
- Админка, вкладка «Конференции»: вкладка «Все» перенесена в конец списка
|
||||||
|
фильтров (после «Завершённые»), по умолчанию открываются «Запланированные».
|
||||||
|
- Админка, вкладка «Пользователи»: по умолчанию открываются «Активные»
|
||||||
|
(было «Все»).
|
||||||
|
|
||||||
## [0.0.8] — 2026-07-27
|
## [0.0.8] — 2026-07-27
|
||||||
|
|
||||||
Админка: несколько mail-доменов для регистрации, фильтр и поиск по команде в
|
Админка: несколько mail-доменов для регистрации, фильтр и поиск по команде в
|
||||||
@@ -206,6 +226,7 @@
|
|||||||
|
|
||||||
Первоначальная версия VidConf.
|
Первоначальная версия VidConf.
|
||||||
|
|
||||||
|
[0.0.9]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.9
|
||||||
[0.0.8]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.8
|
[0.0.8]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.8
|
||||||
[0.0.7]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.7
|
[0.0.7]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.7
|
||||||
[0.0.6]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.6
|
[0.0.6]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.6
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ services:
|
|||||||
MEDIA_ROOT: ${MEDIA_ROOT:-/app/media}
|
MEDIA_ROOT: ${MEDIA_ROOT:-/app/media}
|
||||||
# Версия инстанса (релиз v0.0.1) — install.sh копирует значение
|
# Версия инстанса (релиз v0.0.1) — install.sh копирует значение
|
||||||
# из файла VERSION (корень репозитория) в .env; отдаётся в GET /api/health.
|
# из файла VERSION (корень репозитория) в .env; отдаётся в GET /api/health.
|
||||||
VIDCONF_VERSION: ${VIDCONF_VERSION:-0.0.8}
|
VIDCONF_VERSION: ${VIDCONF_VERSION:-0.0.9}
|
||||||
# config/ лежит в корне репозитория и не попадает в образ (контекст сборки —
|
# config/ лежит в корне репозитория и не попадает в образ (контекст сборки —
|
||||||
# только backend/), поэтому plugins.yaml монтируется отдельно.
|
# только backend/), поэтому plugins.yaml монтируется отдельно.
|
||||||
volumes:
|
volumes:
|
||||||
@@ -704,6 +704,48 @@ services:
|
|||||||
profiles: ["monitoring"]
|
profiles: ["monitoring"]
|
||||||
logging: *default-logging
|
logging: *default-logging
|
||||||
|
|
||||||
|
node-exporter:
|
||||||
|
# Метрики железа хоста (CPU, память, диск, сеть, load average) — то,
|
||||||
|
# чего нет ни в одном из приложенческих экспортеров выше. Без
|
||||||
|
# `network_mode: host` (не нужен: читаем /proc,/sys,/ хоста через
|
||||||
|
# bind-mount, а Prometheus достаёт их по имени сервиса во внутренней
|
||||||
|
# сети compose — так безопаснее, не расширяет сетевой доступ контейнера).
|
||||||
|
image: prom/node-exporter:v1.8.2
|
||||||
|
restart: unless-stopped
|
||||||
|
pid: host
|
||||||
|
volumes:
|
||||||
|
- /proc:/host/proc:ro
|
||||||
|
- /sys:/host/sys:ro
|
||||||
|
- /:/rootfs:ro
|
||||||
|
command:
|
||||||
|
- '--path.procfs=/host/proc'
|
||||||
|
- '--path.sysfs=/host/sys'
|
||||||
|
- '--path.rootfs=/rootfs'
|
||||||
|
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
|
||||||
|
# Не публикуем порт наружу вообще (не 127.0.0.1:9100, а совсем без
|
||||||
|
# ports) — Prometheus ходит к нему по внутренней сети compose
|
||||||
|
# (`node-exporter:9100`), публикация на хост для этого не нужна.
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -q -O- http://127.0.0.1:9100/metrics >/dev/null || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 10s
|
||||||
|
profiles: ["monitoring"]
|
||||||
|
logging: *default-logging
|
||||||
|
|
||||||
|
# cAdvisor сознательно НЕ используется: несовместим с Docker Engine на
|
||||||
|
# сервере `1gb` (containerd-снапшоттер вместо классического overlay2 —
|
||||||
|
# `docker info` показывает `driver-type: io.containerd.snapshotter.v1`).
|
||||||
|
# cAdvisor (проверено на v0.49.2 и v0.52.1) не может определить
|
||||||
|
# read-write layer контейнеров через ни docker.sock, ни напрямую через
|
||||||
|
# `--containerd`/`--containerd-namespace=moby` — падает с
|
||||||
|
# «failed to identify the read-write layer ID», отдаёт метрики только по
|
||||||
|
# корневому cgroup, без разбивки по контейнерам. Открытая проблема
|
||||||
|
# совместимости cAdvisor с containerd image store, не чинится флагами —
|
||||||
|
# см. `.forcc/JOURNAL.md` за детали и альтернативы. node-exporter
|
||||||
|
# (сервис выше) метрики хоста при этом покрывает полностью.
|
||||||
|
|
||||||
grafana:
|
grafana:
|
||||||
image: grafana/grafana:13.1.0
|
image: grafana/grafana:13.1.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -60,3 +60,60 @@ groups:
|
|||||||
# суммаризация встанет (задачи будут копиться в очереди summarize,
|
# суммаризация встанет (задачи будут копиться в очереди summarize,
|
||||||
# см. также алерт QueueGrowing). Проверить
|
# см. также алерт QueueGrowing). Проверить
|
||||||
# `docker compose ps llm` / `llm-gpu` и `docker compose logs llm`.
|
# `docker compose ps llm` / `llm-gpu` и `docker compose logs llm`.
|
||||||
|
|
||||||
|
# Железо хоста (job `node` — node-exporter). Пороги подобраны под
|
||||||
|
# конкретный сервер 1gb: 8 ГБ RAM, 4 CPU, 50 ГБ диска — если сервер
|
||||||
|
# сменится, пересчитать.
|
||||||
|
- name: vidconf-host
|
||||||
|
rules:
|
||||||
|
# MemAvailable — уже честная оценка Linux с учётом того, что легко
|
||||||
|
# освобождаемый page cache/buffers не в счёт (в отличие от naive
|
||||||
|
# used = total - free). 10% от 8 ГБ ≈ 800 МБ — `for: 10m`, чтобы не
|
||||||
|
# дёргать на кратковременный всплеск (например, разовый всплеск
|
||||||
|
# transcribe/summarize), но успеть среагировать до OOM killer.
|
||||||
|
- alert: HostMemoryLow
|
||||||
|
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 10
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Мало свободной памяти на хосте"
|
||||||
|
description: >-
|
||||||
|
Свободно {{ $value | printf "%.1f" }}% RAM дольше 10 минут
|
||||||
|
(порог 10% ≈ 800 МБ из 8 ГБ). Разбивки по контейнерам в Grafana
|
||||||
|
нет (cAdvisor несовместим с containerd-снапшоттером на этом
|
||||||
|
сервере, см. deploy/docker-compose.yml) — смотреть вручную,
|
||||||
|
например `docker stats`.
|
||||||
|
|
||||||
|
# 50 ГБ диска — 10% ≈ 5 ГБ. `for: 15m` (не мгновенно): диск не растёт
|
||||||
|
# так же резко, как память, ложные срабатывания на всплеск не грозят,
|
||||||
|
# но и разовая проверка на границе некритична — 15 минут отсекает шум.
|
||||||
|
- alert: HostDiskLow
|
||||||
|
expr: (node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"}) * 100 < 10
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Мало места на диске хоста"
|
||||||
|
description: >-
|
||||||
|
Свободно {{ $value | printf "%.1f" }}% диска дольше 15 минут
|
||||||
|
(порог 10% ≈ 5 ГБ из 50 ГБ). Частые причины на этом проекте —
|
||||||
|
записи транскрибации (`recordings`), логи docker, образы/слои
|
||||||
|
после пересборки — проверить `docker system df`.
|
||||||
|
|
||||||
|
# 4 CPU. Порог 90% и `for: 15m` — сознательно строже по времени, чем
|
||||||
|
# у памяти/диска: кратковременные пики от пайплайна пост-обработки
|
||||||
|
# (транскрибация/суммаризация) — это ожидаемая, не аварийная нагрузка,
|
||||||
|
# алерт должен ловить именно устойчивую перегрузку, а не обычный всплеск.
|
||||||
|
- alert: HostCpuHigh
|
||||||
|
expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Устойчиво высокая загрузка CPU хоста"
|
||||||
|
description: >-
|
||||||
|
Загрузка CPU {{ $value | printf "%.1f" }}% дольше 15 минут
|
||||||
|
подряд (порог 90% из 4 ядер). Разбивки по контейнерам в Grafana
|
||||||
|
нет (см. HostMemoryLow) — смотреть `docker stats` и латентность
|
||||||
|
API — возможно, не хватает уровня AI/ресурсов под нагрузку.
|
||||||
|
|||||||
205
deploy/monitoring/grafana/dashboards/host.json
Normal file
205
deploy/monitoring/grafana/dashboards/host.json
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
{
|
||||||
|
"title": "Хост и контейнеры",
|
||||||
|
"description": "CPU/RAM/диск/сеть хоста (node-exporter) — профиль compose monitoring. Пороги на панелях согласованы с deploy/monitoring/alerts.yml (группа vidconf-host). Разбивки по контейнерам нет: cAdvisor несовместим с containerd-снапшоттером Docker Engine на сервере 1gb (см. комментарий у сервиса cadvisor в deploy/docker-compose.yml и .forcc/JOURNAL.md) — вручную смотреть `docker stats`.",
|
||||||
|
"uid": "vidconf-host",
|
||||||
|
"editable": false,
|
||||||
|
"timezone": "browser",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"version": 1,
|
||||||
|
"time": { "from": "now-6h", "to": "now" },
|
||||||
|
"refresh": "30s",
|
||||||
|
"tags": ["vidconf", "host"],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Загрузка CPU",
|
||||||
|
"description": "100 - idle, усреднено по всем ядрам. Порог алерта HostCpuHigh — 90% дольше 15 минут, см. alerts.yml.",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "percent", "max": 100, "min": 0, "custom": { "drawStyle": "line", "fillOpacity": 10 } },
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
|
||||||
|
"legendFormat": "CPU busy",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Load average",
|
||||||
|
"description": "node_load1/5/15 — на 4-ядерном сервере устойчивое значение выше ~4 означает очередь на CPU.",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "custom": { "drawStyle": "line", "fillOpacity": 5 } },
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "node_load1", "legendFormat": "1 мин", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "node_load5", "legendFormat": "5 мин", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "node_load15", "legendFormat": "15 мин", "refId": "C" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Использование RAM",
|
||||||
|
"description": "(1 - MemAvailable/MemTotal) — MemAvailable уже учитывает легко освобождаемый page cache/buffers. Порог алерта HostMemoryLow — доступно <10% дольше 10 минут, см. alerts.yml.",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "percent", "max": 100, "min": 0, "custom": { "drawStyle": "line", "fillOpacity": 10 } },
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100",
|
||||||
|
"legendFormat": "RAM used",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Своп (использовано)",
|
||||||
|
"description": "SwapTotal - SwapFree. На этом сервере swap не заведён под мониторинг отдельным алертом — рост от нуля сам по себе сигнал, что памяти уже не хватает (см. HostMemoryLow).",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "decbytes", "custom": { "drawStyle": "line", "fillOpacity": 10 } },
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes",
|
||||||
|
"legendFormat": "swap used",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "Свободно на диске (/)",
|
||||||
|
"description": "node_filesystem_avail_bytes для корня. Порог алерта HostDiskLow — <10% (≈5 ГБ из 50 ГБ) дольше 15 минут.",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 16 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "decbytes",
|
||||||
|
"thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 5368709120 }] }
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "node_filesystem_avail_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "RAM сейчас, %",
|
||||||
|
"description": "Текущий снимок панели «Использование RAM». Порог совпадает с HostMemoryLow (used > 90%).",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 16 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 90 }] }
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "Диск сейчас, %",
|
||||||
|
"description": "Текущий снимок использования корневого раздела. Порог совпадает с HostDiskLow (used > 90%).",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 16 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 90 }] }
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "(1 - node_filesystem_avail_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"} / node_filesystem_size_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"}) * 100",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "CPU сейчас, %",
|
||||||
|
"description": "Текущий снимок загрузки CPU. Порог совпадает с HostCpuHigh (>90%).",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 16 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 90 }] }
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Сетевой трафик",
|
||||||
|
"description": "rx/tx по физическим интерфейсам хоста (исключены lo/veth/docker/br — это внутренние интерфейсы контейнеров, а не реальный сетевой трафик сервера).",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 20 },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "Bps", "custom": { "drawStyle": "line", "fillOpacity": 5 } },
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "rate(node_network_receive_bytes_total{device!~\"lo|veth.*|docker.*|br-.*\"}[5m])",
|
||||||
|
"legendFormat": "rx {{device}}",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "rate(node_network_transmit_bytes_total{device!~\"lo|veth.*|docker.*|br-.*\"}[5m])",
|
||||||
|
"legendFormat": "tx {{device}}",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Конфигурация Prometheus (devops) — сбор метрик backend,
|
# Конфигурация Prometheus (devops) — сбор метрик backend,
|
||||||
# PostgreSQL, Redis и локального LLM-сервера. Поднимается compose-профилем
|
# PostgreSQL, Redis, железа хоста и локального LLM-сервера. Поднимается
|
||||||
# `monitoring` (deploy/docker-compose.yml, сервис `prometheus`).
|
# compose-профилем `monitoring` (deploy/docker-compose.yml, сервис
|
||||||
|
# `prometheus`).
|
||||||
#
|
#
|
||||||
# Имена метрик backend (`vidconf_http_request_duration_seconds`,
|
# Имена метрик backend (`vidconf_http_request_duration_seconds`,
|
||||||
# `vidconf_pipeline_sessions`, `vidconf_celery_queue_depth`) — КОНТРАКТ с
|
# `vidconf_pipeline_sessions`, `vidconf_celery_queue_depth`) — КОНТРАКТ с
|
||||||
@@ -33,6 +34,14 @@ scrape_configs:
|
|||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["redis-exporter:9121"]
|
- targets: ["redis-exporter:9121"]
|
||||||
|
|
||||||
|
# Хост целиком: CPU, память, диск, сеть, load average (профиль monitoring
|
||||||
|
# — сервис node-exporter). Единственный источник, который покажет
|
||||||
|
# нехватку памяти/CPU на сервере, если она не проявится как рост
|
||||||
|
# латентности API (см. дашборд host.json).
|
||||||
|
- job_name: node
|
||||||
|
static_configs:
|
||||||
|
- targets: ["node-exporter:9100"]
|
||||||
|
|
||||||
# Локальный LLM-сервер (llama.cpp, LLAMA_ARG_ENDPOINT_METRICS=1). Адрес
|
# Локальный LLM-сервер (llama.cpp, LLAMA_ARG_ENDPOINT_METRICS=1). Адрес
|
||||||
# `llm:8080` разрешается ОДНИМ из двух compose-сервисов в зависимости от
|
# `llm:8080` разрешается ОДНИМ из двух compose-сервисов в зависимости от
|
||||||
# выбранного при установке пресета — `llm` (CPU, профиль `llm`, уровни
|
# выбранного при установке пресета — `llm` (CPU, профиль `llm`, уровни
|
||||||
|
|||||||
@@ -10,11 +10,31 @@
|
|||||||
| `prometheus` | `prom/prometheus:v3.13.1` | `9090` | сбор и хранение метрик, оценка правил алертинга |
|
| `prometheus` | `prom/prometheus:v3.13.1` | `9090` | сбор и хранение метрик, оценка правил алертинга |
|
||||||
| `postgres-exporter` | `quay.io/prometheuscommunity/postgres-exporter:v0.20.1` | — (внутренний) | метрики PostgreSQL |
|
| `postgres-exporter` | `quay.io/prometheuscommunity/postgres-exporter:v0.20.1` | — (внутренний) | метрики PostgreSQL |
|
||||||
| `redis-exporter` | `oliver006/redis_exporter:v1.87.0-alpine` | — (внутренний) | метрики Redis |
|
| `redis-exporter` | `oliver006/redis_exporter:v1.87.0-alpine` | — (внутренний) | метрики Redis |
|
||||||
| `grafana` | `grafana/grafana:13.1.0` | `3001` (внутри контейнера `3000`) | дашборд «Пайплайны пост-обработки» |
|
| `node-exporter` | `prom/node-exporter:v1.8.2` | — (внутренний) | метрики хоста: CPU, память, диск, сеть, load average |
|
||||||
|
| `grafana` | `grafana/grafana:13.1.0` | `3001` (внутри контейнера `3000`) | дашборды «Пайплайны пост-обработки» и «Хост и контейнеры» |
|
||||||
|
|
||||||
|
`node-exporter` не публикует порт на хост вообще (не только 127.0.0.1) —
|
||||||
|
Prometheus ходит к нему по имени сервиса во внутренней сети compose,
|
||||||
|
публикация на хост для этого не нужна.
|
||||||
|
|
||||||
|
**cAdvisor сознательно не используется** (разбивки метрик по контейнерам
|
||||||
|
в Grafana нет): 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`. Если на другом сервере используется
|
||||||
|
классический overlay2, cAdvisor можно добавить обратно по тому же
|
||||||
|
образцу, что и `node-exporter` (job `cadvisor` в `prometheus.yml`,
|
||||||
|
таргет `cadvisor:8080`, без публикации портов).
|
||||||
|
|
||||||
Файлы: `deploy/monitoring/prometheus.yml`, `deploy/monitoring/alerts.yml`,
|
Файлы: `deploy/monitoring/prometheus.yml`, `deploy/monitoring/alerts.yml`,
|
||||||
`deploy/monitoring/grafana/provisioning/` (datasource + провайдер
|
`deploy/monitoring/grafana/provisioning/` (datasource + провайдер
|
||||||
дашбордов), `deploy/monitoring/grafana/dashboards/pipelines.json`.
|
дашбордов), `deploy/monitoring/grafana/dashboards/pipelines.json`,
|
||||||
|
`deploy/monitoring/grafana/dashboards/host.json`.
|
||||||
|
|
||||||
## 2. Запуск
|
## 2. Запуск
|
||||||
|
|
||||||
@@ -37,9 +57,9 @@ ssh -L 9090:127.0.0.1:9090 -L 3001:127.0.0.1:3001 <user>@<host>
|
|||||||
```
|
```
|
||||||
и открывайте `http://localhost:9090` / `http://localhost:3001` у себя.
|
и открывайте `http://localhost:9090` / `http://localhost:3001` у себя.
|
||||||
|
|
||||||
Дашборд «Пайплайны пост-обработки» (папка VidConf в Grafana) появляется
|
Дашборды «Пайплайны пост-обработки» и «Хост и контейнеры» (папка VidConf
|
||||||
сразу — источник данных и дашборд провижинятся из файлов, без ручной
|
в Grafana) появляются сразу — источник данных и дашборды провижинятся из
|
||||||
настройки.
|
файлов, без ручной настройки.
|
||||||
|
|
||||||
## 3. Метрики backend
|
## 3. Метрики backend
|
||||||
|
|
||||||
@@ -78,6 +98,13 @@ Job `llm` в `prometheus.yml` скрейпит `llm:8080/metrics`
|
|||||||
| `PipelineFailed` | рост числа сеансов в статусе `failed` за 15 минут | critical |
|
| `PipelineFailed` | рост числа сеансов в статусе `failed` за 15 минут | critical |
|
||||||
| `QueueGrowing` | глубина очереди растёт 15 минут подряд и превышает 10 задач | warning |
|
| `QueueGrowing` | глубина очереди растёт 15 минут подряд и превышает 10 задач | warning |
|
||||||
| `LlmDown` | `up{job="llm"} == 0` дольше 2 минут | critical (закомментирован по умолчанию) |
|
| `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`
|
`LlmDown` актуален только на инсталляциях с профилем `llm`/`llm-gpu`
|
||||||
(пресеты 3–5) — по умолчанию (профили `media,monitoring`, без AI) правило
|
(пресеты 3–5) — по умолчанию (профили `media,monitoring`, без AI) правило
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import { formatRecurrenceSummary } from '@/lib/recurrenceFormat'
|
|||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
const STATUS_FILTERS: { value: ConferenceStatus | 'all'; label: string }[] = [
|
const STATUS_FILTERS: { value: ConferenceStatus | 'all'; label: string }[] = [
|
||||||
{ value: 'all', label: 'Все' },
|
|
||||||
{ value: 'scheduled', label: 'Запланированные' },
|
{ value: 'scheduled', label: 'Запланированные' },
|
||||||
{ value: 'active', label: 'Активные' },
|
{ value: 'active', label: 'Активные' },
|
||||||
{ value: 'ended', label: 'Завершённые' },
|
{ value: 'ended', label: 'Завершённые' },
|
||||||
|
{ value: 'all', label: 'Все' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function StatusBadge({ conference }: { conference: AdminConferenceOut }) {
|
function StatusBadge({ conference }: { conference: AdminConferenceOut }) {
|
||||||
@@ -287,7 +287,7 @@ function InviteModal({ conference, onClose }: { conference: AdminConferenceOut;
|
|||||||
* блок «table-card», адаптированный под сущность «конференция»).
|
* блок «table-card», адаптированный под сущность «конференция»).
|
||||||
*/
|
*/
|
||||||
export function AdminConferencesTab() {
|
export function AdminConferencesTab() {
|
||||||
const [statusFilter, setStatusFilter] = useState<ConferenceStatus | 'all'>('all')
|
const [statusFilter, setStatusFilter] = useState<ConferenceStatus | 'all'>('scheduled')
|
||||||
const [searchInput, setSearchInput] = useState('')
|
const [searchInput, setSearchInput] = useState('')
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [offset, setOffset] = useState(0)
|
const [offset, setOffset] = useState(0)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const STATUS_FILTERS: { value: 'active' | 'blocked' | 'all'; label: string }[] =
|
|||||||
*/
|
*/
|
||||||
export function AdminUsersTab() {
|
export function AdminUsersTab() {
|
||||||
const { user: currentUser } = useAuth()
|
const { user: currentUser } = useAuth()
|
||||||
const [statusFilter, setStatusFilter] = useState<'active' | 'blocked' | 'all'>('all')
|
const [statusFilter, setStatusFilter] = useState<'active' | 'blocked' | 'all'>('active')
|
||||||
const [searchInput, setSearchInput] = useState('')
|
const [searchInput, setSearchInput] = useState('')
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [offset, setOffset] = useState(0)
|
const [offset, setOffset] = useState(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user