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)