iac/apps/subscriptions/CONFIGURATION.md

201 lines
20 KiB
Markdown
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.

# Конфигурация sarex-subscriptions (api + cron-задачи)
Документ описывает все переменные окружения и способы конфигурирования сервиса `sarex-subscriptions` — Django-приложения рассылки уведомлений (email/Telegram) по подпискам.
Компоненты сервиса используют **один и тот же** модуль настроек `config.settings.production` и общий набор переменных окружения:
- **api** — HTTP-сервис (uwsgi, `config.wsgi:application`), REST API подписок/получателей/шаблонов; отдаёт статику и медиа через S3;
- **cron `run_notifications`** (`subscription-periodic`) — периодическая рассылка отложенных уведомлений;
- **cron `run_immediately_notifications`** (`subscription-immediately`) — рассылка немедленных уведомлений.
Обе cron-задачи — это management-команды Django (`server/apps/notification/management/commands`), запускаемые как отдельные `CronJob` из того же образа.
## Способы конфигурирования
Сервис настраивается **через переменные окружения** (`os.getenv` в `config/settings/base.py` и `config/settings/production.py`) плюс жёстко заданные в коде настройки. Отдельной библиотеки разбора конфига (как cleanenv в Go-сервисах) здесь нет — используется штатный механизм Django settings.
Часть настроек **захардкожена** в `base.py` и не выносится в окружение: `SECRET_KEY`, `DEBUG`, `ALLOWED_HOSTS`, `CORS_*`, REST Framework, локаль/таймзона (`ru-ru`, `Europe/Moscow`). В `production.py` `DEBUG=False` и заданы фиксированные `ALLOWED_HOSTS`/`CORS_ALLOWED_ORIGINS`.
Обязательные переменные не помечены тегами (как в Go), но при их отсутствии процесс падает при старте (подключение к БД) либо при обращении к соответствующему сервису (клиенты system-log/user-service, хранилище S3).
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (docker-compose) | `docker-compose.yml` поднимает контейнер `db` (`postgis/postgis`) и сервисы `api`/`migrations`. Значения БД (`DATABASE_USER/PASSWORD/NAME`, `ALLOWED_HOST_EMAIL`) подставляются из окружения/файла `.env` рядом с compose. api собирается из `compose/server/Dockerfile` и стартует через `entrypoint.sh` |
| Kubernetes — собственный Helm-чарт репозитория | `.helm/values.yaml`: зависимость от `universal-chart`. Блоки `services.api.envs` (обычные значения, per-env `_default/stage/preprod/production`) и `services.api.secretEnvs` (из k8s-секретов). Плюс `cronjobs.periodic` / `cronjobs.immediately` — шаблоны `CronJob` в `.helm/templates/`, наследующие `envs`/`secretEnvs` api |
| Kubernetes — этот infra-репозиторий (`iac/apps/subscriptions`) | `base/` — kustomize-манифесты с инъекцией секретов через **HashiCorp Vault** (annotations `vault.hashicorp.com/*`), обычные переменные заданы инлайн в `env:`. Оверлеи: `yc-k8s-test` (base + postgresql), `brusnika-stage` / `brusnika-prod` (Flux `HelmRelease` на `universal-chart`, блоки `envs`/`secretEnvs`) |
| CI/CD (GitLab) | `.gitlab-ci.yml` подключает шаблоны `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`); в `workflow.rules` задаются переменные пайплайна (`SERVICE_NAME`, `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `IMAGE_NAME` и т.п.) |
**Миграции БД (api).** Выполняются **в entrypoint контейнера api** перед стартом uwsgi: `python manage.py migrate --settings=config.settings.production` (`compose/server/entrypoint.sh`). Cron-задачи миграций не выполняют. Локально в `docker-compose.yml` отдельный сервис `migrations` вызывает `makemigrations` (генерация миграций, не применение).
---
## api (`sarex-subscriptions`)
Переменные читаются в `config/settings/base.py` (S3, OTEL) и `config/settings/production.py` (БД и внешние сервисы; `production.py` импортирует всё из `base.py`).
### База данных (PostgreSQL / PostGIS)
Движок — `core.db.backends.postgis` (GeoDjango, требуется PostGIS). Все пять переменных обязательны — без них подключение к БД не поднимется.
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
| --- | --- | --- | --- | --- |
| `DATABASE_HOST` | string | да | — | Хост PostgreSQL |
| `DATABASE_PORT` | string | да | — | Порт PostgreSQL |
| `DATABASE_NAME` | string | да | — | Имя базы данных |
| `DATABASE_USER` | string | да | — | Пользователь БД |
| `DATABASE_PASSWORD` | string | да | — | Пароль пользователя БД |
### Хранилище S3 (static + media)
`STATICFILES_STORAGE` и `DEFAULT_FILE_STORAGE` = `storages.backends.s3boto3.S3Boto3Storage`, `AWS_DEFAULT_ACL="public-read"`. Значения по умолчанию отсутствуют (`os.getenv` без default → `None`), поэтому для корректной отдачи статики/медиа креды фактически обязательны.
| Переменная | Тип | Обяз. | Назначение |
| --- | --- | --- | --- |
| `YC_S3_ACCESS_KEY_ID` | string | да | Access key (`AWS_ACCESS_KEY_ID`) |
| `YC_S3_SECRET_ACCESS_KEY` | string | да | Secret key (`AWS_SECRET_ACCESS_KEY`) |
| `YC_S3_BUCKET_NAME` | string | да | Имя бакета (`AWS_STORAGE_BUCKET_NAME`) |
| `YC_S3_ENDPOINT_URL` | string | да | Endpoint S3 (`AWS_S3_ENDPOINT_URL`) |
### Трейсинг (OpenTelemetry)
Блок OTEL в `base.py` (и обёртка WSGI в `wsgi.py`) включается по `os.getenv('USE_OTEL', False)`. **Важно:** проверяется истинность строки, а не её значение — любая непустая строка (в т.ч. `"False"`, `"0"`) включает трейсинг. Задействует пакет `django_otel_tools`.
| Переменная | Тип | По умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_OTEL` | bool-строка | не задана (выкл.) | Включает OTEL-трейсинг, otel-логгер и `OtelMiddleware` |
| `SERVICE_NAME` | string | `subscriptions` | Имя сервиса в трейсах |
| `TRACER_ENDPOINT` | string | `localhost:4375` | Адрес OTLP-коллектора |
| `USE_INSECURE` | bool-строка | не задана (выкл.) | Небезопасное (без TLS) подключение к коллектору; та же логика истинности строки, что и `USE_OTEL` |
> Захардкожено (не через окружение): `SECRET_KEY`, `DEBUG` (`False` в production), `ALLOWED_HOSTS`, `CORS_ALLOWED_ORIGINS`, `CORS_ALLOW_ALL_ORIGINS=True`.
---
## cron-задачи (`run_notifications`, `run_immediately_notifications`)
Обе команды используют тот же модуль настроек и, помимо БД, обращаются к внешним сервисам для сбора данных и рассылки. Переменные читаются в `production.py` и потребляются в `server/apps/notification/management/commands/*.py`.
### Внешние сервисы и рассылка
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
| --- | --- | --- | --- | --- |
| `SYSTEM_LOG_HOST` | string | да | — | Базовый URL сервиса system-log (логирование событий) |
| `USER_SERVICE_HOST` | string | да | — | Базовый URL сервиса пользователей (Django/ЛК) |
| `USER_SERVICE_LOGIN` | string | нет | `""` | Логин для авторизации в user-service |
| `USER_SERVICE_PASSWORD` | string | нет | `""` | Пароль для авторизации в user-service |
| `ALLOWED_HOST_EMAIL` | string | нет | `https://lk.sarex.io` | Базовый URL для формирования ссылок в письмах (`context_processing/document.py`) |
### Email — Mailgun
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
| --- | --- | --- | --- | --- |
| `IS_MAILGUN_USE` | bool-строка | нет | `True` | Использовать Mailgun для отправки писем |
| `MAILGUN_API_KEY` | string | нет | `""` | API-ключ Mailgun |
| `MAILGUN_BASE_URL` | string | нет | `https://api.mailgun.net/v3/mg.sarex.io` | Базовый URL Mailgun API |
| `MAILGUN_EMAIL_FROM` | string | нет | `hello@sarex.io` | Адрес отправителя |
### Email — SMTP
SMTP-ветка активируется, только если **заданы оба** `SMTP_EMAIL_HOST` и `SMTP_EMAIL_PORT` (по умолчанию `None` — SMTP отключён).
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
| --- | --- | --- | --- | --- |
| `SMTP_EMAIL_HOST` | string | нет | `None` | Хост SMTP-сервера |
| `SMTP_EMAIL_PORT` | string | нет | `None` | Порт SMTP-сервера |
| `SMTP_EMAIL_FROM` | string | нет | `hello@sarex.io` | Адрес отправителя |
### Telegram
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
| --- | --- | --- | --- | --- |
| `IS_USE_TELEGRAM` | bool-строка | нет | `True` | Использовать Telegram-рассылку |
| `TELEGRAM_BOT_TOKEN` | string | нет | `""` | Токен Telegram-бота (`settings.TELEGRAM_BOT_TOKEN`) |
> Значения `IS_*` читаются как строки Django-настроек: непустая строка истинна. Чтобы выключить канал, инфраструктурные манифесты задают `"false"`/`"0"` — но с точки зрения Python это тоже непустые строки, поэтому фактическое поведение зависит от того, как значение интерпретируется в коде команды (см. замечания).
---
## Инфраструктурные и вспомогательные переменные
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `API_ADDRESS` | `base/*.yaml`, `.helm/values.yaml`, brusnika-оверлеи | Задаётся в манифестах, но **кодом не читается** — порт uwsgi фиксирован в `uwsgi.ini` (`http = 0.0.0.0:8000`) |
| `DJANGO_SETTINGS_MODULE` | `entrypoint.sh`, `wsgi.py`, `manage.py` | Модуль настроек Django (`config.settings.production` в проде) |
| `NEXUS_USERNAME`, `NEXUS_PASSWORD` | `compose/server/Dockerfile` (build-arg), `.gitlab-ci.yml` | Доступ к приватному PyPI (`nexus.infra.sarex.io`) при сборке образа |
| `SERVICE_NAME`, `DOCKERFILE_PATH`, `BUILD_ARGS`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` (`variables`) | Параметры сборки/пайплайна |
| `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `IMAGE_NAME`, `ENABLE_BUILD_IMAGE` | `.gitlab-ci.yml` (`workflow.rules`) | Параметры пайплайна `generic/common-ci` (деплой по окружениям stage/preprod/production) |
> Обратите внимание: `SERVICE_NAME` встречается в двух ролях — как переменная приложения (имя сервиса в OTEL-трейсах) и как переменная CI (имя сервиса для сборки/чарта). Значения задаются в разных местах и не связаны между собой.
---
## Деплой из этого репозитория (`iac/apps/subscriptions`)
Здесь используется **kustomize** (не собственный Helm-чарт сервиса). Секреты БД и S3 инъектируются агентом **Vault** и подгружаются в окружение процесса до старта:
```
set -a
[ -f /vault/secrets/subscriptions-postgresql ] && . /vault/secrets/subscriptions-postgresql
[ -f /vault/secrets/subscriptions-minio ] && . /vault/secrets/subscriptions-minio
set +a
exec /server/entrypoint.sh
```
### `base/`
- `backend-deployment.yaml` — обычные переменные инлайн в `env:` (`API_ADDRESS`, `SYSTEM_LOG_HOST`, `USER_SERVICE_HOST`, `IS_USE_TELEGRAM=false`, `IS_MAILGUN_USE=0`, `SMTP_EMAIL_FROM/HOST/PORT`). Vault-шаблоны формируют `DATABASE_HOST/PORT/NAME/USER/PASSWORD` (из `secrets/data/postgresql/apps/subscriptions`, БД `subscriptions_db`) и `YC_S3_ACCESS_KEY_ID/SECRET_ACCESS_KEY/BUCKET_NAME/ENDPOINT_URL` (из `secrets/data/minio/apps/subscriptions`).
- `backend-service.yaml`, `namespace.yaml` (`istio-injection: enabled`), `serviceaccount.yaml` (`subscriptions-vault`).
- `kustomization.yaml` собирает namespace, serviceaccount, deployment и service.
### Оверлеи
- **`yc-k8s-test`** — `../base` + `postgresql.yaml` (HelmRelease `postgresql-contour`: создаёт БД `subscriptions_db`, пользователя `subscriptions`, расширения `ltree`/`pg_stat_statements`/`postgis`/`timescaledb`, восстановление из дампа).
- **`brusnika-stage`** / **`brusnika-prod`** — Flux `HelmRelease` на `universal-chart` (v0.1.7). Переменные — в `envs` (`DATABASE_HOST/PORT/NAME`, `API_ADDRESS`, `SYSTEM_LOG_HOST`, `USER_SERVICE_HOST`, `IS_USE_TELEGRAM=false`, `IS_MAILGUN_USE=0`, `SMTP_*`), секреты — в `secretEnvs` (`postgres-secret`: `username`/`password`; `yc-s3-secret`: `key_id`/`access_key`/`storage_bucket_name`/`endpoint_url`). Отличаются только `DATABASE_HOST` (`postgres-service` в prod vs `192.168.2.45` в stage).
> В этих kustomize/brusnika-манифестах **не задаются** `USE_OTEL`, `TELEGRAM_BOT_TOKEN`, `MAILGUN_API_KEY`, `USER_SERVICE_LOGIN/PASSWORD`, `ALLOWED_HOST_EMAIL` — используются дефолты из кода. Telegram и Mailgun выключены (`IS_USE_TELEGRAM=false`, `IS_MAILGUN_USE=0`), рассылка идёт по SMTP.
---
## Отличия от Helm-чарта репозитория (`.helm/values.yaml`)
Собственный чарт сервиса (`.helm/`) — это **другой** путь деплоя, с более широким набором переменных, чем kustomize/brusnika здесь:
- дополнительно задаёт `ALLOWED_HOST_EMAIL`, `USE_OTEL=True`, `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE=True` (per-env), а также секреты `MAILGUN_API_KEY` (`mailgun-cred`) и `TELEGRAM_BOT_TOKEN` (`telegram-bot-secret`);
- `IS_USE_TELEGRAM=true`, монтирует `uwsgi.ini` через ConfigMap;
- определяет `CronJob` `subscription-periodic` (`0,30 * * * *`, `run_notifications`) и `subscription-immediately` (`* * * * *`, `run_immediately_notifications`), наследующие `envs`/`secretEnvs` api.
---
## Замечания и потенциальные проблемы
- **`manage.py` указывает на несуществующий модуль настроек.** По умолчанию `manage.py` задаёт `DJANGO_SETTINGS_MODULE=config.settings.local`, но модуля `local.py` в репозитории нет (есть только `base.py` и `production.py`). Поэтому management-команды нужно запускать с явным `--settings=config.settings.production` (как это и делают entrypoint и cron-задачи). Локальный сервис `migrations` в `docker-compose.yml` вызывает `makemigrations` **без** `--settings` — команда упадёт из-за отсутствия `local`.
- **`USE_OTEL`/`USE_INSECURE` работают по истинности строки.** `os.getenv('USE_OTEL', False)` возвращает строку; любое непустое значение (включая `"False"`, `"0"`) включает трейсинг. Чтобы выключить — переменную нужно **не задавать вовсе**, а не ставить `False`.
- **Каналы рассылки `IS_MAILGUN_USE` / `IS_USE_TELEGRAM` — тоже строки.** Значения по умолчанию — `True` (Python-объект), но из окружения приходит строка; поведение зависит от того, как значение проверяется в коде команды. При настройке важно учитывать это (в манифестах используют `"false"`/`"0"`).
- **S3 обязателен для статики/медиа.** Хранилища заданы как S3 без файлового фолбэка; при отсутствии `YC_S3_*` operations со статикой/медиа будут падать, хотя сам процесс поднимется.
- **`SECRET_KEY` захардкожен** в `base.py` и не выносится в окружение — для продакшена это стоит вынести в секрет.
- **Расхождения имён БД между окружениями:** infra `base` и postgresql-чарт используют `subscriptions_db`, brusnika-оверлеи — `subscriptions`. При подключении важно использовать значение конкретного окружения.
- **Два деплой-пути расходятся по набору переменных** (kustomize/brusnika vs `.helm`): OTEL, Telegram-токен, Mailgun-ключ и `ALLOWED_HOST_EMAIL` присутствуют только в `.helm`. Это стоит учитывать при переносе окружения.
---
## Минимальный набор для запуска
**api** (uwsgi, миграции при старте):
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`
- `YC_S3_ACCESS_KEY_ID`, `YC_S3_SECRET_ACCESS_KEY`, `YC_S3_BUCKET_NAME`, `YC_S3_ENDPOINT_URL`
- при необходимости — `USE_OTEL` (+ `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE`)
**cron `run_notifications` / `run_immediately_notifications`**:
- блок БД (как у api)
- `SYSTEM_LOG_HOST`, `USER_SERVICE_HOST` (+ `USER_SERVICE_LOGIN`, `USER_SERVICE_PASSWORD` при авторизации)
- канал рассылки: `IS_MAILGUN_USE` + `MAILGUN_API_KEY` **или** `SMTP_EMAIL_HOST` + `SMTP_EMAIL_PORT`; при Telegram — `IS_USE_TELEGRAM` + `TELEGRAM_BOT_TOKEN`
- при необходимости — `ALLOWED_HOST_EMAIL` (ссылки в письмах)
См. пример значений в `.env.example` рядом с этим файлом.