DealDocumentScreening/docs/DEPLOY.md

20 KiB
Raw Blame History

Руководство по развёртыванию «Контракт-чек»

Целевая аудитория: DevOps / разработчик, поднимающий стек на одном VPS или локально. Предполагается: Docker + Docker Compose установлены, есть SSH-доступ к VPS. Полная архитектура: ARCHITECTURE.md.


Содержание

  1. Требования
  2. Быстрый старт локально
  3. Конфигурация через .env
  4. Docker Compose — полный стек
  5. Первичная инициализация
  6. Проверка работоспособности
  7. Telegram-бот: первый запуск
  8. B2B API: первый API-ключ
  9. Резервное копирование
  10. Обновление (zero-downtime-ish)
  11. Масштабирование
  12. Troubleshooting

1. Требования

Компонент Минимум Рекомендуемое
CPU 2 vCPU 4 vCPU (worker-extract CPU-bound)
RAM 2 GB 4 GB
Disk 20 GB SSD 40 GB SSD (MinIO + WAL-archive)
OS Ubuntu 22.04+ / Debian 12+ Ubuntu 24.04 LTS
Docker 24.0+ 25.0+
Docker Compose v2 v2
Интернет нужен для Ollama Cloud

Важно: Ollama Cloud инферит в США. Если 152-ФЗ data-residency критичен — рассмотрите self-hosted Ollama (GPU) или GigaChat-адаптер (core/llm/factory.py).


2. Быстрый старт локально

2.1 Клонирование и env

git clone <repo> contract-check
cd contract-check
cp .env.example .env
# Отредактируйте .env — минимум: OLLAMA_HOST, OLLAMA_API_KEY, BOT_TOKEN,
# TELEGRAM_BOT_TOKEN, JWT_SECRET

2.2 Инфраструктура (только Postgres + Redis + RabbitMQ + MinIO)

docker compose up -d
# Ждём healthy:
docker compose ps
# Ожидаемый результат: postgres, redis, rabbitmq, minio, minio-init — Up (healthy)

Порты на хосте (смещены, чтобы не конфликтовать):

  • Postgres: 15432
  • Redis: 17379
  • RabbitMQ AMQP: 5672, Management UI: http://localhost:15672
  • MinIO S3 API: 9000, Console: http://localhost:9001

2.3 Миграции

# Локально (нужен uv + dev-группа):
uv sync --group dev
uv run alembic upgrade head

# Или через временный api-контейнер:
docker compose --profile services run --rm api alembic upgrade head

2.4 Полный стек (api + workers + bot)

docker compose --profile services up -d --build
# Ждём healthy:
docker compose --profile services ps

3. Конфигурация через .env

Все секреты — в .env (не коммитить!). Ключевые переменные:

# --- LLM (обязательно) ---
OLLAMA_HOST=https://api.ollama.com
OLLAMA_API_KEY=sk-xxxxxxxx
OLLAMA_MODEL=qwen2.5:14b
OLLAMA_FALLBACK_MODEL=qwen2.5:7b

# --- Telegram + auth (обязательно) ---
BOT_TOKEN=123456789:ABCDEF...            # для aiogram бота
TELEGRAM_BOT_TOKEN=$BOT_TOKEN            # тот же токен; API использует для проверки Login Widget / Mini App
BOT_SERVICE_TOKEN=bot-prod-secret-xxx   # см. §5.3
JWT_SECRET=$(openssl rand -hex 32)      # HS256 secret для подписи JWT

# --- Postgres (можно оставить defaults для dev) ---
POSTGRES_USER=contract_check
POSTGRES_PASSWORD=changeme-strong-password
POSTGRES_DB=contract_check

# --- RabbitMQ ---
RABBITMQ_USER=contract_check
RABBITMQ_PASS=changeme-strong-password

# --- MinIO ---
S3_ACCESS_KEY=contract_check
S3_SECRET_KEY=changeme-strong-password
S3_BUCKET=contract-check-docs

# --- Billing ---
REFUND_POLICY=all   # или infra_only

# --- Observability (опционально) ---
SENTRY_DSN=https://...@sentry.io/...
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317

Полный список: см. .env.example и ARCHITECTURE.md §11.


4. Docker Compose — полный стек

4.1 Профили

Профиль Что поднимает
(default) postgres, redis, rabbitmq, minio, minio-init
services api, worker-extract, worker-analyze, bot
obs (планируется) prometheus, grafana, otel-collector, tempo
edge (планируется) nginx, certbot
# Инфра:
docker compose up -d

