iac/apps/mapper/CONFIGURATION.md

194 lines
16 KiB
Markdown
Raw Permalink 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.

# Конфигурация проекта mapper (flows mapper)
Документ описывает все переменные окружения и способы конфигурирования сервиса `mapper` — mini-HTTP-сервиса, который объединяет (мапит) данные сервисов документации (`documentations`) и процессов (`flows`), а также заметок (`notes`) и Django-бэкенда Sarex.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/app/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/).
В отличие от многих сервисов, здесь **нет единого корневого префикса и нет вложенного делимитера** (`env_nested_delimiter`). Вместо этого каждая секция описана отдельным классом `BaseSettings` со своим `env_prefix` (задаётся во вложенном классе `Config`):
| Класс | `env_prefix` | Секция |
| --- | --- | --- |
| `Settings` | — (без префикса) | Корневые настройки (`API_PREFIX`) |
| `LoggerSettings` | `LOG_` | Логирование |
| `RedisSettings` | `REDIS_` | Кеш Redis |
| `DocumentationSettings` | `DOCUMENTATION_` | HTTP-клиент сервиса документации |
| `FlowSettings` | `FLOW_` | HTTP-клиент сервиса процессов |
| `DjangoSettings` | `DJANGO_` | HTTP-клиент Django-бэкенда |
| `NoteSettings` | `NOTE_` | HTTP-клиент сервиса заметок |
`DocumentationSettings`, `FlowSettings`, `DjangoSettings` и `NoteSettings` наследуются от общего класса `AsyncSessionManager` (поля `host`, `timeout`, `retries`), поэтому у каждого из них одинаковый набор из трёх переменных: `<PREFIX>_HOST`, `<PREFIX>_TIMEOUT`, `<PREFIX>_RETRIES`.
Отдельного конфиг-файла (yaml/toml) у приложения нет. Приложение **не загружает `.env` автоматически**`config.py` не задан `env_file`, зависимости `python-dotenv` нет) — переменные нужно экспортировать в окружение самому, напр. `set -a && . ./.env && set +a`, либо пробрасывать через контейнер/оркестратор.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (uvicorn/gunicorn) | Переменные окружения процесса. Redis поднимается через `docker-compose.yaml` (только сервис `redis`) |
| Контейнер | `Dockerfile``entrypoint.sh`: `gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app.main:app` |
| Kubernetes (Helm, из репозитория сервиса) | `.helm/values.yaml`, блок `universal-chart.services.backend.envs`; деплой из `.gitlab-ci.yml` |
| Kubernetes (GitOps, инфра-репозиторий) | `iac/apps/mapper/*`: Kustomize-база `base/deployment.yaml` (env + секреты Vault) и Flux `HelmRelease` в overlay'ах `brusnika-*` |
Точки входа:
| Команда | Назначение |
| --- | --- |
| `uvicorn main:app` / `python app/main.py` | Локальный запуск (в `main.py` порт `8002`, host `0.0.0.0`) |
| `gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app.main:app` | Прод-запуск (`entrypoint.sh`, порт `8000`) |
Стек: Python 3.10 (`python:3.10-slim-buster`), FastAPI, httpx (асинхронные клиенты), Redis (кеш), PyJWT (разбор токенов).
## Переменные приложения
Дефолт `—` означает, что значение обязательно (иначе ошибка старта). Все дефолты ниже соответствуют коду `config.py`.
### App (класс `Settings`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `API_PREFIX` | string | `/api/v1` | Префикс маршрутов API |
### Logger (`LOG_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `LOG_LEVEL` | string | `INFO` | Уровень логирования (имя уровня `logging`; при неизвестном значении используется `INFO`) |
| `LOG_FORMAT` | string | `[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s` | Формат строки лога |
### Redis (`REDIS_*`)
Кеширует JSON-ответы внешних сервисов (по ключу `"{user_id}_{url}"`).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `REDIS_USE` | bool | `True` | Включить кеш. При `True` на старте выполняется `PING` (падение при недоступном Redis) |
| `REDIS_HOST` | string | `localhost` | Хост Redis |
| `REDIS_PORT` | int | `6379` | Порт Redis |
| `REDIS_DB` | int | `0` | Номер базы Redis |
| `REDIS_EXPIRE_DAYS` | int | `1` | TTL записей кеша в днях (в секундах — `expire_days * 24 * 3600`) |
### HTTP-клиенты внешних сервисов
Все четыре клиента наследуют `AsyncSessionManager` (`host`, `timeout`, `retries`). Клиент httpx создаётся с `verify=False` (проверка TLS-сертификата отключена) и транспортом с числом ретраев `retries`. Токены пробрасываются заголовками `Authorization` (всегда) и `Identity` (в режиме Zitadel).
| Секция / префикс | Назначение | Дефолт `HOST` |
| --- | --- | --- |
| `DOCUMENTATION_*` | Сервис документации (диски, документы, бандлы) | `https://stage-api.sarex.io/documentations/api/v1` |
| `FLOW_*` | Сервис процессов (flows, review-данные) | `https://stage-api.sarex.io/flows/api/v1` |
| `DJANGO_*` | Django-бэкенд Sarex (target-links) | `https://stage.sarex.io/api` |
| `NOTE_*` | Сервис заметок (notes) | `https://stage-api.sarex.io/notes/api/v1` |
Для каждого — три переменные (пример для `FLOW`):
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `FLOW_HOST` | string | см. выше | Базовый URL сервиса |
| `FLOW_TIMEOUT` | int | `30` | Таймаут запроса (сек) |
| `FLOW_RETRIES` | int | `3` | Число повторов транспорта httpx |
## Аутентификация
Аутентификация выполняется в `src/app/dependensies.py` (`get_user_data`) на основе заголовков запроса и **без проверки подписи токена** (`jwt.decode(..., options={"verify_signature": False})`). Публичный ключ не используется, отдельных переменных для ключа нет.
| Условие | Режим | Как извлекается `user_id` |
| --- | --- | --- |
| Есть заголовки `Authorization` и `Identity` | `zitadel` | Из payload `Identity`-токена, поле `urn:zitadel:iam:user:metadata.user_id` (base64) |
| Есть только `Authorization` | `sarex` | Из payload основного токена, поле `user_id` |
| Заголовков нет | — | `401 Unauthorized` |
## Переменные инфраструктуры и сборки
Не читаются кодом приложения, но участвуют в сборке/запуске.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `SERVICE_NAME` | `.gitlab-ci.yml` | Имя сервиса (`mapper`), используется как `CHART_NAME` |
| `DOCKERFILE_PATH` | `.gitlab-ci.yml` | Путь к Dockerfile |
| `BUILD_ARGS`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` | Аргументы сборки / источник триггера (`app`) |
| `IMAGE_NAME`, `CI_COMMIT_SHA`, `CI_PROJECT_URL`, `CI_JOB_URL`, `CI_PROJECT_NAMESPACE` | `.gitlab-ci.yml` (`HELM_SET_ARGS`) | Прокидываются в universal-chart (образ, commit, ссылки, owner) |
## Переменные из Helm-чарта репозитория сервиса (`.helm/values.yaml`)
Чарт зависит от `universal-chart` (`oci://cr.yandex/.../charts`, версия `0.1.7`). Переменные приложения задаются в блоке `services.backend.envs` с разбивкой по окружениям (`_default`/`stage`/`preprod`/`production`).
| Переменная | `stage` | `preprod` | `production` |
| --- | --- | --- | --- |
| `DOCUMENTATION_HOST` | `https://stage-api.sarex.io/documentations/api/v1` | `https://api.preprod.sarex.io/documentations/api/v1` | `https://api.sarex.io/documentations/api/v1` |
| `FLOW_HOST` | `https://stage-api.sarex.io/flows/api/v1` | `https://api.preprod.sarex.io/flows/api/v1` | `https://api.sarex.io/flows/api/v1` |
| `DJANGO_HOST` | `https://stage.sarex.io/api` | `https://preprod.sarex.io/api` | `https://lk.sarex.io/api` |
| `NOTE_HOST` | `https://stage-api.sarex.io/notes/api/v1` | `https://api.preprod.sarex.io/notes/api/v1` | `https://api.sarex.io/notes/api/v1` |
| `REDIS_USE` | `0` | `0` | `0` |
| `TIMEOUT` | `120` | `120` | `120` |
Прочие параметры чарта (не переменные приложения): `deployment.*` (имя, порт `8000`, `replicaCount` 1/1/3/3, ресурсы, `probes.liveness/readiness`**отключены**), `image.name` (`cr.yandex/.../mapper`), `service.*` (ClusterIP, порт `8000`), `imagePullSecrets` (`dockerhub`), `labels.monitoring=prometheus`.
## Переменные из инфра-репозитория (`iac/apps/mapper`)
GitOps-деплой через Kustomize + Flux, namespace `mapper`. Здесь же лежит настоящий документ.
Структура:
| Путь | Назначение |
| --- | --- |
| `base/` | Базовый Kustomize (`namespace`, `serviceaccount` `mapper-vault`, `deployment`, `service`) |
| `yc-k8s-test/` | Overlay поверх `base` (патч `replicas: 1`) |
| `brusnika-stage/` | Flux `HelmRelease` (universal-chart), хосты `test.sarex.brusnika.tech`, `imagePullSecrets: dockerhub` |
| `brusnika-prod/` | Flux `HelmRelease` (universal-chart), хосты `cde.brusnika.ru`, `imagePullSecrets: regcred` |
Обычные env в `base/deployment.yaml` (production-хосты Sarex):
| Переменная | Значение |
| --- | --- |
| `DOCUMENTATION_HOST` | `https://api.sarex.io/documentations/api/v1` |
| `FLOW_HOST` | `https://api.sarex.io/flows/api/v1` |
| `DJANGO_HOST` | `https://lk.sarex.io/api` |
| `NOTE_HOST` | `https://api.sarex.io/notes/api/v1` |
| `REDIS_USE` | `0` |
| `TIMEOUT` | `120` |
Секреты монтируются через **HashiCorp Vault Agent** (аннотации `vault.hashicorp.com/*`, роль `mapper`) как файлы в `/vault/secrets/*`, которые перед стартом экспортируются в окружение (`set -a && . /vault/secrets/... && set +a`):
| Файл секрета | Источник (Vault path) | Экспортируемые переменные |
| --- | --- | --- |
| `mapper-django-auth` | `secrets/data/vault/common/django_auth` | `MAPPER_DJANGO_TOKEN` |
| `mapper-db` | `secrets/data/postgresql/apps/mapper` | `MAPPER_DB_USER`, `MAPPER_DB_PASSWORD`, `MAPPER_DB_HOST`, `MAPPER_DB_PORT`, `MAPPER_DB_NAME` |
| `mapper-rabbitmq` | `secrets/data/rabbitmq/apps/mapper` | `MAPPER_RABBITMQ_VHOST`, `MAPPER_RABBITMQ_USERNAME`, `MAPPER_RABBITMQ_PASSWORD`, `MAPPER_RABBITMQ_HOST`, `MAPPER_RABBITMQ_PORT` |
| `mapper-s3` | `secrets/data/minio/apps/mapper` | `MAPPER_S3_ENDPOINT`, `MAPPER_S3_REGION`, `MAPPER_S3_BUCKET`, `MAPPER_S3_ACCESS_KEY_ID`, `MAPPER_S3_SECRET_ACCESS_KEY` |
| `mapper-kafka` | `secrets/data/kafka/apps/mapper` | `MAPPER_KAFKA_BOOTSTRAP_SERVERS`, `MAPPER_KAFKA_SECURITY_PROTOCOL`, `MAPPER_KAFKA_SASL_MECHANISM`, `MAPPER_KAFKA_USERNAME`, `MAPPER_KAFKA_PASSWORD` |
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml@apps-business`) и переключает окружение по ветке/тегу:
| Условие | STAND | Namespace | CHART_VERSION |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `platform` | `0.0.1-stage` |
| ветка `master` | `preprod` | `mapper-preprod` | `0.0.1-preprod` |
| тег (`CI_COMMIT_TAG`) | `production` | `mapper-prod` | `0.0.1-prod` |
| merge request | — (сборка образа отключена) | — | — |
Стадия `test`: job `linter` (`flake8 src/app`, `max-line-length=120`) и `typechecker` (`mypy src/app` с `types-redis`; `disallow_untyped_defs=True`).
## Замечания и потенциальные проблемы
- **`TIMEOUT` не читается приложением.** В Helm/Kustomize задаётся `TIMEOUT=120`, но клиенты читают `DOCUMENTATION_TIMEOUT`/`FLOW_TIMEOUT`/`DJANGO_TIMEOUT`/`NOTE_TIMEOUT` (каждый со своим префиксом). Без префикса переменная игнорируется — реальный таймаут остаётся `30`. Чтобы поднять таймаут, задавайте `<PREFIX>_TIMEOUT`.
- **Секреты Vault не используются кодом.** `MAPPER_DB_*`, `MAPPER_RABBITMQ_*`, `MAPPER_S3_*`, `MAPPER_KAFKA_*`, `MAPPER_DJANGO_TOKEN` монтируются и экспортируются в окружение (`base/deployment.yaml`), но текущая версия приложения ни PostgreSQL, ни RabbitMQ, ни S3, ни Kafka, ни `MAPPER_DJANGO_TOKEN` **не читает**`config.py` таких настроек нет). Похоже, инфраструктура заготовлена наперёд либо унаследована из шаблона.
- **Кеш отключён во всех окружениях (`REDIS_USE=0`).** При этом в `get_response` (`utils.py`) при не-200 ответе апстрима и выключенном кеше возвращается `None`, а роутер отдаёт `400 Bad Request`. То есть при выключенном Redis запасного кеша нет.
- **Подпись JWT не проверяется** (`verify_signature=False`) ни в режиме `zitadel`, ни в `sarex`. Доверие к токену — на сетевом слое (Istio/ingress). Публичный ключ не настраивается.
- **TLS-проверка апстримов отключена** (`httpx.AsyncClient(verify=False)`) для всех четырёх клиентов.
- **Нет healthcheck-эндпоинта.** Пробы `liveness`/`readiness` в чарте выключены — это согласовано.
- **Порты различаются:** локально `main.py` слушает `8002`, в контейнере gunicorn — `8000` (проброшен в k8s Service).
- **`docker-compose.yaml`** поднимает только Redis (redis-stack-server); само приложение в compose не описано.
## Минимальный набор для локального запуска
Поднять Redis (`docker compose up redis`) либо задать `REDIS_USE=False`, затем `uvicorn main:app` из `src`. Минимально стоит задать (у остальных есть рабочие дефолты для stage):
- `REDIS_USE` (`False`, если Redis не поднят) и при необходимости `REDIS_HOST`/`REDIS_PORT`
- при работе против нестандартных стендов — `DOCUMENTATION_HOST`, `FLOW_HOST`, `DJANGO_HOST`, `NOTE_HOST`
- `LOG_LEVEL` (по желанию)
Готовые значения-примеры приведены в `.env.example`.