- endpoint коллектора по умолчанию пуст: трейсы включаются только при заданном DEAL_OTEL_ENDPOINT; - storage-service добавлен в prod-compose (mTLS, OTel env, healthcheck); - node-exporter: убран rslave (несовместим с Docker Desktop).
457 lines
24 KiB
YAML
457 lines
24 KiB
YAML
# compose.prod.yml — PROD-стек «Дейла» (этап 7, Ruling 6/7/9; план Task 14).
|
||
#
|
||
# ⚠ НЕ поднимать на dev-машине вместо compose.dev.yml: это production-конфигурация с
|
||
# fail-fast на секреты (см. ниже) и без dev-дефолтов.
|
||
#
|
||
# Состав (всё в одной внутренней сети compose, наружу — ТОЛЬКО caddy :80/:443):
|
||
# postgres, minio — хранилища БЕЗ host-портов (volume'ы);
|
||
# core (:5080 HTTP + :5082 gRPC-ингресс), telegram-service (:5101), ai-service (:5102),
|
||
# ml-service (:5103), storage-service (:5104) — процессы «Дейла»; mTLS-транспорт — по env Ruling 6 (см. ниже);
|
||
# caddy — edge: TLS-терминация, статика фронта, reverse_proxy /api → core.
|
||
# observability (ПРОФИЛЬ `observability`) — современный стек: otel-collector (приём трейсов OTLP),
|
||
# tempo (хранилище трейсов), loki/promtail (логи), prometheus (метрики),
|
||
# cadvisor/node-exporter (потребление ресурсов контейнеров/хоста), grafana (UI).
|
||
# Подъём: docker compose --profile observability up -d.
|
||
#
|
||
# Секреты — ТОЛЬКО из env: шаблон deploy/.env.prod.example → скопируйте в deploy/.env.prod,
|
||
# заполните значения и запускайте с --env-file:
|
||
# cp deploy/.env.prod.example deploy/.env.prod # и заполнить (без дефолтных паролей!)
|
||
# docker compose --env-file deploy/.env.prod -f deploy/compose.prod.yml up -d --build
|
||
# docker compose --env-file deploy/.env.prod -f deploy/compose.prod.yml \
|
||
# --profile observability up -d --build # + Loki/Promtail/Grafana/Prometheus
|
||
# Проверка без движка: docker compose --env-file ... -f deploy/compose.prod.yml config
|
||
# Ключевые обязательные (без значения `config` упадёт с подсказкой): DEAL_PG_PASSWORD,
|
||
# DEAL_ENCRYPTION_KEY, DEAL_SERVICE_TOKEN, DEAL_TELEGRAM_SESSION_KEY, MINIO_ROOT_USER,
|
||
# MINIO_ROOT_PASSWORD, DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD (пустые — оператор не создаётся,
|
||
# см. Ruling 1), DEAL_ALLOWED_ORIGINS.
|
||
#
|
||
# PROD-флаги кода (Ruling 5/9/10): RateLimit__Enabled=true (лимиты/защита входа), куки
|
||
# Cookies__Secure/OperatorCookies__Secure=true (TLS терминирует caddy), ForwardedHeaders__Enabled=true
|
||
# (доверенный прокси — caddy; KnownNetworks — приватные docker-сети хоста: core наружу не публикуется,
|
||
# единственный вход в него — caddy), Security__AllowedOrigins — origin фронта.
|
||
#
|
||
# mTLS внутреннего gRPC (Ruling 6, Task 13/14): DEAL_MTLS_ENABLED=0 — plaintext + service-token
|
||
# (как dev, но внутри изолированной сети); DEAL_MTLS_ENABLED=1 — Kestrel сервисов/ингресса требует
|
||
# клиентский сертификат. Сертификаты: deploy/certs (scripts/mtls-certs.sh) монтируются в
|
||
# /etc/deal/certs. ВАЖНО при включении mTLS:
|
||
# * схемы внутренних endpoint'ов поменять на https:// (DEAL_ML_ENDPOINT/DEAL_AI_ENDPOINT/
|
||
# DEAL_TELEGRAM_ENDPOINT/DEAL_CORE_INGRESS в .env.prod);
|
||
# * healthcheck'и ниже сами переключаются на TLS-пробу grpc_health_probe (клиентский сертификат из
|
||
# /etc/deal/certs; PEM-артефакты deal-client.crt/.key генерирует scripts/mtls-certs.sh);
|
||
# * DEAL_MTLS_CERT_PASSWORD — пароль PFX (серверных и клиентского).
|
||
#
|
||
# Логи (Ruling 7): процессы пишут Serilog JSON в stdout (docker-логи) + rolling-файл
|
||
# data/logs/deal-<процесс>.json в контейнере. У core /app/data — volume deal_api_data (файлы-логи
|
||
# переживают пересоздание); у сервисов канал наблюдаемости — docker-логи → promtail (файлы сервисов
|
||
# эфемерны). Уровень — env DEAL_LOG_LEVEL.
|
||
#
|
||
# Frontend: статика монтируется из ../src/frontend/dist (СОБРАТЬ ДО ПОДЪЁМА: cd src/frontend &&
|
||
# npm run build). Конфигурация caddy — deploy/caddy/Caddyfile (см. его шапку про домен/TLS).
|
||
|
||
# Общий hardening прикладных сервисов (BL-IMG-HARDEN): read-only root FS + tmpfs /tmp,
|
||
# запрет новых привилегий и сброс capabilities. Данные/логи — только в именованных volume или tmpfs.
|
||
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
|
||
environment:
|
||
POSTGRES_DB: deal
|
||
POSTGRES_USER: deal
|
||
POSTGRES_PASSWORD: ${DEAL_PG_PASSWORD:?DEAL_PG_PASSWORD не задан (пароль Postgres deal)}
|
||
volumes:
|
||
- deal_pgdata:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U deal -d deal"]
|
||
interval: 5s
|
||
timeout: 3s
|
||
retries: 10
|
||
restart: unless-stopped
|
||
|
||
# MinIO (файлы проектных карточек; бакет deal-files создаётся лениво при первом upload, Ruling 4).
|
||
# БЕЗ host-портов: наружу файлы ходят только через core→caddy. Healthcheck нет: minio-образ не несёт
|
||
# curl/mc; на core падение minio = фолбэк LocalFileStorage (данные — в volume, подъём лечит).
|
||
minio:
|
||
# Тег зафиксирован (как остальные образы); при обновлении — свежий RELEASE-тег из quay.io/minio/minio.
|
||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||
environment:
|
||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER не задан}
|
||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD не задан}
|
||
volumes:
|
||
- deal_minio_data:/data
|
||
command: server /data
|
||
restart: unless-stopped
|
||
|
||
# core (deal-api): HTTP :5080 + gRPC-ингресс telegram-service :5082. Сквозной gRPC-режим
|
||
# (UseLocal=false), как compose.dev.yml; rate limiting/куки-Secure/прокси-заголовки — PROD (см. шапку).
|
||
core:
|
||
build:
|
||
context: ..
|
||
dockerfile: src/core/Deal.Api/Dockerfile
|
||
<<: *service-hardening
|
||
mem_limit: 1g
|
||
cpus: 1.5
|
||
environment:
|
||
ASPNETCORE_ENVIRONMENT: Production
|
||
ASPNETCORE_URLS: http://0.0.0.0:5080
|
||
GRPC_INGRESS_PORT: "5082"
|
||
ConnectionStrings__DealPostgres: "Host=postgres;Port=5432;Database=deal;Username=deal;Password=${DEAL_PG_PASSWORD:?DEAL_PG_PASSWORD не задан}"
|
||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан (service-token gRPC)}
|
||
DEAL_ENCRYPTION_KEY: ${DEAL_ENCRYPTION_KEY:?DEAL_ENCRYPTION_KEY не задан (AES-256-GCM, 32 байта base64)}
|
||
DEAL_OPERATOR_LOGIN: ${DEAL_OPERATOR_LOGIN:-}
|
||
DEAL_OPERATOR_PASSWORD: ${DEAL_OPERATOR_PASSWORD:-}
|
||
DEAL_DEFAULT_AI_BUDGET: ${DEAL_DEFAULT_AI_BUDGET:-}
|
||
# Безопасность PROD (Rulings 5/9/10): лимиты вкл., куки Secure, доверенный прокси (caddy).
|
||
RateLimit__Enabled: "true"
|
||
Cookies__Secure: "true"
|
||
OperatorCookies__Secure: "true"
|
||
ForwardedHeaders__Enabled: "true"
|
||
# Доверенные подсети прокси: приватные docker-сети хоста (caddy — единственный вход в core,
|
||
# host-портов у core нет). Свою подсеть можно уточнить: docker network inspect <сеть>.
|
||
ForwardedHeaders__KnownNetworks__0: 172.16.0.0/12
|
||
Security__AllowedOrigins: ${DEAL_ALLOWED_ORIGINS:?DEAL_ALLOWED_ORIGINS не задан (origin фронта, напр. https://deal.example)}
|
||
# Интеграции — gRPC-режим всего стека (как dev); схемы endpoint'ов — http:// (mTLS off) либо
|
||
# https:// при DEAL_MTLS_ENABLED=1 (см. .env.prod.example).
|
||
Services__Ml__UseLocal: "false"
|
||
Services__Ml__Endpoint: ${DEAL_ML_ENDPOINT:-http://ml-service:5103}
|
||
Services__Ai__UseLocal: "false"
|
||
Services__Ai__Endpoint: ${DEAL_AI_ENDPOINT:-http://ai-service:5102}
|
||
Services__Telegram__UseLocal: "false"
|
||
Services__Telegram__Endpoint: ${DEAL_TELEGRAM_ENDPOINT:-http://telegram-service:5101}
|
||
Services__Storage__Endpoint: ${DEAL_STORAGE_SERVICE_ENDPOINT:-http://storage-service:5104}
|
||
# Файлы — MinIO (внутренний http; TLS minio — вне этапа, при желании Storage__Minio__Secure=true
|
||
# + endpoint https и сертификаты).
|
||
Storage__Minio__Endpoint: minio:9000
|
||
Storage__Minio__AccessKey: ${MINIO_ROOT_USER:?MINIO_ROOT_USER не задан}
|
||
Storage__Minio__SecretKey: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD не задан}
|
||
Storage__Minio__Bucket: deal-files
|
||
Storage__Minio__Secure: "false"
|
||
# Трейсинг OTel → коллектор профиля observability; без коллектора трейсы не экспортируются.
|
||
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
|
||
OTEL_SERVICE_NAME: core
|
||
# mTLS внутреннего gRPC (Ruling 6; пути — /etc/deal/certs, см. volume ниже).
|
||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/core-server.pfx
|
||
DEAL_MTLS_SERVER_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
DEAL_MTLS_CLIENT_CERT_PFX: /etc/deal/certs/deal-client.pfx
|
||
DEAL_MTLS_CLIENT_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
volumes:
|
||
# data/: ключ шифрования (или DEAL_ENCRYPTION_KEY), LocalFileStorage-фолбэк, логи data/logs.
|
||
- deal_api_data:/app/data
|
||
# deploy/certs (scripts/mtls-certs.sh): нужны только при DEAL_MTLS_ENABLED=1.
|
||
- ${DEAL_CERTS_DIR:-./certs}:/etc/deal/certs:ro
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
healthcheck:
|
||
# gRPC-health ингресса :5082 (health освобождён от service-token). Под mTLS — TLS-проба
|
||
# с клиентским сертификатом deal-client и CA (PEM-артефакты генерирует scripts/mtls-certs.sh).
|
||
test: ["CMD-SHELL", "if [ \"$$DEAL_MTLS_ENABLED\" = \"1\" ]; then /bin/grpc_health_probe -addr=localhost:5082 -tls -tls-ca-cert=/etc/deal/certs/ca.pem -tls-client-cert=/etc/deal/certs/deal-client.crt -tls-client-key=/etc/deal/certs/deal-client.key -tls-server-name=localhost; else /bin/grpc_health_probe -addr=localhost:5082; fi"]
|
||
interval: 10s
|
||
timeout: 3s
|
||
retries: 10
|
||
start_period: 15s
|
||
restart: unless-stopped
|
||
|
||
# telegram-service — gRPC-сервер Telegram (:5101; plaintext либо mTLS — как core). Сессии —
|
||
# /data/sessions (volume deal_tg_sessions, AES-GCM ключом DEAL_TELEGRAM_SESSION_KEY, fail-closed).
|
||
telegram-service:
|
||
build:
|
||
context: ..
|
||
dockerfile: src/telegram-service/Deal.Telegram/Dockerfile
|
||
<<: *service-hardening
|
||
mem_limit: 512m
|
||
cpus: 1.0
|
||
environment:
|
||
GRPC_PORT: "5101"
|
||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||
DEAL_TELEGRAM_SESSION_KEY: ${DEAL_TELEGRAM_SESSION_KEY:?DEAL_TELEGRAM_SESSION_KEY не задан (ключ AES-GCM сессий)}
|
||
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}
|
||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/telegram-service-server.pfx
|
||
DEAL_MTLS_SERVER_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
DEAL_MTLS_CLIENT_CERT_PFX: /etc/deal/certs/deal-client.pfx
|
||
DEAL_MTLS_CLIENT_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
volumes:
|
||
- deal_tg_sessions:/data/sessions
|
||
- ${DEAL_CERTS_DIR:-./certs}:/etc/deal/certs:ro
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "if [ \"$$DEAL_MTLS_ENABLED\" = \"1\" ]; then /bin/grpc_health_probe -addr=localhost:5101 -tls -tls-ca-cert=/etc/deal/certs/ca.pem -tls-client-cert=/etc/deal/certs/deal-client.crt -tls-client-key=/etc/deal/certs/deal-client.key -tls-server-name=localhost; else /bin/grpc_health_probe -addr=localhost:5101; fi"]
|
||
interval: 5s
|
||
timeout: 3s
|
||
retries: 10
|
||
restart: unless-stopped
|
||
|
||
# ai-service — gRPC-сервер ИИ-фасада (:5102). Без БД и volume'ов (конфиг провайдера — в теле RPC).
|
||
ai-service:
|
||
build:
|
||
context: ..
|
||
dockerfile: src/ai-service/Deal.Ai/Dockerfile
|
||
<<: *service-hardening
|
||
mem_limit: 512m
|
||
cpus: 1.0
|
||
environment:
|
||
GRPC_PORT: "5102"
|
||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||
DEAL_LOGS_DIR: /tmp/logs
|
||
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
|
||
OTEL_SERVICE_NAME: ai-service
|
||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/ai-service-server.pfx
|
||
DEAL_MTLS_SERVER_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
DEAL_MTLS_CLIENT_CERT_PFX: /etc/deal/certs/deal-client.pfx
|
||
DEAL_MTLS_CLIENT_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
volumes:
|
||
- ${DEAL_CERTS_DIR:-./certs}:/etc/deal/certs:ro
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "if [ \"$$DEAL_MTLS_ENABLED\" = \"1\" ]; then /bin/grpc_health_probe -addr=localhost:5102 -tls -tls-ca-cert=/etc/deal/certs/ca.pem -tls-client-cert=/etc/deal/certs/deal-client.crt -tls-client-key=/etc/deal/certs/deal-client.key -tls-server-name=localhost; else /bin/grpc_health_probe -addr=localhost:5102; fi"]
|
||
interval: 5s
|
||
timeout: 3s
|
||
retries: 10
|
||
restart: unless-stopped
|
||
|
||
# ml-service — gRPC-сервер ML (:5103). Модели — /data/ml (volume deal_ml_data, SQLite на тенанта).
|
||
ml-service:
|
||
build:
|
||
context: ..
|
||
dockerfile: src/ml-service/Deal.Ml/Dockerfile
|
||
<<: *service-hardening
|
||
mem_limit: 768m
|
||
cpus: 1.0
|
||
environment:
|
||
GRPC_PORT: "5103"
|
||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||
DEAL_ML_DATA_DIR: /data/ml
|
||
DEAL_LOGS_DIR: /tmp/logs
|
||
OTEL_EXPORTER_OTLP_ENDPOINT: ${DEAL_OTEL_ENDPOINT:-}
|
||
OTEL_SERVICE_NAME: ml-service
|
||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/ml-service-server.pfx
|
||
DEAL_MTLS_SERVER_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
DEAL_MTLS_CLIENT_CERT_PFX: /etc/deal/certs/deal-client.pfx
|
||
DEAL_MTLS_CLIENT_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
volumes:
|
||
- deal_ml_data:/data/ml
|
||
- ${DEAL_CERTS_DIR:-./certs}:/etc/deal/certs:ro
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "if [ \"$$DEAL_MTLS_ENABLED\" = \"1\" ]; then /bin/grpc_health_probe -addr=localhost:5103 -tls -tls-ca-cert=/etc/deal/certs/ca.pem -tls-client-cert=/etc/deal/certs/deal-client.crt -tls-client-key=/etc/deal/certs/deal-client.key -tls-server-name=localhost; else /bin/grpc_health_probe -addr=localhost:5103; fi"]
|
||
interval: 5s
|
||
timeout: 3s
|
||
retries: 10
|
||
restart: unless-stopped
|
||
|
||
# storage-service — общий gRPC-сервис данных (вложения источников), :5104. Бэкенд — MinIO.
|
||
# Без host-портов; защита — общий service-token (+ mTLS при DEAL_MTLS_ENABLED=1).
|
||
storage-service:
|
||
build:
|
||
context: ..
|
||
dockerfile: src/storage-service/Deal.Storage/Dockerfile
|
||
<<: *service-hardening
|
||
mem_limit: 512m
|
||
cpus: 1.0
|
||
environment:
|
||
GRPC_PORT: "5104"
|
||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||
DEAL_STORAGE_ENDPOINT: minio:9000
|
||
DEAL_STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER:?MINIO_ROOT_USER не задан}
|
||
DEAL_STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD не задан}
|
||
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
|
||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/storage-service-server.pfx
|
||
DEAL_MTLS_SERVER_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
DEAL_MTLS_CLIENT_CERT_PFX: /etc/deal/certs/deal-client.pfx
|
||
DEAL_MTLS_CLIENT_CERT_PASSWORD: ${DEAL_MTLS_CERT_PASSWORD:-}
|
||
volumes:
|
||
- ${DEAL_CERTS_DIR:-./certs}:/etc/deal/certs:ro
|
||
depends_on:
|
||
minio:
|
||
condition: service_started
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "if [ \"$$DEAL_MTLS_ENABLED\" = \"1\" ]; then /bin/grpc_health_probe -addr=localhost:5104 -tls -tls-ca-cert=/etc/deal/certs/ca.pem -tls-client-cert=/etc/deal/certs/deal-client.crt -tls-client-key=/etc/deal/certs/deal-client.key -tls-server-name=localhost; else /bin/grpc_health_probe -addr=localhost:5104; fi"]
|
||
interval: 5s
|
||
timeout: 3s
|
||
retries: 10
|
||
restart: unless-stopped
|
||
|
||
# caddy — edge: наружу только :80/:443. TLS — плейсхолдер tls internal (см. Caddyfile: домен,
|
||
# реальный сертификат/Cloudflare, CSP/HSTS). Статика — ../src/frontend/dist (СОБРАТЬ ДО up).
|
||
caddy:
|
||
image: caddy:2.9.1
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||
- ../src/frontend/dist:/srv:ro
|
||
# /data — локальный CA и сертификаты Caddy (переживают пересоздание контейнера).
|
||
- deal_caddy_data:/data
|
||
- deal_caddy_config:/config
|
||
depends_on:
|
||
core:
|
||
condition: service_healthy
|
||
restart: unless-stopped
|
||
|
||
# ── Observability (Ruling 7/9; метрики — этап 12, пакет A) — ПРОФИЛЬ: --profile observability ──
|
||
# Логи: docker-логи (stdout контейнеров) → promtail → loki → grafana (127.0.0.1:3001).
|
||
# Метрики: /metrics процессов (:9464) → prometheus (127.0.0.1:9090) → grafana (датасорс uid
|
||
# `prometheus`). Доступ — оператору по SSH-туннелю; наружу grafana/prometheus НЕ публикуются.
|
||
# Версии образов — фиксированные (при подъёме обновите до актуальных patch-релизов).
|
||
|
||
loki:
|
||
image: grafana/loki:3.4.2
|
||
profiles: ["observability"]
|
||
command: -config.file=/etc/loki/loki.yml
|
||
volumes:
|
||
- ./observability/loki.yml:/etc/loki/loki.yml:ro
|
||
- deal_loki_data:/loki
|
||
restart: unless-stopped
|
||
|
||
# Prometheus — сбор метрик Deal-процессов (этап 12, пакет A): scrape /metrics каждого процесса
|
||
# (:9464, HTTP/1.1) внутри compose-сети. Конфиг — deploy/observability/prometheus.yml (таргеты
|
||
# core/telegram-service/ai-service/ml-service). UI — только loopback 127.0.0.1:9090 (оператору по
|
||
# SSH-туннелю, как Grafana); наружу порт не публикуется. Retention 15 суток (volume deal_prometheus_data).
|
||
prometheus:
|
||
image: prom/prometheus:v3.5.0
|
||
profiles: ["observability"]
|
||
command:
|
||
- --config.file=/etc/prometheus/prometheus.yml
|
||
- --storage.tsdb.path=/prometheus
|
||
- --storage.tsdb.retention.time=15d
|
||
ports:
|
||
# Только loopback хоста — наружу не публикуется (Ruling 9).
|
||
- "127.0.0.1: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
|
||
restart: unless-stopped
|
||
|
||
promtail:
|
||
image: grafana/promtail:3.4.2
|
||
profiles: ["observability"]
|
||
command: -config.file=/etc/promtail/promtail.yml
|
||
volumes:
|
||
- ./observability/promtail.yml:/etc/promtail/promtail.yml:ro
|
||
# docker.sock — чтение docker-логов всех контейнеров (docker_sd). Rootless-докер: путь сокета
|
||
# свой (напр. /run/user/<uid>/docker.sock) — поправьте монтирование и promtail.yml.
|
||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||
- deal_promtail_data:/var/lib/promtail
|
||
depends_on:
|
||
loki:
|
||
condition: service_started
|
||
restart: unless-stopped
|
||
|
||
grafana:
|
||
image: grafana/grafana:11.5.2
|
||
profiles: ["observability"]
|
||
environment:
|
||
# Пароль задайте в .env.prod перед подъёмом профиля (пусто — grafana не получит admin-пароль).
|
||
GF_SECURITY_ADMIN_USER: ${DEAL_GRAFANA_ADMIN_USER:-admin}
|
||
GF_SECURITY_ADMIN_PASSWORD: ${DEAL_GRAFANA_ADMIN_PASSWORD:-}
|
||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
||
ports:
|
||
# Только loopback хоста — оператору по SSH-туннелю (Ruling 9); наружу не публикуется.
|
||
- "127.0.0.1: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
|
||
restart: unless-stopped
|
||
|
||
# ── Трейсы и ресурсы (observability-стек) ─────────────────────────────────
|
||
# OpenTelemetry Collector — приёмник трейсов Deal-процессов (OTLP gRPC :4317 / HTTP :4318),
|
||
# батчит и перекладывает в Tempo. Наружу порты не публикуются (внутри compose-сети).
|
||
otel-collector:
|
||
image: otel/opentelemetry-collector-contrib:0.160.0
|
||
profiles: ["observability"]
|
||
command: ["--config=/etc/otelcol-contrib/config.yaml"]
|
||
volumes:
|
||
- ./observability/otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro
|
||
depends_on:
|
||
tempo:
|
||
condition: service_started
|
||
restart: unless-stopped
|
||
|
||
# Tempo — хранилище трейсов (OTLP от коллектора). Retention блоков — 7 суток (см. tempo.yml).
|
||
tempo:
|
||
image: grafana/tempo:2.8.1
|
||
profiles: ["observability"]
|
||
command: ["-config.file=/etc/tempo.yml"]
|
||
volumes:
|
||
- ./observability/tempo.yml:/etc/tempo.yml:ro
|
||
- deal_tempo_data:/var/tempo
|
||
restart: unless-stopped
|
||
|
||
# cAdvisor — ресурсы контейнеров (CPU/RAM/сеть/диск); scrape — job cadvisor в prometheus.yml.
|
||
cadvisor:
|
||
image: gcr.io/cadvisor/cadvisor:v0.52.1
|
||
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
|
||
restart: unless-stopped
|
||
|
||
# node-exporter — ресурсы хоста (CPU/RAM/диск/сеть); scrape — job node-exporter в prometheus.yml.
|
||
node-exporter:
|
||
image: prom/node-exporter:v1.9.1
|
||
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)($|/)
|
||
volumes:
|
||
- /proc:/host/proc:ro
|
||
- /sys:/host/sys:ro
|
||
- /:/host/root:ro
|
||
pid: host
|
||
restart: unless-stopped
|
||
|
||
volumes:
|
||
deal_pgdata:
|
||
deal_minio_data:
|
||
deal_tg_sessions:
|
||
deal_ml_data:
|
||
deal_api_data:
|
||
deal_caddy_data:
|
||
deal_tempo_data:
|
||
deal_caddy_config:
|
||
deal_loki_data:
|
||
deal_promtail_data:
|
||
deal_grafana_data:
|
||
deal_prometheus_data:
|