# + сервисы (сборка + запуск):
docker compose --profile services up -d --build

# + observability + edge (когда будут готовы конфиги в deploy/):
# docker compose --profile services --profile obs --profile edge up -d --build

4.2 depends_on и healthchecks

api ждёт postgres, rabbitmq, minio-init (healthy / completed). bot ждёт только api (healthy) — не инфра напрямую, соблюдая hexagonal boundary. Workers ждут postgres, rabbitmq, minio-init.


5. Первичная инициализация

5.1 Миграции

# Способ A: локально (если uv установлен)
uv run alembic upgrade head

# Способ B: через api-контейнер (если uv недоступен на хосте)
docker compose --profile services run --rm api alembic upgrade head

Проверка:

docker compose exec postgres psql -U contract_check -d contract_check -c "\dt"
# Должны быть: api_key_requests, api_keys, documents, invoices, jobs, reports, service_tokens, users

5.2 Seed-token для адаптеров (bot / web / cli)

Бот аутентифицируется в api через Authorization: Bearer <BOT_SERVICE_TOKEN>. Этот токен нужно положить в service_tokens.

# Генерируем секрет
BOT_SERVICE_TOKEN=$(openssl rand -hex 32)
echo "BOT_SERVICE_TOKEN=$BOT_SERVICE_TOKEN" >> .env

# Записываем hash в БД через временный api-контейнер
docker compose --profile services run --rm api \
  python -c "
import asyncio, os, sys
os.chdir('/app')
sys.path.insert(0, 'src')
from contract_check.core.config import get_settings
from contract_check.core.tokens import hash_token
from contract_check.core.db.session import create_session_factory
from sqlalchemy import text
async def seed():
    factory = create_session_factory()
    async with factory() as s:
        h = hash_token('$BOT_SERVICE_TOKEN')
        await s.execute(text('INSERT INTO service_tokens (name, token_hash, adapter) VALUES (:n, :h, :a) ON CONFLICT (name) DO UPDATE SET token_hash = EXCLUDED.token_hash'), {'n': 'bot-prod', 'h': h, 'a': 'bot'})
        await s.commit()
        print('seeded bot-prod')
asyncio.run(seed())
"

В production используйте python -m contract_check.api seed-token CLI (если реализовано).

5.3 Перезапуск бота с новым токеном

docker compose --profile services restart bot
# Проверка логов:
docker compose --profile services logs -f bot

6. Проверка работоспособности

6.1 Health endpoints

curl http://localhost:8000/healthz   # liveness — 200
curl http://localhost:8000/readyz    # readiness — 200 (PG + Rabbit + MinIO)
curl http://localhost:8000/metrics   # Prometheus exposition

6.2 RabbitMQ Management UI

Откройте http://localhost:15672 (guest/guest или credentials из .env). Проверьте:

  • Exchange contracts.x (direct)
  • Queues: extract.q, analyze.q (quorum), extract.retry.q, analyze.retry.q, DLQ
  • Connections/consumers от worker-ов

6.3 MinIO Console

http://localhost:9001 (root user = S3_ACCESS_KEY / S3_SECRET_KEY). Бакет contract-check-docs должен появиться после minio-init.

6.4 End-to-end smoke-тест (через curl)

# 1. Сначала нужен JWT пользователя. В dev можно обменять telegram_id через
#    service-token endpoint /auth/telegram/bot (в production этим занимается бот):
curl -X POST -H "Authorization: Bearer $BOT_SERVICE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"telegram_id": 123456}' \
  http://localhost:8000/api/v1/auth/telegram/bot
# → {"access_token":"eyJ...", "user_id":"...", "telegram_id":123456, ...}

USER_JWT="eyJ..."  # скопируйте access_token

# 2. Проверить /me
curl -H "Authorization: Bearer $USER_JWT" http://localhost:8000/api/v1/me
# → {"telegram_id":123456, "credits_left":0}

# 3. Загрузить документ (нужен credit — пока 0, но проверим путь)
curl -X POST -H "Authorization: Bearer $USER_JWT" \
  -F "file=@contract.pdf" http://localhost:8000/api/v1/documents
# → 402 Payment Required (нормально — нет кредитов)

