Files
Deal/deploy/compose.dev.yml
T
stepan a9bcd7c5f7 Поднять фронт и observability в dev-стеке
Сервис frontend: сборка SPA (Vite) и отдача через Caddy, /api → core:5080. В профиль observability добавлены Loki/Promtail/Grafana (в dev их не было). Dev-Caddyfile отдаёт SPA с Cache-Control no-cache, чтобы пересборка подхватывалась обновлением страницы.
2026-09-13 16:22:16 +03:00

408 lines
19 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# compose.dev.yml — dev-стек Дейла (этап 6, Task 20; Ruling 12).
#
# Полный стек: postgres + minio (хранилища), три автономных сервиса этапа 6
# (telegram-service :5101, ai-service :5102, ml-service :5103, storage-service :5104) и core (deal-api:
# HTTP :5080 + gRPC-ингресс :5082).
#
# Режимы интеграций core — флагами SERVICES__{ML,AI,TELEGRAM}__USELOCAL (Ruling 6):
# * этот файл — сквозной gRPC-режим ВСЕГО стека «по-настоящему»: UseLocal=false заданы env
# core-сервиса ниже, интеграции ходят в ml/ai/telegram-сервисы этой же compose-сети
# (MlOutboxFlushScheduler → TrainBatch, GrpcAiClassifier/GrpcAiTools, GrpcTelegramClient).
# Подъём всего стека одной командой: docker compose -f deploy/compose.dev.yml up -d --build
# (см. также scripts/dev-smoke.sh — сквозная проверка с авто-очисткой).
# * Local-заглушки (дефолт КОДА — appsettings.json, UseLocal:true) сохраняются для запуска core
# с хоста БЕЗ сервисов (обычная dev-разработка, curl-приёмки этапов 2–5): поднимаются только
# postgres/minio, core — dotnet run из src/core/Deal.Api. Код-дефолт не меняется — режим
# переключает этот compose-файл (env ниже).
#
# Секреты dev (переопределяются .env/экспортом, шаблон ${VAR:-default}):
# DEAL_SERVICE_TOKEN — единый service-token всех процессов (metadata gRPC, Ruling 1/2/13)
# DEAL_ENCRYPTION_KEY — ключ AES-256-GCM шифрования секретов настроек core (32 байта base64)
# DEAL_TELEGRAM_SESSION_KEY — ключ AES-256-GCM файлов сессий telegram-service (32 байта base64)
# минио/БД creds — dev-значения ниже (в проде — этап 7, compose-prod + mTLS).
#
# Core в этом файле — «весь стек в docker» (эндпоинты — имена compose-сети). Альтернатива — core
# с хоста (Ruling 12): поднимаются только postgres/minio и (по желанию) сервисы, core запускается
# dotnet run, и сервисы ходят в ингресс хоста через DEAL_CORE_INGRESS=http://host.docker.internal:5082
# (см. env telegram-service ниже).
# Общий hardening прикладных сервисов (BL-IMG-HARDEN): read-only root FS + tmpfs /tmp,
# запрет новых привилегий и сброс capabilities. Данные — только в именованных volume.
x-service-hardening: &service-hardening
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
services:
postgres:
image: postgres:16-alpine
container_name: deal-postgres
environment:
POSTGRES_DB: deal
POSTGRES_USER: deal
POSTGRES_PASSWORD: deal_dev_password
ports:
- "5433:5432" # 5432 может быть занят LeadRadar-стеком
volumes:
- deal_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U deal -d deal"]
interval: 5s
timeout: 3s
retries: 10
# MinIO для файлов проектных карточек (Ruling 4, план Task 6; образца-прототипа object_store.py).
# Опционально: API работает и без него — LocalFileStorage (data/attachments) выбирается, пока MinIO не
# сконфигурирован (Storage__Minio__* / DEAL_MINIO_*). Бакет deal-files создаётся приложением лениво при
# первом upload (Ruling 4: EnsureBucket на первом put), отдельный init-контейнер не нужен.
# Консоль: http://localhost:9001 (root deal_minio / deal_minio_secret).
# Если 9000/9001 заняты LeadRadar-minio — поменяйте на 9100/9101 (и endpoint в конфигурации API).
minio:
image: minio/minio
container_name: deal-minio
environment:
MINIO_ROOT_USER: deal_minio
MINIO_ROOT_PASSWORD: deal_minio_secret
ports:
- "9000:9000" # S3 API
- "9001:9001" # Web-консоль
volumes:
- deal_minio_data:/data
command: server /data --console-address ":9001"
# core (deal-api) — HTTP :5080 + gRPC-ингресс telegram-service :5082 (план Task 12/20, Ruling 7/12).
# Здесь core поднимается в сквозном gRPC-режиме (UseLocal=false ниже): сервисы этой же сети
# вызываются по-настоящему; MlOutbox копится в БД и выгружается MlOutboxFlushScheduler (10 с).
# Local-режим (дефолт appsettings.json) остаётся для запуска core с хоста без сервисов.
core:
build:
context: ..
dockerfile: src/core/Deal.Api/Dockerfile
container_name: deal-core
<<: *service-hardening
mem_limit: 1g
cpus: 1.5
environment:
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://0.0.0.0:5080
GRPC_INGRESS_PORT: "5082"
ConnectionStrings__DealPostgres: Host=postgres;Port=5432;Database=deal;Username=deal;Password=deal_dev_password
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
DEAL_ENCRYPTION_KEY: ${DEAL_ENCRYPTION_KEY:-MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=}
# gRPC-режим всего стека (Ruling 6): Local-заглушки кода (appsettings default true)
# переключаются на gRPC-клиентов сервисов compose-сети. Хост-запуск core без compose
# остаётся на Local (код-дефолт не менялся).
Services__Ml__UseLocal: "false"
Services__Ai__UseLocal: "false"
Services__Telegram__UseLocal: "false"
Services__Ml__Endpoint: http://ml-service:5103
Services__Ai__Endpoint: http://ai-service:5102
Services__Telegram__Endpoint: http://telegram-service:5101
Services__Storage__Endpoint: http://storage-service:5104
Storage__Minio__Endpoint: minio:9000
Storage__Minio__AccessKey: deal_minio
Storage__Minio__SecretKey: deal_minio_secret
Storage__Minio__Bucket: deal-files
Storage__Minio__Secure: "false"
# Трейсинг OTel → коллектор профиля observability (без коллектора трейсы не экспортируются).
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
OTEL_SERVICE_NAME: core
ports:
- "5080:5080"
- "5082:5082" # gRPC-ингресс telegram-service (сервисы ходят на http://core:5082 внутри сети)
volumes:
- deal_api_data:/app/data # data/encryption.key + data/attachments (Local-фолбэк файлов)
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "/bin/grpc_health_probe", "-addr=localhost:5082"]
interval: 10s
timeout: 3s
retries: 10
start_period: 15s
# telegram-service — gRPC-сервер Telegram (этап 6, Ruling 12; план Task 2). Отдельный процесс,
# отдельное sln (src/telegram-service/Deal.Telegram.sln). Команды ядра приходят на localhost:5101
# (SERVICES__Telegram__Endpoint — задачи 9–12), исходящий поток — в core-ингресс :5082
# (SERVICES__CORE__INGRESS, Ruling 7; внутри compose — http://core:5082; при core на хосте задайте
# DEAL_CORE_INGRESS=http://host.docker.internal:5082). Plaintext gRPC без mTLS (Ruling 2 — dev):
# защита — общий service-token (DEAL_SERVICE_TOKEN, env хоста и контейнеров). Сессии тенантов —
# файлы /data/sessions (Ruling 3), шифруются AES-256-GCM ключом DEAL_TELEGRAM_SESSION_KEY
# (fail-closed: без ключа сервис не стартует). Контекст сборки — корень репозитория: csproj
# ссылается на src/contracts/Deal.Proto.csproj (общий проект кодогенерации, Task 1).
telegram-service:
build:
context: ..
dockerfile: src/telegram-service/Deal.Telegram/Dockerfile
container_name: deal-telegram-service
<<: *service-hardening
mem_limit: 512m
cpus: 1.0
environment:
GRPC_PORT: "5101"
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
DEAL_TELEGRAM_SESSION_KEY: ${DEAL_TELEGRAM_SESSION_KEY:-ZmVkY2JhOTg3NjU0MzIxMGZlZGNiYTk4NzY1NDMyMTA=}
DEAL_TELEGRAM_SESSION_DIR: /data/sessions
DEAL_LOGS_DIR: /tmp/logs
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
OTEL_SERVICE_NAME: telegram-service
SERVICES__CORE__INGRESS: ${DEAL_CORE_INGRESS:-http://core:5082}
ports:
- "5101:5101"
volumes:
- deal_tg_sessions:/data/sessions
healthcheck:
test: ["CMD", "/bin/grpc_health_probe", "-addr=localhost:5101"]
interval: 5s
timeout: 3s
retries: 10
# ai-service — gRPC-сервер ИИ-фасада (этап 6, Ruling 12; план Task 4). Отдельный процесс,
# отдельное sln (src/ai-service/Deal.Ai.sln). Промпты/классификацию/фильтр/ключи ядро шлёт на
# localhost:5102 (SERVICES__Ai__Endpoint — задачи 7/8/15). Plaintext gRPC без mTLS (Ruling 2 —
# dev): защита — общий service-token (DEAL_SERVICE_TOKEN, env хоста и контейнеров). Сервис без БД
# (Ruling 5): конфиг провайдера (id/base/model/apiKey/api_style) приходит в теле каждого запроса
# от ядра, персистентных файлов нет — volume не нужен. Контекст сборки — корень репозитория:
# csproj ссылается на src/contracts/Deal.Proto.csproj (общий проект кодогенерации, Task 1).
ai-service:
build:
context: ..
dockerfile: src/ai-service/Deal.Ai/Dockerfile
container_name: deal-ai-service
<<: *service-hardening
mem_limit: 512m
cpus: 1.0
environment:
GRPC_PORT: "5102"
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
DEAL_LOGS_DIR: /tmp/logs
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
OTEL_SERVICE_NAME: ai-service
ports:
- "5102:5102"
healthcheck:
test: ["CMD", "/bin/grpc_health_probe", "-addr=localhost:5102"]
interval: 5s
timeout: 3s
retries: 10
# ml-service — gRPC-сервер ML (этап 6, Ruling 12; план Task 3). Отдельный процесс, отдельное sln
# (src/ml-service/Deal.Ml.sln). Обучение и предсказания ядро шлёт на localhost:5103
# (SERVICES__Ml__Endpoint — задачи 5/6/16). Plaintext gRPC без mTLS (Ruling 2 — dev): защита —
# общий service-token (DEAL_SERVICE_TOKEN, env хоста и контейнеров). Модели тенантов — файлы
# SQLite /data/ml/<tenantId>.sqlite (Ruling 4). Контекст сборки — корень репозитория: csproj
# ссылается на src/contracts/Deal.Proto.csproj (общий проект кодогенерации, Task 1).
ml-service:
build:
context: ..
dockerfile: src/ml-service/Deal.Ml/Dockerfile
container_name: deal-ml-service
<<: *service-hardening
mem_limit: 768m
cpus: 1.0
environment:
GRPC_PORT: "5103"
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
DEAL_ML_DATA_DIR: /data/ml # файлы моделей data/ml/<tenantId>.sqlite на volume deal_ml_data (Ruling 4/12)
DEAL_LOGS_DIR: /tmp/logs
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
OTEL_SERVICE_NAME: ml-service
ports:
- "5103:5103"
volumes:
- deal_ml_data:/data/ml
healthcheck:
test: ["CMD", "/bin/grpc_health_probe", "-addr=localhost:5103"]
interval: 5s
timeout: 3s
retries: 10
# storage-service — общий gRPC-сервис данных (вложения источников). Отдельный процесс, отдельное sln
# (src/storage-service/Deal.Storage.sln). Загрузка/выгрузка/стат/удаление объектов; тип объекта
# определяет сервис. Бэкенд — MinIO. Защита — общий service-token (DEAL_SERVICE_TOKEN).
storage-service:
build:
context: ..
dockerfile: src/storage-service/Deal.Storage/Dockerfile
container_name: deal-storage-service
<<: *service-hardening
mem_limit: 512m
cpus: 1.0
environment:
GRPC_PORT: "5104"
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
DEAL_STORAGE_ENDPOINT: minio:9000
DEAL_STORAGE_ACCESS_KEY: deal_minio
DEAL_STORAGE_SECRET_KEY: deal_minio_secret
DEAL_STORAGE_BUCKET: deal-attachments
DEAL_STORAGE_SECURE: "false"
DEAL_LOGS_DIR: /tmp/logs
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
OTEL_SERVICE_NAME: storage-service
ports:
- "5104:5104"
depends_on:
- minio
healthcheck:
test: ["CMD", "/bin/grpc_health_probe", "-addr=localhost:5104"]
interval: 5s
timeout: 3s
retries: 10
# Фронтенд (SPA) — сборка образа (Vite) и отдача через Caddy; /api → core:5080. UI — http://localhost:8080.
frontend:
build:
context: ..
dockerfile: src/frontend/Dockerfile
container_name: deal-frontend
ports:
- "8080:80"
depends_on:
core:
condition: service_healthy
# Prometheus (профиль observability, этап 12/пакет A) — сбор /metrics всех 4 процессов (:9464)
# внутри dev-сети. Подъём: docker compose -f deploy/compose.dev.yml --profile observability up -d.
# Конфиг — общий deploy/observability/prometheus.yml (те же имена сервисов и таргеты). UI — 9090.
prometheus:
image: prom/prometheus:v3.5.0
container_name: deal-prometheus
profiles: ["observability"]
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=15d
ports:
- "9090:9090"
volumes:
- ./observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./observability/prometheus-rules.yml:/etc/prometheus/prometheus-rules.yml:ro
- deal_prometheus_data:/prometheus
# OpenTelemetry Collector — приём трейсов Deal-процессов (OTLP) → Tempo (профиль observability).
otel-collector:
image: otel/opentelemetry-collector-contrib:0.160.0
container_name: deal-otel-collector
profiles: ["observability"]
command: ["--config=/etc/otelcol-contrib/config.yaml"]
ports:
- "4317:4317"
- "4318:4318"
volumes:
- ./observability/otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro
depends_on:
tempo:
condition: service_started
# Tempo — хранилище трейсов (OTLP от коллектора), UI/API — :3200 (профиль observability).
tempo:
image: grafana/tempo:2.8.1
container_name: deal-tempo
profiles: ["observability"]
command: ["-config.file=/etc/tempo.yml"]
ports:
- "3200:3200"
volumes:
- ./observability/tempo.yml:/etc/tempo.yml:ro
- deal_tempo_data:/var/tempo
# cAdvisor — ресурсы контейнеров (CPU/RAM/сеть/диск); scrape — job cadvisor.
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.52.1
container_name: deal-cadvisor
profiles: ["observability"]
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
# node-exporter — ресурсы хоста (CPU/RAM/диск/сеть); scrape — job node-exporter.
node-exporter:
image: prom/node-exporter:v1.9.1
container_name: deal-node-exporter
profiles: ["observability"]
command:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($|/)
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/host/root:ro
pid: host
# Loki — хранилище логов (профиль observability), UI/API — :3100.
loki:
image: grafana/loki:3.4.2
container_name: deal-loki
profiles: ["observability"]
command: -config.file=/etc/loki/loki.yml
ports:
- "3100:3100"
volumes:
- ./observability/loki.yml:/etc/loki/loki.yml:ro
- deal_loki_data:/loki
# Promtail — сбор docker-логов deal-процессов в Loki (docker.sock, профиль observability).
promtail:
image: grafana/promtail:3.4.2
container_name: deal-promtail
profiles: ["observability"]
command: -config.file=/etc/promtail/promtail.yml
volumes:
- ./observability/promtail.yml:/etc/promtail/promtail.yml:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- deal_promtail_data:/var/lib/promtail
depends_on:
loki:
condition: service_started
# Grafana — UI логов/метрик/трейсов (профиль observability), локальный вход admin/admin.
grafana:
image: grafana/grafana:11.5.2
container_name: deal-grafana
profiles: ["observability"]
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
ports:
- "3001:3000"
volumes:
- ./observability/grafana/provisioning:/etc/grafana/provisioning:ro
- ./observability/grafana/dashboards:/var/lib/grafana/dashboards:ro
- deal_grafana_data:/var/lib/grafana
depends_on:
loki:
condition: service_started
prometheus:
condition: service_started
tempo:
condition: service_started
volumes:
deal_pgdata:
deal_minio_data:
deal_tg_sessions:
deal_ml_data:
deal_api_data:
deal_prometheus_data:
deal_tempo_data:
deal_loki_data:
deal_promtail_data:
deal_grafana_data: