# Конфигурация проекта eav-python

Документ описывает все переменные окружения и способы конфигурирования сервиса.

## Способы конфигурирования

Сервис — это Django-приложение (**Django 4.1 + Django REST Framework**), запускаемое как WSGI (`config.wsgi`) через **uWSGI** (порт `8000`, см. `compose/eav-backend/uwsgi.ini`). Настройки читаются из переменных окружения в `src/config/settings/base.py` и `src/config/settings/production.py`. Разбор выполняется частично через библиотеку [`django-environ`](https://django-environ.readthedocs.io/) (объект `env = environ.Env()`), частично напрямую через `os.getenv`.

Особенности разбора:

- **префикса/делимитера у секций нет** — каждая настройка задаётся плоской переменной окружения (напр. `DJANGO_POSTGRES_HOST`, `KAFKA_HOST`, `YC_S3_BUCKET_NAME`);
- **`.env` не загружается автоматически** — в коде нет вызова `environ.Env.read_env()` / `load_dotenv`, хотя `python-dotenv` присутствует в зависимостях. Переменные нужно экспортировать в окружение самому (напр. `set -a && . ./.env && set +a`) либо задавать через `--env`/манифесты k8s. Файл `.env` при этом в `.gitignore`;
- **выбор набора настроек** задаётся `DJANGO_SETTINGS_MODULE`: `config.settings.production` (боевой набор с БД, CORS, JWT), `config.settings.test` (только `base`), либо `config.settings.local` (по умолчанию в `manage.py`, в репозитории отсутствует, `.gitignore`);
- **часть переменных читается только в `production.py`** — БД (`DJANGO_POSTGRES_*`) и JWT (`JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`); в `base.py`/`test.py` их нет.

Отдельного конфиг-файла (yaml/toml) у приложения нет.

Источники переменных по способам запуска:

| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (manage.py / uWSGI) | Переменные окружения процесса (`.env` нужно экспортировать вручную) |
| Локально (docker-compose) | `docker-compose.yml`: блок `environment` сервиса `backend` + образ `postgres` (timescaledb-postgis) |
| Kubernetes (Helm, репозиторий приложения) | `.helm/values-<env>.yaml`: блоки `backend.deployment.envs` (обычные значения) и `backend.deployment.secrets` (из k8s-секретов через `secretKeyRef`); шаблон `templates/server.yaml`, роутинг — `templates/mesh-config.yaml` (Istio VirtualService) |
| Kubernetes (infra, Flux/Kustomize) | `infra/iac/apps/eav/base/helmrelease.yaml` (universal-chart): секреты инъектируются Vault-агентом (`vault.hashicorp.com/agent-inject-*`) и экспортируются в окружение в `args`; настройки `production.py` монтируются из `django-configmap` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci`, переменные пайплайна в `workflow.rules` |

Способы запуска процессов:

| Процесс | Точка входа | Назначение |
| --- | --- | --- |
| HTTP API | `compose/eav-backend/entrypoint.sh` → `uwsgi --ini uwsgi.ini` (`config.wsgi`, порт 8000) | REST API |
| Миграции | `entrypoint.sh` → `python3 manage.py migrate` (выполняется перед стартом uWSGI) | Миграции БД |
| Kafka-продюсер | `config/kafka.py` (инициализируется при импорте, если `KAFKA_ENABLED`) | Публикация событий EAV в топики |

Порядок запуска в контейнере (`entrypoint.sh`): сначала `manage.py migrate`, затем `uwsgi` (оба под `opentelemetry-instrument`).

## Переменные приложения

Дефолт `—` означает, что значения по умолчанию в коде нет.

### Django / приложение

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_SETTINGS_MODULE` | string | `config.settings.local` (в `manage.py`); в контейнере — `config.settings.production` | Какой набор настроек Django загружать |
| `DJANGO_DEBUG` | bool | `False` | Режим отладки Django (в `production.py` жёстко `False`) |
| `DJANGO_SECRET_KEY` | string | (захардкоженный дефолт) | Секретный ключ Django. В проде обязателен свой |
| `SERVICE_NAME` | string | `eav` | Имя сервиса (в `base.py`); в OTEL-секции дефолт `eav.eav-backend` |
| `VERSION` | string | `1.0.0` | Версия приложения |

### Database — PostgreSQL (только `config.settings.production`)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_POSTGRES_HOST` | string | — | Хост PostgreSQL |
| `DJANGO_POSTGRES_PORT` | int | `6432` | Порт PostgreSQL (в infra-манифесте — `5432`) |
| `DJANGO_POSTGRES_DATABASE` | string | — | Имя базы данных |
| `DJANGO_POSTGRES_USER` | string | — | Пользователь БД |
| `DJANGO_POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |

> Engine — `django.db.backends.postgresql`. В `docker-compose.yml` поднимается `timescale/timescaledb-postgis` (проекту нужны расширения PostGIS/ltree).

### Auth / JWT (только `config.settings.production`)

Используются два механизма аутентификации (`REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES`): `ZitadelJWTAuthentication` (заголовок `Identity`) и `rest_framework_simplejwt` (RS512).

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SIMPLE_JWT_ISSUER` | string | `django` | Значение claim `iss` (проверяется при верификации токена) |
| `JWT_PRIVATE_KEY` | string (PEM) | — | Приватный RSA-ключ (подпись). Экранированные `\n` заменяются на переводы строк. Обязателен |
| `JWT_PUBLIC_KEY` | string (PEM) | — | Публичный RSA-ключ (проверка). Экранированные `\n` заменяются на переводы строк. Обязателен |

> `SIMPLE_JWT`: алгоритм `RS512`, `ACCESS_TOKEN_LIFETIME` 5 мин, `REFRESH_TOKEN_LIFETIME` 1 день, тип заголовка `Bearer`, claim пользователя — `user_id`.

### S3 — Yandex Object Storage (`base.py`, boto3/django-storages)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `YC_S3_ACCESS_KEY_ID` | string | `None` | Access key |
| `YC_S3_SECRET_ACCESS_KEY` | string | `None` | Secret key |
| `YC_S3_BUCKET_NAME` | string | `None` | Бакет по умолчанию |
| `YC_S3_ENDPOINT_URL` | string | `None` | Эндпоинт S3 |

> `DEFAULT_FILE_STORAGE`/`STATICFILES_STORAGE` — `storages.backends.s3boto3.S3Boto3Storage`, `AWS_DEFAULT_ACL=public-read`.

### Kafka (`base.py`, `config/kafka.py`)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_ENABLED` | bool | `True` | Включить реального продюсера (иначе `MockProducer` — события не отправляются) |
| `KAFKA_HOST` | string | `""` | Адрес брокера (`bootstrap_servers`) |
| `KAFKA_USERNAME` | string | `platform` | Пользователь SASL |
| `KAFKA_PASSWORD` | string | `""` | Пароль SASL |
| `KAFKA_SSL_CAFILE` | string | `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt` | CA-сертификат для TLS |
| `SASL_MECHANISM` | string | `SCRAM-SHA-512` | Механизм SASL |
| `SECURITY_PROTOCOL` | string | `SASL_SSL` | Протокол безопасности Kafka |
| `ASSETS_TOPIC` | string | `assets_broadcast_test` | Топик рассылки по ассетам |

Топики событий EAV:

| Переменная | Значение по умолчанию |
| --- | --- |
| `KAFKA_TOPIC_ATTRIBUTE_CREATED` | `eav.attribute.created.v1` |
| `KAFKA_TOPIC_ATTRIBUTE_UPDATED` | `eav.attribute.updated.v1` |
| `KAFKA_TOPIC_ATTRIBUTE_DELETED` | `eav.attribute.deleted.v1` |
| `KAFKA_TOPIC_VALUE_OPTION_CREATED` | `eav.value_option.created.v1` |
| `KAFKA_TOPIC_VALUE_OPTION_DELETED` | `eav.value_option.deleted.v1` |

### OpenTelemetry (`base.py`)

Блок трейсинга активируется, только если задана переменная `USE_OTEL` (проверяется через `os.getenv('USE_OTEL', False)` — истинно при любом непустом значении). Используется `django-otel-tools`; при включении в начало `MIDDLEWARE` добавляется `OtelMiddleware`.

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_OTEL` | bool/string | `False` | Включить трейсинг и OTEL-логгер |
| `SERVICE_NAME` | string | `eav.eav-backend` | Имя сервиса в трейсах |
| `TRACER_ENDPOINT` | string | `localhost:4375` | Адрес OTLP-коллектора |
| `USE_INSECURE` | bool/string | `False` | Небезопасное (без TLS) подключение к коллектору |
| `ENVIRONMENT` | string | `prod` | Атрибут ресурса `environment` |
| `MODULE` | string | `eav` | Атрибут ресурса `module` |
| `TEAM` | string | `platform_team` | Атрибут ресурса `team` |
| `COMPONENT` | string | `backend` | Атрибут ресурса `component` |

## Переменные из Helm-чарта (`.helm/values-<env>.yaml`)

Обычные значения задаются в блоке `backend.deployment.envs` для каждого окружения (`stage`/`preprod`/`production`): `DJANGO_SETTINGS_MODULE`, `USE_OTEL`, `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE`, `ENVIRONMENT`, `KAFKA_HOST`, `ASSETS_TOPIC` (различаются адресами коллектора/брокера и именами топиков).

Значения из секретов (блок `secrets`, монтируются как env через `secretKeyRef`):

| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
| --- | --- | --- |
| `DJANGO_POSTGRES_HOST` | `yc-pg-secret` | `host` |
| `DJANGO_POSTGRES_DATABASE` | `yc-pg-secret` | `database` |
| `DJANGO_POSTGRES_PORT` | `yc-pg-secret` | `port` (только preprod) |
| `DJANGO_POSTGRES_USER` | `yc-pg-secret` | `user` |
| `DJANGO_POSTGRES_PASSWORD` | `yc-pg-secret` | `password` |
| `DJANGO_CLICKHOUSE_HOST` | `yc-ch-secret` | `host` |
| `DJANGO_CLICKHOUSE_DATABASE` | `yc-ch-secret` | `database` |
| `DJANGO_CLICKHOUSE_USER` | `yc-ch-secret` | `user` |
| `DJANGO_CLICKHOUSE_PASSWORD` | `yc-ch-secret` | `password` |
| `YC_S3_ACCESS_KEY_ID` | `yc-s3-secret` | `key_id` |
| `YC_S3_SECRET_ACCESS_KEY` | `yc-s3-secret` | `access_key` |
| `YC_S3_BUCKET_NAME` | `yc-s3-secret` | `storage_bucket_name` |
| `YC_S3_ENDPOINT_URL` | `yc-s3-secret` | `endpoint_url` |
| `JWT_PRIVATE_KEY` | `jwt-secret` | `private_key` |
| `JWT_PUBLIC_KEY` | `jwt-secret` | `public_key` |
| `KAFKA_USERNAME` | `kafka-secret` / `yc-kafka-secret` | `username` |
| `KAFKA_PASSWORD` | `kafka-secret` / `yc-kafka-secret` | `password` |
| `KAFKA_HOST` | `yc-kafka-secret` | `host` (prod/stage) |

Помимо env, чарт монтирует CA-сертификаты: PostgreSQL (`yc-pg-certificate` → `~/.postgresql/root.crt`) и Yandex Internal Root CA (`yc-ch-certificate` → `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt`, тот же путь, что в `KAFKA_SSL_CAFILE`), а также конфиг clickhouse-client.

## Переменные в infra-манифесте (Flux/Kustomize, `infra/iac/apps/eav`)

В отличие от Helm-чарта приложения, боевой деплой Sarex использует Vault-инъекцию (`base/helmrelease.yaml`, чарт `universal-chart`). Секреты рендерятся Vault-агентом в файлы `/vault/secrets/*` и экспортируются в окружение в `args` контейнера перед запуском `entrypoint.sh`:

| Переменная(ые) | Источник (Vault path) |
| --- | --- |
| `DJANGO_POSTGRES_HOST/PORT/DATABASE/USER/PASSWORD` | `secrets/data/postgresql/apps/eav` |
| `YC_S3_ENDPOINT_URL/BUCKET_NAME/ACCESS_KEY_ID/SECRET_ACCESS_KEY` | `secrets/data/minio/apps/eav` |
| `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` | `secrets/data/vault/common/rsa_keys` |

Прямо в `env` деплоймента задаются `KAFKA_ENABLED=False`, `ASSETS_TOPIC=sarex`, `DJANGO_SETTINGS_MODULE=config.settings.production`. Файл `production.py` монтируется из `django-configmap` (переопределяет `production.py` из образа; в нём `DEBUG=True`, `ALLOWED_HOSTS=['*']`, свои CORS/CSRF-домены и имена cookie `eav-sessionid`/`eav-csrftoken`).

## Замечания и потенциальные проблемы

- Приложение **не загружает `.env` автоматически** (нет `read_env`/`load_dotenv`). `python-dotenv` установлен, но не используется в настройках — переменные нужно экспортировать в окружение самому.
- Переменные БД (`DJANGO_POSTGRES_*`) и JWT (`JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`) читаются **только** в `config.settings.production`. При `test`/`base` их отсутствие не мешает старту, но БД по умолчанию не сконфигурирована.
- `JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY` в `production.py` читаются через `env.str(...)` **без дефолта** — их отсутствие приводит к ошибке старта. В infra-варианте (`django-configmap`) используется `get_env_variable` с тем же требованием.
- `DJANGO_CLICKHOUSE_*` присутствуют в Helm-секретах, но **кодом приложения не читаются** (в текущих настройках ClickHouse не используется) — это подготовка/наследие инфраструктуры.
- `KAFKA_ENABLED`: при ложном значении используется `MockProducer` — события EAV в Kafka не публикуются (так сделано в infra-деплое: `KAFKA_ENABLED=False`). Значение разбирается `django-environ` как bool.
- Флаги OTEL (`USE_OTEL`, `USE_INSECURE`) читаются через `os.getenv(..., False)` и трактуются как истинные при **любой непустой строке**, включая `"False"`. Чтобы отключить — переменную нужно не задавать вовсе.
- `SERVICE_NAME` определяется дважды: как имя приложения (`base.py`, дефолт `eav`) и как имя сервиса в OTEL (дефолт `eav.eav-backend`) — фактически одна и та же переменная окружения.
- В `docker-compose.yml` захардкожен пароль БД (`zealot096`) — только для локального окружения.

## Минимальный набор для локального запуска (`config.settings.production`)

- `DJANGO_SETTINGS_MODULE=config.settings.production`
- `DJANGO_POSTGRES_HOST`, `DJANGO_POSTGRES_PORT`, `DJANGO_POSTGRES_DATABASE`, `DJANGO_POSTGRES_USER`, `DJANGO_POSTGRES_PASSWORD`
- `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY` (обязательны; можно тестовую RSA-пару)
- `KAFKA_ENABLED=False` (чтобы не поднимать брокер) либо `KAFKA_HOST`/`KAFKA_USERNAME`/`KAFKA_PASSWORD`
- при работе с файлами: `YC_S3_ACCESS_KEY_ID`, `YC_S3_SECRET_ACCESS_KEY`, `YC_S3_BUCKET_NAME`, `YC_S3_ENDPOINT_URL`
- `USE_OTEL` — не задавать (иначе включится трейсинг)

Готовые значения-примеры для всех переменных приведены в `.env.example`.
