# 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//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: