# 🏭 Инфраструктура ## Docker Compose (локальная разработка) ### Сервисы ```yaml services: backend: # Spring Boot (Java 17), порт 8080 frontend: # Apache httpd:alpine, порт 80 db: # PostgreSQL alpine3.23, порт 5432 ``` ### Сеть Все сервисы работают в Docker-сети `proxy` (external). Перед первым запуском: ```bash docker network create proxy ``` ### Переменные окружения Файл `.env` в корне проекта: ```env POSTGRES_USER=myuser POSTGRES_PASSWORD=replace-with-local-password POSTGRES_DB=app_db JWT_SECRET=replace-with-random-jwt-secret-minimum-32-bytes JWT_ACCESS_TOKEN_TTL=15m JWT_REFRESH_TOKEN_TTL=7d ``` `POSTGRES_PASSWORD` обязателен для `docker compose up`: пароль не хранится в `compose.yaml`. `JWT_SECRET` должен быть случайным секретом длиной минимум 32 байта. В продакшене секреты задаются через Kubernetes Secret, а не через коммитимые файлы. Встроенного JWT fallback в приложении нет. Отсутствующее, короткое или шаблонное значение останавливает запуск; профиль `production` также требует Secure refresh-cookie. Локальный `backend/tenants.json` тоже не коммитится. Для ручного запуска backend вне Docker можно взять `backend/tenants.example.json`, создать рядом `tenants.json` и подставить локальный пароль. ### Dockerfile (Backend) Backend собирается через multi-stage сборку Maven: 1. Этап сборки: `maven:3.9-eclipse-temurin-17` → `mvn package` 2. Этап запуска: `eclipse-temurin:17-jre-alpine` → `java -jar app.jar` ### Dockerfile (Frontend) ```dockerfile FROM httpd:alpine COPY . /usr/local/apache2/htdocs/ RUN chown -R www-data:www-data /usr/local/apache2/htdocs/ ``` --- ## Kubernetes (продакшн) ### Расположение конфигурации Файлы Kubernetes манифестов: `../k8s/` ### Ключевые ресурсы | Ресурс | Тип | Описание | |--------|-----|----------| | `backend` | Deployment | Spring Boot приложение | | `frontend` | Deployment | Apache httpd | | `tenants-secret` | Внешний Secret | JSON-список tenant-подключений; значения отсутствуют в манифестах | ### JWT настройки `app-config` задаёт TTL access/refresh-токенов и признак Secure-cookie: - `JWT_ACCESS_TOKEN_TTL=15m` - `JWT_REFRESH_TOKEN_TTL=7d` - `JWT_REFRESH_COOKIE_SECURE=true` `app-secret` задаёт `JWT_SECRET`. Этот Secret не создаётся файлами `../k8s/`: его заранее предоставляет внешний secret manager или оператор. Deployment явно включает профиль `production`, поэтому небезопасная cookie-конфигурация останавливает startup. ### Secrets для приложения и тенантов Обязательные внешние объекты: | Secret | Назначение | |---|---| | `app-secret` | JWT и fallback credentials backend | | `tenants-secret` | Полный `tenants.json` с credentials tenant-БД | | `otel-postgres-secret` | Credentials PostgreSQL receivers для OTel Collector | | `gitea-registry` | Доступ Kubernetes к registry | Secret `tenants-secret` монтируется в pod backend по пути `/config/tenants.json`. При добавлении тенанта через API: 1. `DatabaseController` обновляет in-memory DataSource 2. `KubernetesTenantSecretUpdater` обновляет Secret через Kubernetes API 3. `TenantConfigWatcher` на остальных pod подхватывает смонтированное изменение (каждые 30 сек) Kubernetes-клиент доверяет только service-account CA и проверяет hostname API server. RBAC ограничен объектом `tenants-secret`. Скрипт `../k8s/deploy.sh` проверяет наличие объектов и ключей до rollout, не читая и не печатая их значения. Создание, ротация, отзыв скомпрометированных значений и очистка истории описаны в [`SECURITY_RUNBOOK.md`](SECURITY_RUNBOOK.md). Эти действия требуют полномочий оператора и не выполняются автоматически. ### Обновление backend ```bash kubectl rollout restart deployment backend -n magistr ``` --- ## Caddy (реверс-прокси) **Расположение:** `../caddy-proxy/` для локальной разработки, в продакшене - отдельный сервис В продакшене Caddy обрабатывает входящий трафик для `*.zuev.company`: - Автоматическое получение TLS-сертификатов (Let's Encrypt) - Маршрутизация `/api/*` → backend:8080 - Маршрутизация статики → frontend:80 --- ## CI/CD (Gitea Actions) ### Пайплайн сборки Docker-образов Расположение: `.gitea/workflows/docker-build.yaml` Основные шаги: 1. Checkout кода 2. Login в Docker Registry 3. Build + Push образов (`backend`, `frontend`) 4. Генерация меток через `docker/metadata-action` --- ## Мониторинг (SigNoz + OpenTelemetry) ### Архитектура мониторинга ```mermaid graph LR Backend["Spring Boot"] -->|OTLP gRPC| Collector["OTel Collector"] Frontend["JS (otel.js)"] -->|OTLP HTTP| Collector Collector --> SigNoz["SigNoz"] Collector -->|"Метрики PostgreSQL"| PgExporter["pg_exporter"] ``` ### Интеграция Backend Backend отправляет через OpenTelemetry: - **Логи** — через Logback + OTLP exporter - **Трейсы** — автоинструментация Spring Boot - **Метрики** — JVM метрики, HTTP метрики Tenant ID добавляется в: - MDC (логи): `MDC.put("tenant.id", tenant)` - Span атрибуты: `Span.current().setAttribute("tenant.id", tenant)` ### Интеграция Frontend Файл `admin/js/otel.js` — клиентская телеметрия: - Метрики производительности страниц - Трейсы пользовательских действий PostgreSQL receivers collector получают endpoint и credentials только из `otel-postgres-secret`; ConfigMap collector содержит лишь `${env:...}` ссылки. ### Дашборды SigNoz - JVM Dashboard (Heap, GC, Threads) - PostgreSQL Dashboard (Connections, Queries) - HTTP Dashboard (Requests, Latency, Errors)