# 4. Добавить кредит вручную (dev):
docker compose exec postgres psql -U contract_check -d contract_check \
  -c "UPDATE users SET credits_left = 10 WHERE telegram_id = 123456;"

7. Telegram-бот: первый запуск

7.1 Создание бота в BotFather

  1. Напишите @BotFather/newbot
  2. Скопируйте токен (BOT_TOKEN) в .env
  3. Установите webhook (опционально, polling работает по умолчанию):
    curl -F "url=https://your-domain.com/webhook" \
         https://api.telegram.org/bot$BOT_TOKEN/setWebhook
    

7.2 Запуск

docker compose --profile services up -d bot
docker compose --profile services logs -f bot

Ожидаемый вывод при /start:

Привет! Я «Контракт-чек» — первичный скрининг рисков в договорах...
Осталось проверок: 0.

7.3 Добавление кредитов пользователю

# Найти telegram_id пользователя (из логов бота или таблицы users)
docker compose exec postgres psql -U contract_check -d contract_check \
  -c "UPDATE users SET credits_left = credits_left + 5 WHERE telegram_id = <tg_id>;"

8. B2B API: первый API-ключ

8.1 Создание ключа (через user JWT)

Управление B2B-ключами теперь требует пользовательский JWT. Получите JWT через /auth/telegram/bot (как в §6.4) и выполните:

curl -X POST -H "Authorization: Bearer $USER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "integration-test", "rate_limit_rps": 3, "monthly_quota": 100}' \
  http://localhost:8000/api/v1/b2b/keys
# → {"api_key": "cc_abc123...", "id": "...", ...}
# Сохраните api_key — он показывается ТОЛЬКО ОДИН РАЗ

8.2 Анализ документа через B2B

API_KEY="cc_abc123..."
curl -X POST -H "X-API-Key: $API_KEY" \
  -F "file=@contract.pdf" \
  http://localhost:8000/api/v1/analyze
# → 202 {"document_id": "...", "correlation_id": "..."}

8.3 Проверка usage

curl -H "X-API-Key: $API_KEY" http://localhost:8000/api/v1/b2b/usage

9. Резервное копирование

9.1 Postgres

# Ручной backup
docker compose exec postgres pg_dump -U contract_check -d contract_check \
  > backup_$(date +%Y%m%d_%H%M%S).sql

# Автоматический backup (cron на хосте)
# 0 2 * * * cd /opt/contract-check && docker compose exec -T postgres pg_dump -U contract_check -d contract_check | gzip > backups/pg_$(date +\%Y\%m\%d).sql.gz

9.2 WAL-архивы (PITR-ready)

Postgres настроен с wal_level=replica, archive_mode=on. WAL-сегменты пишутся в volume pgwal (mount /walarchive). Для полноценного PITR настройте pg_backrest или репликацию (см. ARCHITECTURE.md §10).

9.3 MinIO

MinIO хранит raw-документы и extracted .txt. ILM-правило истекает объекты через DOC_RETENTION_DAYS (по умолчанию 7 дней). Отчёты живут в Postgres — они выживут после истечения raw-документов. Backup MinIO не обязателен для бизнес-логики.


10. Обновление

10.1 Rolling update (без остановки всего)

# 1. Pull изменений
git pull origin main

# 2. Rebuild + recreate (Compose пересоздаёт только изменённые контейнеры)
docker compose --profile services up -d --build

# 3. Миграции (если есть новые)
docker compose --profile services run --rm api alembic upgrade head

# 4. Проверка:
curl http://localhost:8000/readyz

10.2 Graceful shutdown workers

Workers получают SIGTERM → завершают текущее сообщение → SIGKILL после grace period. Compose stop_grace_period по умолчанию 10s; для долгих контрактов можно увеличить.


11. Масштабирование

11.1 На одном VPS (вертикальное)

  • Увеличьте MQ_PREFETCH_EXTRACT до числа CPU
  • Увеличьте MQ_PREFETCH_ANALYZE до конкарренси Ollama (3 на Pro, 10 на Max)
  • Масштабируйте RAM под размер контрактов

11.2 Горизонтальное (несколько worker-ов)

# docker-compose.override.yml
services:
  worker-extract:
    deploy:
      replicas: 2
  worker-analyze:
    deploy:
      replicas: 2
docker compose --profile services up -d --scale worker-extract=2 --scale worker-analyze=2

