feat(monitoring): метрики CPU/RAM/диска хоста и контейнеров
Дашборд «Пайплайны пост-обработки» покрывал только прикладную логику — нехватка памяти/CPU на сервере была видна только косвенно, по латентности API. Добавлены node-exporter (метрики хоста) и cAdvisor (метрики по контейнерам, профиль monitoring) — оба без публикации портов наружу, Prometheus ходит к ним по внутренней сети compose. Новый дашборд host.json («Хост и контейнеры») и три алерта (HostMemoryLow/HostDiskLow/HostCpuHigh) с порогами под сервер 1gb (8 ГБ RAM, 4 CPU, 50 ГБ диска).
This commit is contained in:
@@ -60,3 +60,58 @@ groups:
|
||||
# суммаризация встанет (задачи будут копиться в очереди summarize,
|
||||
# см. также алерт QueueGrowing). Проверить
|
||||
# `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 ГБ). Смотреть, какой контейнер ест
|
||||
память — дашборд «Хост и контейнеры», топ по памяти (cAdvisor).
|
||||
|
||||
# 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 ядер). Смотреть топ контейнеров по CPU
|
||||
(дашборд «Хост и контейнеры», cAdvisor) и латентность API —
|
||||
возможно, не хватает уровня AI/ресурсов под нагрузку.
|
||||
|
||||
245
deploy/monitoring/grafana/dashboards/host.json
Normal file
245
deploy/monitoring/grafana/dashboards/host.json
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Хост и контейнеры",
|
||||
"description": "CPU/RAM/диск/сеть хоста (node-exporter) и разбивка по контейнерам (cAdvisor) — профиль compose monitoring. Пороги на панелях согласованы с deploy/monitoring/alerts.yml (группа vidconf-host).",
|
||||
"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": 12, "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"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"title": "Топ контейнеров по CPU",
|
||||
"description": "Источник — cAdvisor (job cadvisor). Отвечает на вопрос «кто из контейнеров грузит CPU» при срабатывании HostCpuHigh.",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": {
|
||||
"defaults": { "unit": "percentunit", "custom": { "drawStyle": "line", "fillOpacity": 10, "stacking": { "mode": "normal" } } },
|
||||
"overrides": []
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "topk(5, sum by (name) (rate(container_cpu_usage_seconds_total{name!=\"\"}[5m])))",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "Топ контейнеров по памяти",
|
||||
"description": "container_memory_working_set_bytes — это то, что реально учитывает cgroup OOM killer (в отличие от RSS), источник — cAdvisor.",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 28 },
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"fieldConfig": {
|
||||
"defaults": { "unit": "decbytes", "custom": { "drawStyle": "line", "fillOpacity": 10, "stacking": { "mode": "normal" } } },
|
||||
"overrides": []
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "topk(5, container_memory_working_set_bytes{name!=\"\"})",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
# Конфигурация Prometheus (devops) — сбор метрик backend,
|
||||
# PostgreSQL, Redis и локального LLM-сервера. Поднимается compose-профилем
|
||||
# `monitoring` (deploy/docker-compose.yml, сервис `prometheus`).
|
||||
# PostgreSQL, Redis, железа хоста, контейнеров и локального LLM-сервера.
|
||||
# Поднимается compose-профилем `monitoring` (deploy/docker-compose.yml,
|
||||
# сервис `prometheus`).
|
||||
#
|
||||
# Имена метрик backend (`vidconf_http_request_duration_seconds`,
|
||||
# `vidconf_pipeline_sessions`, `vidconf_celery_queue_depth`) — КОНТРАКТ с
|
||||
@@ -33,6 +34,22 @@ scrape_configs:
|
||||
static_configs:
|
||||
- targets: ["redis-exporter:9121"]
|
||||
|
||||
# Хост целиком: CPU, память, диск, сеть, load average (профиль monitoring
|
||||
# — сервис node-exporter). Единственный источник, который покажет
|
||||
# нехватку памяти/CPU на сервере, если она не проявится как рост
|
||||
# латентности API (см. дашборд host.json).
|
||||
- job_name: node
|
||||
static_configs:
|
||||
- targets: ["node-exporter:9100"]
|
||||
|
||||
# Метрики по каждому контейнеру (CPU/память отдельно у backend, worker,
|
||||
# postgres и т.д. — профиль monitoring, сервис cadvisor). Отвечает на
|
||||
# вопрос «какой из контейнеров ест ресурсы», в отличие от job `node`
|
||||
# (только хост целиком).
|
||||
- job_name: cadvisor
|
||||
static_configs:
|
||||
- targets: ["cadvisor:8080"]
|
||||
|
||||
# Локальный LLM-сервер (llama.cpp, LLAMA_ARG_ENDPOINT_METRICS=1). Адрес
|
||||
# `llm:8080` разрешается ОДНИМ из двух compose-сервисов в зависимости от
|
||||
# выбранного при установке пресета — `llm` (CPU, профиль `llm`, уровни
|
||||
|
||||
Reference in New Issue
Block a user