From d2d99d8231c1cb8a14b05b54b768848012d3f2b7 Mon Sep 17 00:00:00 2001 From: jze9 Date: Tue, 11 Aug 2026 20:37:47 +0500 Subject: [PATCH] =?UTF-8?q?feat(obs):=20=D0=BD=D0=B0=D0=B1=D0=BB=D1=8E?= =?UTF-8?q?=D0=B4=D0=B0=D0=B5=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20Prometheus?= =?UTF-8?q?=20+=20Grafana=20=D0=BF=D0=BE=D0=B4=20=D0=BF=D1=80=D0=BE=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D0=B5=D0=BC=20observability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Раньше видимости в прод не было — только email-монитор (костыль). Добавлено без нагрузки по умолчанию (профиль не поднимается, пока не попросят): - API: /metrics через prometheus-fastapi-instrumentator (кол-во/латентность запросов по хендлерам); - Prometheus (infra/prometheus/prometheus.yml) скрейпит API и Flower — из Flower приходят метрики Celery (задачи, время, воркеры) вообще без доп. кода; - Grafana с автопровижном источника Prometheus (infra/grafana/provisioning), пароль через GRAFANA_ADMIN_PASSWORD; - docker-compose.prod.yml: сервисы prometheus/grafana под profiles:[observability] + volumes; .env.example и README пополнены. Запуск: docker compose --profile observability up -d prometheus grafana. Co-Authored-By: Claude Opus 4.8 --- .env.example | 8 +++++ README.md | 14 ++++++++ docker-compose.prod.yml | 32 +++++++++++++++++++ .../provisioning/datasources/prometheus.yml | 10 ++++++ infra/prometheus/prometheus.yml | 20 ++++++++++++ services/api/app/main.py | 6 ++++ services/api/requirements.txt | 1 + 7 files changed, 91 insertions(+) create mode 100644 infra/grafana/provisioning/datasources/prometheus.yml create mode 100644 infra/prometheus/prometheus.yml diff --git a/.env.example b/.env.example index fcdc8f3..2ede58a 100644 --- a/.env.example +++ b/.env.example @@ -39,3 +39,11 @@ SMTP_TLS_VERIFY=true # App APP_URL=https://academic.jze9.ru ENVIRONMENT=development + +# Векторный бэкенд (опционально): faiss (по умолчанию) или qdrant. См. README. +VECTOR_BACKEND=faiss +QDRANT_URL=http://qdrant:6333 +QDRANT_COLLECTION=documents + +# Наблюдаемость (профиль observability, опционально) +GRAFANA_ADMIN_PASSWORD=admin diff --git a/README.md b/README.md index 2d3276a..6b2c14f 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,20 @@ docker compose -f docker-compose.prod.yml exec worker-gpu python -m app.migrate_ docker compose -f docker-compose.prod.yml up -d worker-gpu ``` +## Наблюдаемость (Prometheus + Grafana) + +Опционально (профиль `observability`, по умолчанию не поднимается): + +```bash +docker compose -f docker-compose.prod.yml --profile observability up -d prometheus grafana +``` + +- API отдаёт HTTP-метрики на `/metrics` (кол-во и латентность запросов по хендлерам). +- Prometheus (`infra/prometheus/prometheus.yml`) скрейпит API и **Flower** — из + Flower приходят метрики Celery (задачи, время выполнения, воркеры) без доп. кода. +- Grafana с автоподключённым источником Prometheus (`infra/grafana/provisioning/`); + пароль admin — через `GRAFANA_ADMIN_PASSWORD` в `.env` (по умолчанию `admin`). + ## Тестирование и качество кода Перед деплоем CI (`.gitea/workflows/deploy.yml`, job `test`) прогоняет два гейта, diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index dd4e792..da596cb 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -14,6 +14,8 @@ volumes: es_prod_data: faiss_index_prod: qdrant_prod_data: + prometheus_data: + grafana_data: x-app-env: &app-env env_file: .env @@ -65,6 +67,36 @@ services: - antiplagiator restart: unless-stopped + # ─── Наблюдаемость (опционально; профиль observability) ──────────────────── + # docker compose -f docker-compose.prod.yml --profile observability up -d prometheus grafana + # Prometheus скрейпит api:/metrics и flower:/metrics (метрики Celery). + prometheus: + image: prom/prometheus:v2.54.1 + container_name: antiplagiator-prometheus + profiles: ["observability"] + volumes: + - ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus_data:/prometheus + networks: + - antiplagiator + restart: unless-stopped + + grafana: + image: grafana/grafana:11.2.0 + container_name: antiplagiator-grafana + profiles: ["observability"] + environment: + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin} + - GF_USERS_ALLOW_SIGN_UP=false + volumes: + - ./infra/grafana/provisioning:/etc/grafana/provisioning:ro + - grafana_data:/var/lib/grafana + networks: + - antiplagiator + restart: unless-stopped + depends_on: + - prometheus + # ─── Приложение ──────────────────────────────────────────────────────────── # RabbitMQ вынесен на отдельный сервер 192.168.20.82 (см. RABBITMQ_URL в .env) api: diff --git a/infra/grafana/provisioning/datasources/prometheus.yml b/infra/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..5dea77d --- /dev/null +++ b/infra/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,10 @@ +# Автопровижн источника данных Grafana → Prometheus (профиль observability). +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false diff --git a/infra/prometheus/prometheus.yml b/infra/prometheus/prometheus.yml new file mode 100644 index 0000000..82f7921 --- /dev/null +++ b/infra/prometheus/prometheus.yml @@ -0,0 +1,20 @@ +# Prometheus scrape-конфиг (профиль observability в docker-compose.prod.yml). +# Скрейпит HTTP-метрики API и Celery-метрики из Flower (Flower 2.0 отдаёт их сам). +global: + scrape_interval: 30s + evaluation_interval: 30s + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ["localhost:9090"] + + - job_name: api + metrics_path: /metrics + static_configs: + - targets: ["api:8000"] + + - job_name: flower # метрики Celery: задачи, время выполнения, воркеры + metrics_path: /metrics + static_configs: + - targets: ["flower:5555"] diff --git a/services/api/app/main.py b/services/api/app/main.py index 01122f5..c3b2381 100644 --- a/services/api/app/main.py +++ b/services/api/app/main.py @@ -7,6 +7,7 @@ from contextlib import asynccontextmanager from fastapi import Depends, FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse +from prometheus_fastapi_instrumentator import Instrumentator from app.api import admin, auth, documents, reports, search, tasks from app.config import settings @@ -80,6 +81,11 @@ app.include_router(documents.router, prefix="/api") app.include_router(reports.router, prefix="/api") app.include_router(admin.router, prefix="/api") +# ─── Метрики Prometheus ─────────────────────────────────────────────────────── +# HTTP-метрики (кол-во/латентность запросов по хендлерам) на /metrics. +# Prometheus скрейпит их (см. infra/prometheus/prometheus.yml, профиль observability). +Instrumentator().instrument(app).expose(app, endpoint="/metrics", include_in_schema=False) + # ─── WebSocket ──────────────────────────────────────────────────────────────── @app.websocket("/ws/tasks/{public_id}") diff --git a/services/api/requirements.txt b/services/api/requirements.txt index 5a44660..5e7e099 100644 --- a/services/api/requirements.txt +++ b/services/api/requirements.txt @@ -16,3 +16,4 @@ minio==7.2.7 httpx==0.27.0 aiofiles==23.2.1 websockets==12.0 +prometheus-fastapi-instrumentator==7.0.0 # /metrics для Prometheus (профиль observability)