Workers stateless — горизонтальное масштабирование бесплатно. Единственное ограничение: конкарренси Ollama Cloud (3 на Pro). Не масштабируйте worker-analyze выше, чем позволяет квота провайдера — иначе получите 429.

11.3 Многонодовая HA

См. ARCHITECTURE.md §10. Путь к HA — только инфраструктурный (Compose → кластер RabbitMQ 3 nodes + Patroni Postgres + distributed MinIO). Код core/ не меняется.


12. Troubleshooting

12.1 docker compose up зависает — сервисы не стартуют

Причина: depends_on: condition: service_healthy — кто-то не прошёл healthcheck.

# Диагностика:
docker compose ps
docker compose logs <service>

# Частые причины:
# - Postgres ещё не готов: подождите 15-20s после первого запуска
# - minio-init не выполнился: проверьте docker compose logs minio-init
# - RabbitMQ не отвечает: docker compose logs rabbitmq

12.2 POST /documents → 402 Payment Required

Пользователю не хватает credits_left. В dev добавьте вручную:

docker compose exec postgres psql -U contract_check -d contract_check \
  -c "UPDATE users SET credits_left = credits_left + 1 WHERE telegram_id = <id>;"

12.3 Worker-extract падает с ExtractionError

Документ — скан/PDF без текстового слоя. Worker должен автоматически перейти к OCR. Если и OCR падает — documents.status=failed, credit refunded (если refundable). Проверьте:

docker compose --profile services logs worker-extract
# Или в Postgres:
docker compose exec postgres psql -U contract_check -d contract_check \
  -c "SELECT id, status, stage, last_failure_class FROM documents ORDER BY created_at DESC LIMIT 5;"

12.4 Worker-analyze: постоянные 429 / llm_quota

  • Превышена квота Ollama Cloud. Проверьте usage в dashboard ollama.com.
  • Уменьшите MQ_PREFETCH_ANALYZE или перейдите на Enterprise тариф.
  • Проверьте fallback-model (OLLAMA_FALLBACK_MODEL) — он должен сработать на 429.

12.5 Bot: Unauthorized / 401

  • BOT_SERVICE_TOKEN не совпадает с service_tokens.token_hash в БД.
  • Бот не смог получить user JWT через /api/v1/auth/telegram/bot (проверьте логи бота и api).
  • TELEGRAM_BOT_TOKEN / JWT_SECRET не заданы в .env для сервиса api.
  • Пересоздайте токен через §5.2.

12.6 Бот не отвечает

# Проверка логов
docker compose --profile services logs -f bot

# Проверка polling:
# Бот использует polling по умолчанию (aiogram). Если webhook установлен —
# убедитесь, что Nginx проксирует /webhook к api:8000.

12.7 MinIO: файлы не видны / bucket не создан

# Ручной init (если minio-init не отработал)
docker compose run --rm minio-init
# Или проверьте через mc:
docker compose run --rm minio-init mc ls local/

12.8 Миграции: alembic не видит таблицы / revision conflict

# Проверка текущей HEAD:
docker compose --profile services run --rm api alembic current

# Принудительный upgrade (осторожно — только dev!):
docker compose --profile services run --rm api alembic stamp head

12.9 Полный сброс (dev only!)

# Удалить ВСЕ данные (тома + контейнеры):
docker compose --profile services down -v
docker compose down -v
# Затем пересоздать с нуля: §2 + §5

Чек-лист перед production

  • .env заполнен, .env.example не содержит реальных секретов
  • POSTGRES_PASSWORD, RABBITMQ_PASS, S3_SECRET_KEY — strong random
  • .env заполнен (включая TELEGRAM_BOT_TOKEN, JWT_SECRET)
  • BOT_SERVICE_TOKEN засеян в service_tokens
  • Миграции накатаны (alembic upgrade head)
  • docker compose --profile services ps показывает все healthy
  • /healthz и /readyz отвечают 200
  • Telegram-бот отвечает на /start
  • Тестовый PDF проходит pipeline: upload → extract → analyze → report
  • B2B-ключ создаётся через /api/v1/b2b/keys с user JWT
  • Backup cron настроен
  • Firewall: открыты только 443 (nginx), 22 (ssh), 15672 (RabbitMQ mgmt, restrict IP)

Ссылки

  • ARCHITECTURE.md — полная архитектура, схемы БД, RabbitMQ topology, конфиг
  • TICKETS.md — текущие статусы задач
  • README.md — структура, quick start, API overview