Проект разделен на два репозитория
This commit is contained in:
+25
-10
@@ -11,17 +11,21 @@
|
||||
| [`arch-00-glossary.md`](arch-00-glossary.md) | Канонические имена и семантика enum/lifecycle: сущности, поля, id, enum, бакеты S3, env |
|
||||
| [`arch-01-system-architecture.md`](arch-01-system-architecture.md) | Общая архитектура: компоненты, сценарии, потоки данных, безопасность |
|
||||
| [`arch-02-api-contracts.md`](arch-02-api-contracts.md) | Реестр API-контрактов, realtime, гостевая сессия, OpenAPI, аудит |
|
||||
| [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md) | Требования к Docker Compose, nginx, сетям, TLS и rate limits |
|
||||
| [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md) | Требования к Docker Compose, сетям и published ports. Детальный контракт nginx — arch-08 |
|
||||
| [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md) | `.env` (infra), таблица `app_settings`, значения service-token переменных, типы файлов |
|
||||
| [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md) | Правила разработки модулей отдельными агентами |
|
||||
| [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md) | Безопасность VM и деплоя: OS-роли, SSH, sudo/systemd, секреты, контейнеры, сеть и lockdown |
|
||||
| [`arch-07-observability.md`](arch-07-observability.md) | Контракт наблюдаемости: Collector, JSON-логи, корреляция, redaction, sampling, SLO, SigNoz. Реализация VM — `module-09-observability-vm1.md` / `module-09-observability-vm2.md` |
|
||||
| [`arch-08-nginx.md`](arch-08-nginx.md) | Контракт корневого nginx: TLS/ACME, request id, internal 404, access log, reload. Реализация VM — `module-03-nginx-vm1.md` / `module-03-nginx-vm2.md`. Сети и ports — arch-03 |
|
||||
| [`arch-09-redis.md`](arch-09-redis.md) | Контракт Redis: не source of truth, формат ключей, TTL, Lua, AOF/ACL. Реализация VM — `module-04-redis-vm1.md` / `module-04-redis-vm2.md` |
|
||||
| [`arch-10-deployment.md`](arch-10-deployment.md) | Контракт развёртывания: VPC/SG, PG/S3, роли `deploy`, TLS процедура, cutover. Runbook VM — `module-10-deployment-vm1.md` / `module-10-deployment-vm2.md`. OS-роли — arch-06 |
|
||||
|
||||
## Как читать
|
||||
|
||||
1. Начните с **arch-01** — общая картина и зафиксированные решения MVP.
|
||||
2. При работе с API — **arch-02**; с Compose/nginx — **arch-03**; с настройками — **arch-04**; с VM, SSH, правами деплоя, секретами и host/container hardening — **arch-06**.
|
||||
2. При работе с API — **arch-02**; с Compose/сетями — **arch-03**; с контрактом nginx — **arch-08** и профильная спецификация VM; с Redis — **arch-09** и профильная спецификация VM; с настройками — **arch-04**; с VM, SSH, правами деплоя, секретами и host/container hardening — **arch-06**; с rollout stages/gates — **arch-10** и профильный runbook VM; с telemetry/логами/traces — **arch-07** и профильная спецификация VM.
|
||||
3. Спорные **имена** полей, id, enum, бакетов и базовая семантика enum/lifecycle — **arch-00**. Лимиты и правила реализации остаются в профильных arch-*.
|
||||
4. Перед разработкой модуля — **arch-05**, релевантные разделы arch-01/arch-02 и arch-06, если меняются deployment, сети, volumes, capabilities или секреты.
|
||||
4. Перед разработкой модуля — **arch-05**, релевантные разделы arch-01/arch-02 и arch-06, если меняются deployment, сети, volumes, capabilities или секреты. Наблюдаемость сервиса — arch-07 плюс `module-09-observability-vm1.md` или `module-09-observability-vm2.md`. Nginx — arch-08 плюс `module-03-nginx-vm1.md` или `module-03-nginx-vm2.md`. Redis — arch-09 плюс `module-04-redis-vm1.md` или `module-04-redis-vm2.md`. Раскатка VM — arch-10 плюс `module-10-deployment-vm1.md` или `module-10-deployment-vm2.md`.
|
||||
|
||||
## Приоритет документов
|
||||
|
||||
@@ -31,9 +35,13 @@
|
||||
2. **arch-01** — границы сервисов, сценарии, sync, безопасность.
|
||||
3. **arch-02** — HTTP-контракты и направление вызовов.
|
||||
4. **arch-06** — безопасность размещения на VM, OS-роли, SSH, secrets delivery, host/container hardening и production-деплой.
|
||||
5. **arch-03** — Compose, сети контейнеров, nginx и TLS.
|
||||
6. **arch-04** — non-secret env, secret references, `app_settings`, публичные DTO.
|
||||
7. **arch-05** — процесс разработки.
|
||||
5. **arch-03** — Compose, сети контейнеров и published ports.
|
||||
6. **arch-08** — контракт nginx: TLS/ACME, request id, internal 404, access log, reload. Routing matrix — профильный module-03 VM spec.
|
||||
7. **arch-04** — non-secret env, secret references, `app_settings`, публичные DTO.
|
||||
8. **arch-09** — контракт Redis: не source of truth, ключи, TTL, Lua, AOF/ACL. Карта ключей — профильный module-04 VM spec.
|
||||
9. **arch-07** — контракт telemetry: JSON-поля, resource attributes, redaction, sampling, Collector, SigNoz. Не бизнес-лимиты сервисов.
|
||||
10. **arch-10** — контракт развёртывания: VPC/SG, PG/S3, stages/gates, cutover. Не ослабляет arch-06. Процедуры VM — профильный module-10 runbook.
|
||||
11. **arch-05** — процесс разработки.
|
||||
|
||||
Профильные спецификации модулей уточняют реализацию внутри этих границ. Если границы не позволяют эффективно реализовать модуль, то агент, разрабатывающий модуль, может предложить внести изменения в архитектуру.
|
||||
|
||||
@@ -42,15 +50,19 @@
|
||||
- Имена полей, бакетов, статусов → **arch-00**, затем синхронизация arch-*.
|
||||
- Endpoint или auth → **arch-02**, при необходимости arch-01/arch-03.
|
||||
- Новая интеграция → сначала **arch-02**.
|
||||
- Compose, nginx, TLS → **arch-03**.
|
||||
- Compose, сети контейнеров, published ports → **arch-03**.
|
||||
- TLS/ACME nginx, request id, internal 404, access log → **arch-08**, затем профильный `module-03-nginx-vm1.md` или `module-03-nginx-vm2.md`.
|
||||
- Redis ключи/TTL/ACL/AOF, запрет очереди и OTP store → **arch-09**, затем профильный `module-04-redis-vm1.md` или `module-04-redis-vm2.md`.
|
||||
- VM, SSH, sudo, systemd-деплой, secret delivery, container/host hardening → **arch-06**, затем синхронизация arch-03/arch-04 и runbook.
|
||||
- Rollout stages, SG/DNS, PG/S3 gates, Safety cutover порядок → **arch-10**, затем профильный `module-10-deployment-vm1.md` или `module-10-deployment-vm2.md`.
|
||||
- JSON-лог, `request_id`/`trace_id`, redaction, sampling, Collector, SigNoz → **arch-07**, затем профильный `module-09-observability-vm1.md` или `module-09-observability-vm2.md`.
|
||||
|
||||
## В бэклоге (не MVP)
|
||||
|
||||
| Тема | Где зафиксировано |
|
||||
|---|---|
|
||||
| Доставка документов компании из Bitrix24 в приложение (`bitrix-sync` → `api-backend`, уведомление клиента) | [`!Backlog.md`](../../HAN_chat/!Backlog.md), п. 9; arch-01 — заглушка UI «Документы» |
|
||||
| Интеграция с SMS-провайдерами (отправка OTP, отключение `KEYCLOAK_OTP_MOCK_*`) | Спецификация: [`module-11-idgtl-sms.md`](../modules/module-11-idgtl-sms.md) (доставка через Direct SMS API; проверка OTP — локально в Keycloak) |
|
||||
| Интеграция с SMS-провайдерами (отправка OTP, отключение `KEYCLOAK_OTP_MOCK_*`) | Спецификация: [`module-11-idgtl-sms.md`](../VM1_app/documentation/module-11-idgtl-sms.md) (доставка через Direct SMS API; проверка OTP — локально в Keycloak) |
|
||||
|
||||
## Каноническое размещение production-контуров
|
||||
|
||||
@@ -66,7 +78,6 @@
|
||||
| # | Пробел | Статус |
|
||||
|---|---|---|
|
||||
| G8 | Явный список `is_public=true` для ключей `app_settings` | Отложить до оформления сервисов; seed в модуле `database` |
|
||||
| G9 | GRANT-модель `bitrix_sync_user` на `han_app`: таблицы, колонки, read/write границы | Закрыто в `module-07-bitrix-sync.md` §13; точный SQL реализуется migrations и проходит negative permission tests |
|
||||
| G10 | Полный DTO `GET /api/v1/public/app-config` и мэппинг `setting_key → response field` | Уточнить при оформлении OpenAPI `api-backend` |
|
||||
| G11 | Версионирование API/WS: deprecation policy, срок поддержки v1, `ws_protocol_version` | Уточнить перед публичным релизом API |
|
||||
| G12 | Масштабирование realtime: Redis Pub/Sub, sticky sessions, backpressure при нескольких репликах `api-backend` | Post-MVP / перед горизонтальным масштабированием |
|
||||
@@ -83,6 +94,10 @@
|
||||
runbook.
|
||||
- Изменение service seed/schema/embedded artifact или feature flag, влияющего
|
||||
на edge route/allow-list → arch-04 + профильный module + rollout/rollback
|
||||
gates в module-10.
|
||||
gates в arch-10 и профильном `module-10-deployment-vm1.md` / `module-10-deployment-vm2.md`.
|
||||
- Новый термин / enum → arch-00, затем поиск по arch-*.
|
||||
- Изменение JSON-лога, resource attributes, redaction, sampling, Collector pipeline или SigNoz endpoint → arch-07 (+ профильный module-09 VM spec, если меняется состав сервисов/алертов этой машины).
|
||||
- Изменение общего контракта nginx (TLS/ACME, request id, internal 404, reload) → arch-08 (+ профильный module-03 VM spec, если меняется routing/allow-list этой машины).
|
||||
- Изменение общего контракта Redis (формат ключей, TTL, Lua, AOF/ACL, запрет очереди) → arch-09 (+ профильный module-04 VM spec, если меняется карта ключей этой машины).
|
||||
- Изменение общего rollout (SG/DNS, PG/S3 gates, cutover порядок) → arch-10 (+ профильный module-10 VM runbook).
|
||||
- Закрытие пробела → убрать из «Открытые пробелы» и отразить решение в arch-*.
|
||||
|
||||
@@ -20,11 +20,11 @@ HAN Chat - приложение для мигрантов, где стартов
|
||||
- Мультиязычность в первом релизе не нужна, но тексты должны храниться по мнемоникам для будущих переводов.
|
||||
- Среда на первом этапе одна и проектируется как боевая.
|
||||
- Вложения чата MVP: **только изображения и PDF** — см. [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), «Разрешённые типы файлов чата».
|
||||
- SMS OTP вводится поэтапно: до production rollout действует явный mock (`KEYCLOAK_OTP_MOCK_ENABLED=true`); целевой real mode — Keycloak генерирует/локально проверяет OTP и создаёт durable order в `sms-service`, а worker асинхронно вызывает i-Digital Direct. Контракт и gates — [`module-11-idgtl-sms.md`](../modules/module-11-idgtl-sms.md).
|
||||
- SMS OTP вводится поэтапно: до production rollout действует явный mock (`KEYCLOAK_OTP_MOCK_ENABLED=true`); целевой real mode — Keycloak генерирует/локально проверяет OTP и создаёт durable order в `sms-service`, а worker асинхронно вызывает i-Digital Direct. Контракт и gates — [`module-11-idgtl-sms.md`](../VM1_app/documentation/module-11-idgtl-sms.md).
|
||||
- Популярный вопрос при выборе **автоматически отправляется как сообщение**; если пользователь не авторизован — сначала согласия и OTP, затем отправка.
|
||||
- Notification Center v1 использует два контура: G — общие read-only гостевые кампании, P — персональные уведомления с состоянием в App DB. Виды, CTA, кнопки и палитра задаются каталогом данных.
|
||||
- Инструкция `install_app` всегда открывается во внешней новой вкладке; iframe/модалка для неё не используется.
|
||||
- Перечень таблиц и миграций App DB проектирует модуль `database` (и владельцы схем других сервисов); arch фиксирует только **разделение схем** PostgreSQL и контракты между сервисами.
|
||||
- Перечень таблиц и миграций схемы `han_app` проектирует `module-01-api-backend` и его migration owner; владельцы остальных сервисов проектируют свои схемы. Arch фиксирует только **разделение схем** PostgreSQL и контракты между сервисами.
|
||||
|
||||
## Пользовательские сценарии
|
||||
|
||||
@@ -243,7 +243,7 @@ Frontend не должен:
|
||||
|
||||
### Bitrix24 sync service
|
||||
|
||||
Отвечает за асинхронную двустороннюю синхронизацию данных между App DB и Битрикс24 CRM по контракту [`../modules/module-07-bitrix-sync.md`](../modules/module-07-bitrix-sync.md):
|
||||
Отвечает за асинхронную двустороннюю синхронизацию данных между App DB и Битрикс24 CRM по контракту [`module-07-bitrix-sync.md`](../VM2_services/documentation/module-07-bitrix-sync.md):
|
||||
|
||||
- **канонический mapping** и его историю в `bitrix_sync.entity_external_mapping`; App DB не хранит CRM Contact ID;
|
||||
- **App DB → Bitrix24:** durable workflow для `contact.map_or_create`, `contact.update`, `contact.deactivate`;
|
||||
@@ -287,7 +287,7 @@ Frontend не должен:
|
||||
|
||||
Confidential **backend client** Keycloak (client credentials) в MVP **не обязателен**: S2S между нашими сервисами идёт по service tokens, не через Keycloak. Client можно завести заранее в realm как optional для будущих admin/ops сценариев.
|
||||
|
||||
### Nginx Reverse Proxy
|
||||
### Nginx Reverse Proxy (целевая двух-VM топология)
|
||||
|
||||
Отвечает за:
|
||||
|
||||
@@ -296,11 +296,11 @@ Confidential **backend client** Keycloak (client credentials) в MVP **не об
|
||||
- редирект HTTP на HTTPS (на веб-домене; для выделенного API-домена HTTP не допускается — см. «Принципы безопасности»);
|
||||
- маршрутизацию `/api/*` в api-backend (включая `WS /api/v1/realtime`);
|
||||
- маршрутизацию `/auth/*` или выделенного auth-домена в Keycloak;
|
||||
- маршрутизацию публичных `/bitrix/*` endpoint в `bitrix-local-app`;
|
||||
- маршрутизацию `/bitrix/sync/*` webhook endpoint в `bitrix-sync`;
|
||||
- на nginx ВМ1 — маршрутизацию только `/bitrix/handler`, `/bitrix/install`, `/bitrix/placement` в `bitrix-local-app`;
|
||||
- на отдельном public nginx ВМ2 — маршрутизацию только exact `/bitrix/sync/webhook/contact` и `/bitrix/sync/webhook/alert` в `bitrix-sync`; ВМ1 эти paths не проксирует;
|
||||
- маршрутизацию только exact `POST /callbacks/idgtl/sms` в `sms-service` по HTTPS, с allowlist актуального IP Direct и без логирования Basic Authorization;
|
||||
- защиту internal endpoint `bitrix-local-app` через private network или `nginx allowlist`;
|
||||
- отсутствие публичной маршрутизации к `message-safety` — сервис доступен только из внутренней Docker-сети;
|
||||
- отсутствие публичной маршрутизации к `message-safety`: `api-backend` ВМ1 вызывает private nginx ВМ2 `:8443` по HTTPS с internal CA и service token; Docker DNS/HTTP допустим только внутри ВМ2 за gateway;
|
||||
- передачу `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Request-ID` (если клиент не прислал `X-Request-ID` — nginx **генерирует** UUID и прокидывает upstream);
|
||||
- базовые лимиты размера запроса и timeout;
|
||||
- грубые edge rate limits по IP, route и зоне риска;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
## Правила связности
|
||||
|
||||
- Любой новый endpoint, webhook, worker-contract или внешний вызов сначала добавляется в этот файл; при появлении профильного документа модуля-владельца — дублируется там для детализации реализации.
|
||||
- Публичные пользовательские API находятся под `/api/v1`; internal API не публикуются наружу через `nginx`.
|
||||
- Internal HTTP API между backend-сервисами используют единую маску: **`/internal/{service_mnemonic}/v1/{resource}`**, где `{service_mnemonic}` — короткое имя владельца endpoint (см. [`arch-00-glossary.md`](arch-00-glossary.md), «Мнемоники internal API»). Health-check остаётся на `/health/*`.
|
||||
- Публичные пользовательские API находятся под `/api/v1`; public listeners nginx `80/443` не публикуют `/internal/*`. Канонический production ingress Safety — отдельный private listener nginx ВМ2 `:8443` с internal CA, source allow-list и service token; это не public route и не Docker HTTP fallback.
|
||||
- Internal HTTP API между backend-сервисами используют единую маску: **`/internal/{service_mnemonic}/v1/{resource}`**, где `{service_mnemonic}` — короткое имя владельца endpoint (см. [`arch-00-glossary.md`](arch-00-glossary.md), «Мнемоники internal API»). Утверждённое исключение — target Message Safety `/internal/safety/v2/*`; legacy `/internal/safety/v1/*` остаётся только stub до cutover и на private `:8443` не публикуется. Health-check остаётся на `/health/*`.
|
||||
- OpenAPI 3.1 обязателен для HTTP-контрактов `api-backend`, `message-safety`, `bitrix-sync` и `bitrix-local-app` — файлы `{service}/openapi.yaml` в репозитории сервиса (см. раздел «OpenAPI»); для Bitrix24 REST фиксируются используемые методы и payload-мэппинг.
|
||||
- Все service-to-service вызовы передают `X-Request-ID` и по возможности W3C `traceparent`.
|
||||
- Frontend передаёт **`X-Ux-Session-Id`** во всех JWT-запросах к `api-backend`, когда UX-сессия активна (рекомендуется для аналитики и логов; **не** является auth). `session-start` и `consents` требуют JWT.
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
## Service tokens (internal API)
|
||||
|
||||
Все internal endpoint (`/internal/*`) доступны **только** из Docker/VPC-сети и требуют service token. Endpoint не публикуются через `nginx` (исключение — ops внутри VPC).
|
||||
Все internal endpoint (`/internal/*`) доступны **только** из Docker/VPC-сети и требуют service token. Public listeners nginx их не публикуют. Private nginx ВМ2 `:8443` является утверждённым ingress для Safety hot path и allow-listed ops endpoint внутри VPC.
|
||||
|
||||
| Переменная | Кто проверяет | Кто передаёт | Endpoint | Заголовок |
|
||||
|---|---|---|---|---|
|
||||
@@ -114,6 +114,7 @@
|
||||
| `403` | `forbidden` | Доступ запрещён и ресурс не скрывается | нет |
|
||||
| `404` | `not_found` | Ресурс не существует или принадлежит другому пользователю | нет |
|
||||
| `409` | `idempotency_key_reused` | Тот же `Idempotency-Key` с другим fingerprint | нет |
|
||||
| `409` | `resource_state_conflict` | JWT валиден, но локальный `UserIdentity` ещё не создан через bootstrap, либо ресурс находится в несовместимом lifecycle state | после bootstrap либо изменения state |
|
||||
| `409` | `notification_conflict` | `(source, external_id)` уже занят Create с другим fingerprint | нет |
|
||||
| `409` | `notification_closed` | Действие по уже закрытому уведомлению | нет |
|
||||
| `422` | `message_blocked` | Message Safety вернул final deny | нет |
|
||||
@@ -126,6 +127,8 @@
|
||||
|
||||
Правило доступа к пользовательским ресурсам: для `dialog_id`, `message_id`, `attachment_id`, `document_id`, принадлежащих другому `user_id`, api-backend по умолчанию возвращает `404 not_found`, чтобы не раскрывать существование ресурса. `403 forbidden` используется только для операций, где сам факт ресурса уже известен пользователю или оператору.
|
||||
|
||||
Для любого protected endpoint, кроме самого `POST /api/v1/auth/bootstrap`, валидный JWT при отсутствии локального `UserIdentity` возвращает `409 resource_state_conflict` с generic сообщением `bootstrap required`. Frontend после такого ответа выполняет bootstrap один раз и повторяет исходную операцию с тем же idempotency key, если она идемпотентна.
|
||||
|
||||
### `POST /api/v1/auth/bootstrap` (после OTP)
|
||||
|
||||
Вызывается **один раз** после успешного OTP и получения JWT. Создаёт локального пользователя и **сразу** сохраняет согласия из тела (атомарно в одной транзакции). **Не** создаёт UX-сессию — для этого используется `POST /api/v1/analytics/session-start`.
|
||||
@@ -182,7 +185,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
api-backend сохраняет согласия с привязкой к **`user_id`** из JWT. Пользователь должен уже существовать (`bootstrap` выполнен), иначе **`404`** / **`409`** по контракту модуля. Обязательные согласия без `accepted: true` → **`403`** `consents_required`.
|
||||
api-backend сохраняет согласия с привязкой к **`user_id`** из JWT. Пользователь должен уже существовать (`bootstrap` выполнен), иначе `409 resource_state_conflict`. Обязательные согласия без `accepted: true` → **`403`** `consents_required`.
|
||||
|
||||
### `POST /api/v1/analytics/session-start` (событие `session_start`)
|
||||
|
||||
@@ -499,12 +502,46 @@ Frontend не обращается напрямую к Keycloak DB и не хр
|
||||
|---|---|---|---|---|
|
||||
| `POST /internal/safety/v2/messages/check` | `message-safety` | `api-backend` | Проверка текста, ссылок и файлов | private HTTPS + internal CA + `X-Service-Token` |
|
||||
| `GET /internal/safety/v2/messages/tasks/{task_id}` | `message-safety` | `api-backend` | Опрос до финального вердикта **внутри** того же public `POST .../messages` | private HTTPS + internal CA + `X-Service-Token` |
|
||||
| `GET /internal/safety/status` | nginx ВМ2 → `message-safety /health/ready` | `api-backend`, ops | Короткоживущий capability snapshot; не correctness gate | private HTTPS + internal CA + source allow-list |
|
||||
| Read S3-quarantine | Selectel S3 | `message-safety` | Чтение файла worker-ом при cache miss | read-only key |
|
||||
|
||||
HTTP-семантика target v2 от `message-safety`: `200 allow`, `403 deny`, `202 Accepted/pending`. Текущие `/v1/*` и `203` относятся только к legacy stub и не являются production-контрактом.
|
||||
|
||||
Канонический wire DTO `POST .../check`:
|
||||
|
||||
```json
|
||||
{"message_id":"uuid","content_kind":"text","text":"Текст сообщения","attachment":null}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"message_id":"uuid",
|
||||
"content_kind":"file",
|
||||
"text":"",
|
||||
"attachment":{
|
||||
"attachment_id":"uuid",
|
||||
"quarantine_object_key":"quarantine/users/{user_id}/dialogs/{dialog_id}/{attachment_id}",
|
||||
"quarantine_version_id":"opaque-version-id",
|
||||
"quarantine_etag":"\"etag\"",
|
||||
"mime_type":"application/pdf",
|
||||
"size_bytes":12345,
|
||||
"checksum":"sha256:<64-lowercase-hex>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
DTO является strict discriminated union, unknown fields запрещены. Caller маппит App DB `checksum_sha256` в `attachment.checksum` с обязательным prefix `sha256:`; `quarantine_version_id` и `quarantine_etag` передаются без переименования.
|
||||
|
||||
Normative details v2: каждый verdict/pending содержит `processing_mode=standard|mock` и `config_version`; `202` обязательно содержит `Location`, `Retry-After`, `task_id`, `expires_at` и существует только в standard mode; terminal `503 task_failed` — `terminal=true,retryable=false`; transient `503 dependency_unavailable` — `terminal=false,retryable=true`; `409 safety_request_conflict` — non-retryable caller invariant. Все domain deny имеют `reason_code=message_blocked`.
|
||||
|
||||
`Location` должен быть origin-relative path `/internal/safety/v2/messages/tasks/{task_id}`. Caller и recovery job резолвят его относительно origin `MESSAGE_SAFETY_URL`; absolute URL, другой host или path вне этого prefix отклоняются как нарушение контракта без HTTP-запроса.
|
||||
|
||||
CA-пара caller: `MESSAGE_SAFETY_CA_HOST_PATH` задаёт root-owned host bind, `MESSAGE_SAFETY_CA_FILE` — путь к нему внутри контейнера `api-backend`. Для remote production URL обязательны оба уровня доставки; TLS verification отключать запрещено.
|
||||
|
||||
Caller budget: POST timeout `MESSAGE_SAFETY_POST_TIMEOUT_SEC=5`, одна попытка GET task — не более `2` секунд, client sync-poll budget `MESSAGE_SAFETY_TASK_POLL_MAX_SEC=300`, durable recovery budget `HAN_APP_SAFETY_RECOVERY_MAX_SEC=1200`. Числа являются initial defaults из arch-04; изменение выполняется синхронно в arch-04 и профильных модулях.
|
||||
|
||||
Internal error subset не смешивается с public JWT errors: `401 service_unauthorized`, `400 validation_error`, `404 task_not_found`, `409 safety_request_conflict`, `429 rate_limit_exceeded`, `500 internal_error`, `503 dependency_unavailable|task_failed`. Поля и retry-семантика определены в `module-05` §8.3.
|
||||
|
||||
Emergency MOCK включается только root-owned helper/restart на ВМ2. В MOCK нет content/link/file checks и `202`: `TEXT_FREE`/`FILE_FREE=true` → sync `200`, false → canonical sync `403`. Auth/DTO/idempotency/audit/rate limits сохраняются. Public API не раскрывает `processing_mode`.
|
||||
|
||||
Поведение `api-backend`:
|
||||
@@ -514,6 +551,8 @@ Emergency MOCK включается только root-owned helper/restart на
|
||||
3. При `202` сохраняет `task_id`, `Location`, deadline и **не ставит задачу в свою очередь анализа**; синхронно поллит `Location`, соблюдая `Retry-After`, до `200`/`403`, terminal failed `503` или timeout.
|
||||
4. Решение «проверка быстрая или долгая» — только у `message-safety`. Ожидание poll держит **одно** клиентское HTTP-соединение; это не блокирует обработку других запросов (параллельные workers/async).
|
||||
|
||||
`api-backend` не вызывает `/internal/sync/v1/*`. Этот ops-only namespace может находиться на том же private listener `:8443`, но защищается отдельным `BITRIX_SYNC_SERVICE_TOKEN` и path allow-list.
|
||||
|
||||
Checkpoint: на время poll — запись в **`safety_tasks`** (`han_app`) для recovery при crash/timeout (I1), не очередь анализа.
|
||||
|
||||
Recovery contract для `han_app.safety_tasks`:
|
||||
@@ -622,7 +661,7 @@ Circuit breaker + timeout budget (I2): при открытом circuit на `mes
|
||||
`contact.rebind` не является задачей `han_app.sync_queue`: это audited административный workflow, создаваемый только через `bitrix_sync.request_bitrix_contact_rebind`.
|
||||
|
||||
`bitrix-sync` **не создаёт** `UserIdentity` / `ClientProfile` в auth-flow; вход worker — задачи из `sync_queue`, созданные триггерами.
|
||||
`entity_id` contact-задачи всегда равен `UserIdentity.id`; payload не содержит PII snapshot. Полный DDL/state-machine contract — [`../modules/module-07-bitrix-sync.md`](../modules/module-07-bitrix-sync.md), §§6–9.
|
||||
`entity_id` contact-задачи всегда равен `UserIdentity.id`; payload не содержит PII snapshot. Полный DDL/state-machine contract — [`module-07-bitrix-sync.md`](../VM2_services/documentation/module-07-bitrix-sync.md), §§6–9.
|
||||
|
||||
### Internal HTTP `bitrix-sync` (ops, не hot path)
|
||||
|
||||
@@ -707,7 +746,7 @@ Raw OTP и полный номер телефона в audit **не** пишут
|
||||
| `request_id` | из `X-Request-ID` |
|
||||
| `ip`, `user_agent` | из proxy headers |
|
||||
|
||||
Presigned URL и содержимое файла в audit **не** пишутся. Структура таблицы — модуль `database`.
|
||||
Presigned URL и содержимое файла в audit **не** пишутся. Структура таблицы принадлежит `module-01-api-backend` и migration owner схемы `han_app`.
|
||||
|
||||
## Health-контракты
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# arch-03. Docker Compose blueprint
|
||||
|
||||
> Термины (имена бакетов S3, идентификаторы) — в [`arch-00-glossary.md`](arch-00-glossary.md). Контракт Message Safety Service — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md), раздел «api-backend ↔ message-safety». Переменные окружения и настройки — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). VM/SSH, OS-роли, секреты, systemd-деплой и hardening — в [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md).
|
||||
> Термины (имена бакетов S3, идентификаторы) — в [`arch-00-glossary.md`](arch-00-glossary.md). Контракт Message Safety Service — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md), раздел «api-backend ↔ message-safety». Переменные окружения и настройки — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). VM/SSH, OS-роли, секреты, systemd-деплой и hardening — в [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md). Детальный контракт nginx (TLS/ACME, request id, internal 404, reload) — [`arch-08-nginx.md`](arch-08-nginx.md); routing matrix VM — [`module-03-nginx-vm1.md`](../VM1_app/documentation/module-03-nginx-vm1.md) и [`module-03-nginx-vm2.md`](../VM2_services/documentation/module-03-nginx-vm2.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
@@ -201,7 +201,8 @@ docker compose exec api-backend ruff format .
|
||||
- **API-домен** (если выделен отдельный host, напр. `api.example.ru`): **не** слушает порт `80`; только `listen 443 ssl`; HTTP-запросы к API-домену недоступны;
|
||||
- **единый домен MVP** (напр. `tohin.ru` с путями `/api/*`, `/auth/*`, web): считается веб-доменом; порт `80` — только redirect на HTTPS для всего server block; после редиректа весь пользовательский трафик — HTTPS;
|
||||
- **auth** на том же host, что API (`/auth/*`): следует политике host (redirect-only на :80 или HTTPS-only для выделенного API-host);
|
||||
- **Bitrix callbacks** (`/bitrix/*`, `/bitrix/sync/*`): только HTTPS; порт `80` не обслуживает эти location — только redirect;
|
||||
- **Bitrix local-app callbacks ВМ1** (`/bitrix/handler|install|placement`): только HTTPS; порт `80` — только redirect;
|
||||
- **CRM sync webhook ВМ2** (exact `/bitrix/sync/webhook/contact|alert`): только HTTPS на отдельном processing host; HTTP не отражает query token в redirect;
|
||||
- маршрутизирует `/api/*` в `api-backend` (включая WebSocket upgrade для `/api/v1/realtime`);
|
||||
- маршрутизирует `/auth/*` в `keycloak` или проксирует отдельный auth-домен;
|
||||
- маршрутизирует публичные `/bitrix/*` endpoint в `bitrix-local-app`;
|
||||
@@ -293,7 +294,7 @@ paths, `clamd` health и фактического обновления signature
|
||||
|
||||
### bitrix-sync
|
||||
|
||||
Python API/worker service ВМ2 для durable двусторонней синхронизации App DB ↔ Bitrix24 CRM. Каноническая постановка — [`../modules/module-07-bitrix-sync.md`](../modules/module-07-bitrix-sync.md).
|
||||
Python API/worker service ВМ2 для durable двусторонней синхронизации App DB ↔ Bitrix24 CRM. Каноническая постановка — [`module-07-bitrix-sync.md`](../VM2_services/documentation/module-07-bitrix-sync.md).
|
||||
|
||||
Требования:
|
||||
|
||||
@@ -362,7 +363,7 @@ Identity provider. **Обязателен** в compose-контуре с пер
|
||||
- включены proxy settings для работы за `nginx`;
|
||||
- импорт realm в local/dev;
|
||||
- использует managed PostgreSQL, схема `keycloak` (см. раздел «Managed PostgreSQL» выше);
|
||||
- OTP mock / SMS SPI — см. arch-04; real mode вызывает `sms-service` по сети `backend`, а единственный утверждённый внешний вызов Keycloak через `egress` — server-side validation Yandex SmartCaptcha;
|
||||
- OTP mock / SMS SPI — см. arch-04; real mode вызывает `sms-service` по сети `backend`; при `KEYCLOAK_YANDEX_CAPTCHA_ENABLED=true` Keycloak подключается к `egress` с destination allow-list только для server-side validation Yandex SmartCaptcha, при `false` egress у Keycloak отсутствует;
|
||||
- healthcheck;
|
||||
- взаимодействия — [`arch-02-api-contracts.md`](arch-02-api-contracts.md), «Frontend ↔ Keycloak», и [`arch-01-system-architecture.md`](arch-01-system-architecture.md), «Keycloak».
|
||||
|
||||
@@ -386,7 +387,8 @@ Identity provider. **Обязателен** в compose-контуре с пер
|
||||
- поддерживать TTL для лимитных и idempotency ключей;
|
||||
- **не** хранить OTP counters для `api-backend` (OTP — зона Keycloak/SPI);
|
||||
- sync_queue, leases, limiter coordination и durable wake-up fallback хранятся в PostgreSQL; `LISTEN/NOTIFY` — только optimization, Redis sync-service не использует;
|
||||
- разделение DB index (I4): см. arch-04 (`REDIS_URL`, `MESSAGE_SAFETY_REDIS_URL`).
|
||||
- разделение DB index (I4): см. arch-04 (`REDIS_URL`, `MESSAGE_SAFETY_REDIS_URL`);
|
||||
- детальный контракт и карта ключей — [`arch-09-redis.md`](arch-09-redis.md), [`module-04-redis-vm1.md`](../VM1_app/documentation/module-04-redis-vm1.md), [`module-04-redis-vm2.md`](../VM2_services/documentation/module-04-redis-vm2.md).
|
||||
|
||||
### otel-collector
|
||||
|
||||
@@ -405,7 +407,8 @@ Identity provider. **Обязателен** в compose-контуре с пер
|
||||
- ВМ1 `public`: edge nginx, Keycloak proxy и frontend entrypoint.
|
||||
- ВМ1 `backend`: `api-backend`, `bitrix-local-app`, Keycloak, SMS API и Redis DB0/DB1.
|
||||
- ВМ2 `backend`: nginx, Safety API/worker, `bitrix-sync`, `clamd` и Redis Safety.
|
||||
- `egress` подключается только к процессам с назначением: `freshclam` → signature CDN; `bitrix-sync` → утверждённый Bitrix portal; Safety worker → S3/PG/DNS; collector → private SigNoz. Общего internet egress у Safety API/clamd/Redis нет.
|
||||
- ВМ1 `egress` подключается только к процессам с назначением: `sms-worker` → i-Digital Direct; Keycloak → SmartCaptcha только при включённом feature flag; `api-backend` → S3 и private PG, а вызов Safety идёт к `processing.internal:8443`; local collector → private SigNoz. `sms-service` без совмещённого worker, `bitrix-local-app` и Redis не получают общий internet egress.
|
||||
- ВМ2 `egress` подключается только к процессам с назначением: `freshclam` → signature CDN; `bitrix-sync` → утверждённый Bitrix portal; Safety worker → S3/PG/DNS; collector → private SigNoz. Общего internet egress у Safety API/clamd/Redis нет.
|
||||
- `observability` существует отдельно на каждой VM и ведёт в её local collector.
|
||||
|
||||
Базы данных, Redis, OTLP receivers и internal service ports не публикуются. Cross-host calls идут через private network, точные SG и TLS.
|
||||
@@ -453,7 +456,8 @@ Local OTEL queue на каждой VM использует отдельный pe
|
||||
|---|---|---|---|
|
||||
| Веб-домен (frontend) | только `301`/`308` → HTTPS | HTTPS, бизнес-логика | MVP: `tohin.ru`; staging/dev может использовать отдельный host |
|
||||
| API-домен (если выделен) | **не слушает** | только HTTPS | Post-MVP: `api.example.ru` |
|
||||
| Bitrix callbacks (`/bitrix/*`, `/bitrix/sync/*`) | не обслуживает API; только redirect на том же host | HTTPS | webhook и install URL |
|
||||
| Bitrix Local App ВМ1 (`/bitrix/handler|install|placement`) | только redirect на web host | HTTPS | install/handler/placement |
|
||||
| CRM webhook ВМ2 (exact `/bitrix/sync/webhook/contact|alert`) | generic `404/426`, без redirect query token | HTTPS | отдельный processing host |
|
||||
|
||||
Правила:
|
||||
|
||||
@@ -494,7 +498,7 @@ Local OTEL queue на каждой VM использует отдельный pe
|
||||
- **веб-домен** (MVP: `tohin.ru`): `/api/*` (REST + WS realtime), `/auth/*`, web frontend; `:80` → redirect HTTPS; `:443` — TLS + маршрутизация;
|
||||
- **выделенный API-домен** (post-MVP, опционально): отдельный `server { listen 443 ssl; ... }` **без** `listen 80`; только `/api/*`;
|
||||
- для `location` WebSocket (`/api/v1/realtime`): `proxy_http_version 1.1`, `Upgrade`/`Connection` headers, увеличенный `proxy_read_timeout`;
|
||||
- домен или path `/bitrix/*` → `bitrix-local-app`; `/bitrix/sync/*` → `bitrix-sync`;
|
||||
- только `/bitrix/handler`, `/bitrix/install`, `/bitrix/placement` на ВМ1 → `bitrix-local-app`; CRM `/bitrix/sync/*` на этом host не маршрутизируется;
|
||||
- `GET/POST /bitrix/handler` и `GET/POST /bitrix/install` доступны публично для Bitrix24;
|
||||
- `/bitrix/placement` доступен публично как заглушка UI настроек коннектора;
|
||||
- `/health/live` и `/health/ready` для `bitrix-local-app` доступны только там, где это нужно для healthcheck и проверки Bitrix form URL;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
| **Настройки SMS runtime** | таблица **`sms.sms_setting`** | sender default, provider timeouts, callback flag, worker intervals |
|
||||
| **Контент** | `text_resources`, `popular_questions` | тексты UI |
|
||||
|
||||
Managed PostgreSQL **поднимается до** развёртывания приложения. Бизнес-настройки **не дублируются** в `.env`: seed в `app_settings` выполняется миграцией/скриптом модуля `database` **до** первого запуска `api-backend`.
|
||||
Managed PostgreSQL **поднимается до** развёртывания приложения. Бизнес-настройки **не дублируются** в `.env`: seed в `app_settings` выполняется миграцией/скриптом `module-01-api-backend` **до** первого запуска `api-backend`.
|
||||
|
||||
## Источники настроек
|
||||
|
||||
@@ -74,7 +74,7 @@ Managed PostgreSQL **поднимается до** развёртывания п
|
||||
|
||||
## Требования к таблице `app_settings`
|
||||
|
||||
Схема: **`han_app`**. Детальная DDL — модуль `database`; arch фиксирует контракт.
|
||||
Схема: **`han_app`**. Детальная DDL — `module-01-api-backend` и migration owner этой схемы; arch фиксирует контракт.
|
||||
|
||||
### Колонки (минимум)
|
||||
|
||||
@@ -314,6 +314,9 @@ IDGTL_SMS_CALLBACK_PUBLIC_URL=https://tohin.ru/callbacks/idgtl/sms
|
||||
BITRIX_LOCAL_APP_BASE_URL=http://bitrix-local-app:8080
|
||||
BITRIX_API_INBOX_PATH=/internal/openlines/v1/inbox
|
||||
MESSAGE_SAFETY_URL=https://processing.internal:8443
|
||||
# Host-level Compose bind source; не передаётся приложению как runtime path.
|
||||
MESSAGE_SAFETY_CA_HOST_PATH=/opt/han-chat/secrets/processing-internal-ca.crt
|
||||
# Путь того же bind внутри контейнера api-backend.
|
||||
MESSAGE_SAFETY_CA_FILE=/run/han-chat/secrets/processing-internal-ca.crt
|
||||
MESSAGE_SAFETY_API_PREFIX=/internal/safety/v2
|
||||
MESSAGE_SAFETY_CIRCUIT_FAILURE_THRESHOLD=5
|
||||
@@ -451,7 +454,7 @@ Feature flag, edge allow-list и readiness образуют единый fail-cl
|
||||
|
||||
Versioned hot settings содержат batch size/wait, claim size, lease TTL, portal limiter refill/burst, max in-flight, retry base/max/horizon, Contact/alert reconciliation intervals, пороги всплеска Contact, восстановленных без webhook, и IDs/стадии/поля/SLA smart process. Новая версия активируется только после полной type/range/cross-field validation; невалидная версия не заменяет последнюю рабочую.
|
||||
|
||||
Secrets, DSN, portal host/member ID, inbound source IP CIDR allow-list, custom Contact field names и cutover watermark не являются hot settings. Полный каталог и defaults — [`../modules/module-07-bitrix-sync.md`](../modules/module-07-bitrix-sync.md), §12.
|
||||
Secrets, DSN, portal host/member ID, inbound source IP CIDR allow-list, custom Contact field names и cutover watermark не являются hot settings. Полный каталог и defaults — [`module-07-bitrix-sync.md`](../VM2_services/documentation/module-07-bitrix-sync.md), §12.
|
||||
|
||||
## Keycloak settings bridge для OTP
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
- Все сервисы при чтении бизнес-данных по умолчанию запрашивают только `record_status = 'A'`.
|
||||
- Исключения допускаются только для аудита, админки, технического восстановления и миграций.
|
||||
- Прикладные сущности, имеют `id`, `created_at`, `updated_at`, `updater_user_id`.
|
||||
- Системные таблицы (`app_settings`, `text_resources`, `popular_questions`, `sync_queue`, audit, справочники) могут использовать `user_id = NULL` или отдельное поле `actor_type` — по спецификации модуля `database`.
|
||||
- Системные таблицы (`app_settings`, `text_resources`, `popular_questions`, `sync_queue`, audit, справочники) могут использовать `user_id = NULL` или отдельное поле `actor_type` — по спецификации `module-01-api-backend` и migrations схемы `han_app`.
|
||||
- Для **строковых enum из arch-00** (`Dialog.status`, `Message.safety_status`, `Message.delivery_status`, `sender_type`, `scan_status` и т.п.) справочник sequence **не** обязателен: значения фиксированы контрактом API.
|
||||
- Для больших/изменяемых списков (типы документов post-MVP, причины, классификаторы UI) — справочники с ID (sequence) и расшифровкой.
|
||||
- Для часто используемых фильтров добавляются индексы.
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
Каждый модуль должен:
|
||||
|
||||
- использовать общий формат JSON-логов;
|
||||
- использовать общий формат JSON-логов из [`arch-07-observability.md`](arch-07-observability.md);
|
||||
- добавлять `module`, `event`, `request_id`, `trace_id`;
|
||||
- для `api-backend` добавлять **`ux_session_id`** в JSON-логи, если передан заголовок `X-Ux-Session-Id`;
|
||||
- не логировать access token, refresh token, raw OTP, документы, полные PII;
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
# arch-07. Контракт наблюдаемости
|
||||
|
||||
> Канонический контракт telemetry для всех application VM и private SigNoz.
|
||||
> Реализация на конкретной VM — в [`module-09-observability-vm1.md`](../VM1_app/documentation/module-09-observability-vm1.md) и [`module-09-observability-vm2.md`](../VM2_services/documentation/module-09-observability-vm2.md).
|
||||
> Имена сущностей — [`arch-00-glossary.md`](arch-00-glossary.md). Границы системы — [`arch-01-system-architecture.md`](arch-01-system-architecture.md). HTTP — [`arch-02-api-contracts.md`](arch-02-api-contracts.md). Compose — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md). Env — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). Процесс — [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md). Host security — [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
Документ фиксирует то, что **должно совпасть между ВМ1 и ВМ2**: схема сигналов, корреляция, redaction, sampling, Collector pipeline и удаленный backend. Локальные `service.name`, scrape targets, дашборды и алерты конкретной машины в этом файле не детализируются.
|
||||
|
||||
Этот контракт **нельзя** независимо кастомизировать в репозитории VM. Изменение JSON-полей, labels, redaction, sampling или `service.namespace` сначала вносится сюда.
|
||||
|
||||
## 1. Цели и границы
|
||||
|
||||
Наблюдаемость должна позволять:
|
||||
|
||||
- найти пользовательский запрос по `request_id`, `trace_id` или `ux_session_id`;
|
||||
- восстановить путь «frontend → nginx → API → Safety → S3/Open Lines»;
|
||||
- измерять доступность, задержку, ошибки и насыщение каждого сервиса;
|
||||
- обнаруживать backlog, DLQ, circuit open, потерю telemetry и истечение TLS;
|
||||
- расследовать security/audit события без записи PII и секретов;
|
||||
- проверять SLO по данным, независимым от бизнес-логов.
|
||||
|
||||
Telemetry не является источником бизнес-истины и не влияет на auth, safety verdict или доставку сообщений. Недоступность Collector не должна блокировать запросы. Audit в `han_app` — отдельный durable контур.
|
||||
|
||||
## 2. Production-like решение MVP
|
||||
|
||||
### 2.1. Обязательный минимум в основном Compose
|
||||
|
||||
Каждый root Compose (ВМ1 и ВМ2) обязан содержать локальный `otel-collector`. Prometheus/Grafana/Loki/Tempo не обязаны размещаться на application VM.
|
||||
|
||||
Операбельный вариант:
|
||||
|
||||
1. приложения на каждой VM экспортируют OTLP gRPC только в свой local `otel-collector:4317`;
|
||||
2. Collector отправляет telemetry в self-hosted SigNoz на отдельной VM `192.168.0.5:4317`;
|
||||
3. JSON stdout остаётся аварийным локальным журналом Docker с rotation;
|
||||
4. пока удалённый backend недоступен в конкретном окружении, допустим архитектурный минимум из arch-03: bounded JSON stdout/platform logs и Collector `debug` exporter с sampling в acceptance; такой режим не считается полноценным production-хранением и не закрывает alerting/SLO.
|
||||
|
||||
Требуемые возможности backend: OTLP ingest, поиск traces, PromQL-совместимые или эквивалентные metrics, поиск структурированных logs, alerting, RBAC, retention и TLS. Для текущего SigNoz plaintext OTLP разрешён только внутри доверенной приватной сети; UI — через SSH jump host.
|
||||
|
||||
ВМ2 никогда не использует Docker hostname collector ВМ1. Каждый collector имеет собственный `otel-queue`.
|
||||
|
||||
### 2.2. Самостоятельно размещаемая опция
|
||||
|
||||
Опциональный Compose profile `observability-local` может включать Prometheus, Grafana, Loki и Tempo. Он **не включается по умолчанию на малой VM**: стек требует дополнительной RAM/диска и сам становится объектом backup/monitoring.
|
||||
|
||||
Для operable local-варианта нужны отдельный volume каждому backend, retention limits, compaction, auth через ops/VPN и отсутствие host ports. Grafana доступна только через отдельный защищённый ops route/VPN, не через публичный `/`.
|
||||
|
||||
Рекомендуемый минимум VM при local profile: дополнительно 4 vCPU, 8 ГБ RAM и 100+ ГБ SSD сверх приложения; точный размер — после измерения ingest. Нужен ли profile на ВМ1 — TBD профильной спецификации ВМ1.
|
||||
|
||||
## 3. Архитектура Collector
|
||||
|
||||
### 3.1. Компоненты
|
||||
|
||||
```text
|
||||
backend services ─OTLP gRPC/HTTP─┐
|
||||
nginx/Redis/Keycloak exporters ──┼─> otel-collector
|
||||
Docker JSON stdout ─filelog───────┘ ├─ OTLP remote backend (SigNoz)
|
||||
├─ Prometheus endpoint (optional)
|
||||
└─ debug exporter (acceptance only)
|
||||
```
|
||||
|
||||
Collector запускается одним сервисом MVP. При росте разделяется на agent/gateway: локальный agent принимает и буферизует, remote gateway выполняет policy/export.
|
||||
|
||||
### 3.2. Receivers
|
||||
|
||||
- `otlp` gRPC `0.0.0.0:4317` — основной internal receiver;
|
||||
- `otlp` HTTP `0.0.0.0:4318` — совместимость SDK;
|
||||
- `prometheus` — scrape самого Collector, exporters и сервисных `/metrics`, если они не идут OTLP;
|
||||
- `filelog` — только если Docker logging driver предоставляет read-only каталог/volume; парсит JSON stdout без чтения secret-файлов;
|
||||
- `hostmetrics` — CPU, memory, filesystem, network VM/container host, если Collector получает только необходимые read-only mounts.
|
||||
|
||||
Порты `4317`, `4318`, `8888`, `8889` используют `expose`, не `ports`. Receiver доступен только в сети `observability`.
|
||||
|
||||
### 3.3. Processors и порядок
|
||||
|
||||
Во всех pipelines первым стоит защита памяти, последним — batch:
|
||||
|
||||
1. `memory_limiter`: check interval 1s, soft/hard limit относительно container memory;
|
||||
2. `resource`: нормализует `service.namespace=han-chat`, `deployment.environment`, `service.version`;
|
||||
3. `attributes`: удаляет/маскирует sensitive attributes;
|
||||
4. `transform`: нормализует route/status/error semantic conventions;
|
||||
5. `filter`: исключает health noise, debug events и запрещённые поля;
|
||||
6. `probabilistic_sampler` или tail sampling для traces;
|
||||
7. `batch`: bounded batch/timeout;
|
||||
8. при remote export — `queued_retry`/sending queue и `file_storage` extension.
|
||||
|
||||
`memory_limiter` не заменяется Docker OOM limit. При pressure Collector отбрасывает telemetry контролируемо и увеличивает `otelcol_processor_refused_*`.
|
||||
|
||||
### 3.4. Exporters
|
||||
|
||||
- `otlp/remote`: endpoint из secret env/mount; для SigNoz в доверенной private network — plaintext `192.168.0.5:4317`; для любого иного remote — TLS verify;
|
||||
- `prometheus`: optional pull endpoint только internal;
|
||||
- `debug`: только `APP_ENV=test|acceptance`, verbosity normal; production debug exporter по умолчанию выключен;
|
||||
- `loki`/`otlphttp` — только если выбран дополнительный backend и его контракт закреплён.
|
||||
|
||||
Секрет exporter-а не должен появляться в rendered config, логах или `/debug/configz`. Config монтируется read-only; secret подставляется env.
|
||||
|
||||
### 3.5. Extensions
|
||||
|
||||
- `health_check` — internal endpoint, используется Compose;
|
||||
- `pprof`/`zpages` — только при явном ops profile, internal network;
|
||||
- `file_storage` — persistent sending queue на volume `otel-queue`;
|
||||
- `basicauth`/`oauth2client` — если требует remote backend.
|
||||
|
||||
### 3.6. Принципиальная конфигурация
|
||||
|
||||
```yaml
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc: {endpoint: 0.0.0.0:4317}
|
||||
http: {endpoint: 0.0.0.0:4318}
|
||||
prometheus:
|
||||
config:
|
||||
scrape_configs:
|
||||
- job_name: otel-collector
|
||||
static_configs: [{targets: ["127.0.0.1:8888"]}]
|
||||
|
||||
processors:
|
||||
memory_limiter:
|
||||
check_interval: 1s
|
||||
limit_mib: 384
|
||||
spike_limit_mib: 96
|
||||
resource/common:
|
||||
attributes:
|
||||
- {key: service.namespace, value: han-chat, action: upsert}
|
||||
- {key: deployment.environment, value: "${env:APP_ENV}", action: upsert}
|
||||
attributes/redact:
|
||||
actions:
|
||||
- {key: http.request.header.authorization, action: delete}
|
||||
- {key: http.request.header.cookie, action: delete}
|
||||
- {key: url.query, action: delete}
|
||||
- {key: db.statement, action: delete}
|
||||
filter/noise:
|
||||
error_mode: ignore
|
||||
traces:
|
||||
span:
|
||||
- 'attributes["http.route"] == "/health/live"'
|
||||
batch:
|
||||
send_batch_size: 1024
|
||||
timeout: 5s
|
||||
|
||||
exporters:
|
||||
otlp/remote:
|
||||
endpoint: "${env:OTEL_REMOTE_ENDPOINT}"
|
||||
# true только для утверждённого plaintext SigNoz внутри private network;
|
||||
# для любого другого remote — false и проверка CA.
|
||||
tls: {insecure: ${env:OTEL_REMOTE_TLS_INSECURE}}
|
||||
headers: {authorization: "${env:OTEL_REMOTE_AUTH_HEADER}"}
|
||||
|
||||
extensions:
|
||||
health_check: {endpoint: 0.0.0.0:13133}
|
||||
file_storage: {directory: /var/lib/otelcol/queue}
|
||||
|
||||
service:
|
||||
extensions: [health_check, file_storage]
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [memory_limiter, resource/common, attributes/redact, filter/noise, batch]
|
||||
exporters: [otlp/remote]
|
||||
metrics:
|
||||
receivers: [otlp, prometheus]
|
||||
processors: [memory_limiter, resource/common, attributes/redact, batch]
|
||||
exporters: [otlp/remote]
|
||||
logs:
|
||||
receivers: [otlp]
|
||||
processors: [memory_limiter, resource/common, attributes/redact, filter/noise, batch]
|
||||
exporters: [otlp/remote]
|
||||
telemetry:
|
||||
metrics: {address: 0.0.0.0:8888}
|
||||
```
|
||||
|
||||
Конкретная версия schema проверяется командой Collector `validate`; image закрепляется по digest. Значения memory/batch/queue — стартовые, не SLO. Для текущего SigNoz `tls.insecure` и auth header задаются профильной спецификацией VM согласно O1/O-TBD1. Scrape targets кроме самого Collector добавляет спецификация VM.
|
||||
|
||||
## 4. Resource attributes и корреляция
|
||||
|
||||
Обязательные resource attributes:
|
||||
|
||||
- `service.name` — только из реестра ниже;
|
||||
- `service.namespace=han-chat`;
|
||||
- `service.version=<release-or-git-sha>`;
|
||||
- `deployment.environment=production-like|production`;
|
||||
- `host.name`/`service.instance.id` без публичного IP.
|
||||
|
||||
Реестр `service.name`:
|
||||
|
||||
| Имя | Владелец |
|
||||
|---|---|
|
||||
| `nginx` | nginx каждой VM; различать экземпляры `host.name` / `service.instance.id` |
|
||||
| `api-backend` | ВМ1 |
|
||||
| `bitrix-local-app` | ВМ1 |
|
||||
| `keycloak` | ВМ1 |
|
||||
| `sms-service` | ВМ1, callback/internal API |
|
||||
| `sms-worker` | ВМ1, отправка в Direct |
|
||||
| `message-safety` | ВМ2 |
|
||||
| `bitrix-sync` | ВМ2 |
|
||||
| `redis` | Redis каждой VM; различать экземпляры |
|
||||
| `otel-collector` | collector каждой VM |
|
||||
|
||||
Новое имя добавляется только сюда, затем в спецификацию владельца.
|
||||
|
||||
Обязательные поля request-события:
|
||||
|
||||
- `request_id`;
|
||||
- `trace_id`, `span_id`;
|
||||
- `ux_session_id` — nullable, только когда передан;
|
||||
- `service.name`;
|
||||
- `environment` либо canonical `deployment.environment`.
|
||||
|
||||
`request_id` формирует/валидирует nginx; сервис возвращает его клиенту и передаёт downstream. `trace_id` берётся из active span. `ux_session_id` не является auth и не должен использоваться как metric label.
|
||||
|
||||
## 5. W3C propagation
|
||||
|
||||
- принимаются только валидные `traceparent` и опциональный `tracestate`;
|
||||
- nginx передаёт context upstream; при edge instrumentation создаёт server span;
|
||||
- caller создаёт child spans для своих зависимостей (PostgreSQL, Redis, S3, Safety, Open Lines, JWKS, CRM);
|
||||
- internal calls передают `traceparent`, `tracestate`, `X-Request-ID`;
|
||||
- `baggage` по умолчанию не принимается от внешнего клиента; если включён, allow-list исключает PII;
|
||||
- Bitrix24/S3 могут не вернуть context: внешний client span всё равно закрывается результатом;
|
||||
- async outbox/inbox связывается span link с исходным trace; новый worker trace не притворяется продолжением спустя долгий срок.
|
||||
|
||||
Frontend может отправлять валидный `traceparent`, но backend не доверяет его sampling/security атрибутам.
|
||||
|
||||
## 6. JSON stdout contract
|
||||
|
||||
Одна JSON-запись на строку UTF-8:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-07-10T09:00:00.123Z",
|
||||
"level": "INFO",
|
||||
"service.name": "api-backend",
|
||||
"service.version": "git-abcdef0",
|
||||
"environment": "production-like",
|
||||
"module": "message_service",
|
||||
"event": "message.delivery.completed",
|
||||
"message": "Message delivery completed",
|
||||
"request_id": "01J...",
|
||||
"trace_id": "32hex",
|
||||
"span_id": "16hex",
|
||||
"ux_session_id": "uuid-or-null",
|
||||
"route": "/api/v1/dialogs/{dialog_id}/messages",
|
||||
"method": "POST",
|
||||
"status_code": 201,
|
||||
"duration_ms": 742,
|
||||
"dependency": "bitrix-local-app",
|
||||
"outcome": "success",
|
||||
"error_code": null
|
||||
}
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- `event` — стабильная mnemonic, `message` — безопасное описание;
|
||||
- route — template, никогда raw URI с id/query;
|
||||
- stack trace допускается только в internal error log после redaction;
|
||||
- message text, callback body, SQL values и file content запрещены;
|
||||
- Docker driver ограничен `50m × 5`, но это buffer, не retention backend;
|
||||
- multiline stack trace сериализуется полем JSON, не отдельными строками.
|
||||
|
||||
## 7. Redaction и data minimization
|
||||
|
||||
Удаляются или маскируются:
|
||||
|
||||
- `Authorization`, Cookie, Set-Cookie, JWT, OAuth/code/refresh/access tokens;
|
||||
- raw OTP/mock code, Keycloak admin/client password;
|
||||
- phone/email/full name, device id, IP по policy (допустим HMAC/truncated);
|
||||
- message text, filenames с PII, document/file contents;
|
||||
- DSN/password, Redis URL, S3 keys;
|
||||
- presigned URL и любая query string;
|
||||
- Bitrix raw payload/download URL/application token;
|
||||
- `db.statement` с literals; предпочтительно operation/table, не SQL.
|
||||
|
||||
Redaction выполняется в SDK/logger **до stdout**, затем повторяется Collector processor. Collector не может считаться единственной защитой. Автотесты отправляют canary secrets/PII и требуют отсутствие во всех трёх сигналах.
|
||||
|
||||
## 8. Общие правила instrumentation
|
||||
|
||||
Детальный список инструментов конкретной VM — в профильной спецификации. Ниже правила, общие для всех сервисов.
|
||||
|
||||
### 8.1. FastAPI и workers
|
||||
|
||||
- OpenTelemetry ASGI/FastAPI server spans с route template;
|
||||
- HTTPX client spans с sanitized host/method/status;
|
||||
- SQLAlchemy/asyncpg spans без параметров и raw statement;
|
||||
- Redis instrumentation с command name и DB index, без key/value;
|
||||
- boto/S3 spans: operation/bucket logical name, без object key/query;
|
||||
- background workers: span на claim/process/finalize, links на origin;
|
||||
- исключить `/health/live` из traces; readiness оставить в metrics и sampled logs.
|
||||
|
||||
### 8.2. PostgreSQL
|
||||
|
||||
Собираются pool wait/checked-out, transaction duration, error class, migrations revision, managed PG provider metrics (CPU, storage, connections, locks, replication/PITR state). `user_id`, SQL text и row data не labels.
|
||||
|
||||
### 8.3. Redis
|
||||
|
||||
`redis_exporter` подключается отдельным ACL user только на `INFO`, `PING`, безопасные latency/keyspace metrics. Нужны memory ratio, evictions, expirations, blocked/rejected clients, command latency, AOF status/rewrite, Pub/Sub buffers, key count/TTL агрегаты. Keys/values не экспортируются.
|
||||
|
||||
### 8.4. nginx
|
||||
|
||||
JSON access log содержит `request_id`, извлечённый `trace_id`, route class, method, normalized path, status, bytes, request/upstream duration/status, TLS version, cache status. `$request` с query не используется.
|
||||
|
||||
Collector `filelog` parser:
|
||||
|
||||
- разбирает JSON, timestamp и severity;
|
||||
- переносит `service.name=nginx`;
|
||||
- превращает пустые/`-` в null;
|
||||
- route class нормализует в bounded set;
|
||||
- отбрасывает ACME/health success noise;
|
||||
- не парсит raw URI в labels.
|
||||
|
||||
Native nginx OTEL module предпочтителен, если image/version закреплены. Без него nginx только передаёт W3C context и коррелирует access log; первый server span создаёт upstream.
|
||||
|
||||
### 8.5. Host/Docker
|
||||
|
||||
CPU, load, memory/swap, disk usage/inodes/IO, network, container restarts/OOM, Docker daemon health и clock sync. Container name/version — bounded labels; container id не хранится как долгосрочный high-cardinality label.
|
||||
|
||||
## 9. Метрики: запрет high-cardinality
|
||||
|
||||
Никакие UUID/user/session/dialog/task/message id не labels. Они допустимы только в sampled logs/traces при принятой retention. Allow-list labels; route template вместо raw path; status class/known code; dependency enum.
|
||||
|
||||
Карта бизнес-метрик принадлежит спецификации VM-владельца сервиса. Сквозные SLI ниже используют сигналы обеих VM.
|
||||
|
||||
## 10. Сквозные dashboards в SigNoz
|
||||
|
||||
SigNoz обязан иметь:
|
||||
|
||||
1. **Executive/SLO**: availability, error budget burn, p50/p95/p99, message delivery, auth, active incidents.
|
||||
2. **Business flow**: guest config → OTP → bootstrap → session → dialog → safety → Open Lines → operator reply.
|
||||
3. **Collector health**: accepted/sent/refused/dropped, queue, retry, exporter errors, memory/CPU — отдельно по `host.name` ВМ1 и ВМ2.
|
||||
|
||||
Сервисные дашборды nginx/API/Safety/Keycloak/Redis/sync — в спецификациях VM. Каждая панель содержит release annotation, environment filter и links trace→logs по `trace_id`.
|
||||
|
||||
## 11. SLI, SLO и общие alerts
|
||||
|
||||
Значения — начальная production-like политика до load/product review:
|
||||
|
||||
| SLI | Initial SLO, 30 дней |
|
||||
|---|---|
|
||||
| HTTPS edge availability | 99.9% |
|
||||
| public config/content successful requests | 99.9% |
|
||||
| protected read API successful requests | 99.5% |
|
||||
| Keycloak login flow availability | 99.5% |
|
||||
| text message accepted и доставлен в Open Lines | 99.0% |
|
||||
| operator inbox applied без permanent loss | 99.5% |
|
||||
| p95 protected read API | < 750 ms |
|
||||
| p95 text send без внешнего rate limit | < 5 s |
|
||||
| telemetry Collector ingest availability | 99.0%, не входит в business availability |
|
||||
|
||||
Файловый send измеряется отдельно: p95 не должен превышать configured safety poll budget; user-cancel, safety deny, 4xx validation и edge abuse 429 не считаются server failure. 503/504 и unexpected 5xx считаются.
|
||||
|
||||
Paging/ticket alerts, привязанные к конкретному сервису, задаёт спецификация VM. Общие инфраструктурные paging alerts:
|
||||
|
||||
- multi-window burn: 14.4× за 5m/1h или 6× за 30m/6h;
|
||||
- PostgreSQL unavailable/connection saturation >90%;
|
||||
- Collector exporter queue >80%, dropped/refused telemetry >0 sustained;
|
||||
- TLS expiry <14 дней warning, <7 дней page;
|
||||
- disk >85% warning, >92% page; OOM/restart loop;
|
||||
- managed PG backup/PITR failure.
|
||||
|
||||
Alert содержит service, environment, symptom, dashboard, runbook, release и безопасный query; не содержит PII.
|
||||
|
||||
## 12. Sampling, cardinality и retention
|
||||
|
||||
### Traces
|
||||
|
||||
- errors/5xx, circuit, timeout, DLQ, safety final deny и slow requests — 100%;
|
||||
- обычные успешные requests — 5–10%;
|
||||
- health/ACME success — 0%;
|
||||
- tail sampling предпочтителен в Collector, но head sample SDK должен оставлять достаточно данных;
|
||||
- sampling decision передаётся W3C.
|
||||
|
||||
### Metrics
|
||||
|
||||
Cardinality budget: целевой <10 000 active series на MVP environment. CI проверяет запрещённые labels.
|
||||
|
||||
### Retention initial
|
||||
|
||||
- metrics: 30 дней high resolution, 13 месяцев downsampled при доступности backend;
|
||||
- traces: 7 дней, errors 14 дней;
|
||||
- technical logs: 14 дней, security/auth logs 30 дней;
|
||||
- audit `han_app`: 365 дней **только как временное допущение до legal policy**;
|
||||
- raw Bitrix callback не хранится в telemetry;
|
||||
- local Docker logs: не более 250 МБ/container и 5 файлов.
|
||||
|
||||
Legal retention/erasure имеет приоритет; изменение требует обновления policy и backup lifecycle.
|
||||
|
||||
## 13. Collector health и отказоустойчивость
|
||||
|
||||
Контролируются:
|
||||
|
||||
- `/health` extension;
|
||||
- process CPU/RSS/restarts;
|
||||
- accepted/refused/sent/failed spans, points, records;
|
||||
- batch send size/latency;
|
||||
- exporter queue capacity/size, enqueue failures, retry age;
|
||||
- file storage usage/corruption;
|
||||
- scrape failures;
|
||||
- config reload/validation.
|
||||
|
||||
При remote outage queue хранится на `otel-queue` с bounded size/age. При заполнении отбрасываются сначала low-priority success traces/logs; приложение продолжает работу. Нельзя позволять queue заполнить системный диск. Telemetry outage/overflow fail-open для business и Safety readiness, но создаёт alert.
|
||||
|
||||
## 14. Docker Compose collector
|
||||
|
||||
`otel-collector`:
|
||||
|
||||
- pinned contrib image;
|
||||
- networks: только `observability`, а для scrape internal targets — минимально необходимая `backend`;
|
||||
- `expose`: 4317, 4318, 13133, 8888/8889;
|
||||
- без host ports;
|
||||
- config read-only, `otel-queue` volume rw;
|
||||
- non-root, read-only rootfs, tmpfs `/tmp`, drop capabilities, no-new-privileges;
|
||||
- перед collector запускается idempotent `otel-queue-init`: one-shot без сети
|
||||
и secrets, с `user: 0:0`, `cap_drop: ALL` и только `CHOWN/FOWNER`, выставляет
|
||||
mount root `10001:10001 0700`; collector зависит от
|
||||
`service_completed_successfully`;
|
||||
- initial limit: 0.5 CPU/512 MiB, queue disk 5–10 ГБ; уточнить load test;
|
||||
- healthcheck extension;
|
||||
- restart policy с backoff;
|
||||
- приложения имеют bounded non-blocking OTLP exporter queue.
|
||||
|
||||
Новый named volume считается потенциально `root:root`; основной collector не
|
||||
запускается от root и volume не получает `0777`. Ownership init проверяется
|
||||
после первого create и повторного recreate.
|
||||
|
||||
Доступ к Docker socket запрещён. Для container metrics используется безопасный exporter/hostmetrics, а не unrestricted socket mount.
|
||||
|
||||
## 15. Security
|
||||
|
||||
- OTLP receiver internal-only; при переходе между hosts — mTLS, кроме явно зафиксированного plaintext SigNoz в private network;
|
||||
- remote exporter credentials least privilege;
|
||||
- Grafana/Prometheus/Loki/Tempo не публичны;
|
||||
- RBAC: viewer/operator/admin; audit доступа к logs/traces;
|
||||
- dashboards не показывают PII;
|
||||
- config/secret permissions 0400/0600;
|
||||
- dependency/image scan и SBOM;
|
||||
- защита от log injection: JSON encoding, control chars, bounded field lengths;
|
||||
- telemetry input не исполняет expressions из пользовательских значений;
|
||||
- регулярная secret-canary проверка и incident deletion procedure.
|
||||
|
||||
## 16. Общие runbooks
|
||||
|
||||
### Collector not-ready
|
||||
|
||||
1. `docker compose ps otel-collector` и bounded logs.
|
||||
2. Проверить config validation, memory/OOM, queue volume.
|
||||
3. Проверить DNS/TLS/auth remote exporter.
|
||||
4. Не рестартовать бесконечно при полной queue; сначала освободить/расширить безопасно.
|
||||
5. Бизнес-сервисы оставить работающими; подтвердить local JSON logs.
|
||||
6. После восстановления проверить drain и gap.
|
||||
|
||||
### Telemetry отсутствует у одного сервиса
|
||||
|
||||
1. Проверить `service.name`, endpoint/protocol и сеть `observability`.
|
||||
2. Проверить SDK queue/drop counters и clock.
|
||||
3. Отправить synthetic request с `X-Request-ID`.
|
||||
4. Найти его в stdout, Collector accepted и backend.
|
||||
5. Проверить sampling/filter/redaction rules.
|
||||
|
||||
### Remote backend outage
|
||||
|
||||
1. Подтвердить exporter errors, а не application outage.
|
||||
2. Оценить queue fill rate/time-to-full.
|
||||
3. Ограничить debug exporter; не включать verbose.
|
||||
4. При длительном outage увеличить sampling только через reviewed config.
|
||||
5. После восстановления подтвердить drain и создать incident note о потере данных.
|
||||
|
||||
### Cardinality/ingest spike
|
||||
|
||||
1. Найти новое metric/log attribute по release annotation.
|
||||
2. Отключить offending instrument/filter в Collector.
|
||||
3. Проверить raw path/id/user/session labels.
|
||||
4. Rollback instrumentation при риске стоимости/доступности.
|
||||
5. Добавить CI regression test.
|
||||
|
||||
### Логи содержат секрет/PII
|
||||
|
||||
1. Ограничить доступ и остановить offending export.
|
||||
2. Сохранить только incident metadata, не копировать значение.
|
||||
3. Ротировать скомпрометированный secret.
|
||||
4. Удалить данные по процедуре backend/provider.
|
||||
5. Исправить source redaction + Collector defense; добавить canary test.
|
||||
|
||||
Разбор высокой latency сообщения — в спецификации VM, которая владеет соответствующим hop.
|
||||
|
||||
## 17. Общий Definition of Done
|
||||
|
||||
- Collector config проходит validate и запускается в root Compose каждой VM;
|
||||
- OTLP gRPC и HTTP принимают три сигнала;
|
||||
- все сервисы имеют правильные resource attributes из реестра §4;
|
||||
- request проходит nginx/API/Safety/Open Lines с одним `request_id` и связанным trace;
|
||||
- `ux_session_id` есть только где передан и не является label;
|
||||
- remote outage, queue full, Collector restart и backend recovery rehearsed;
|
||||
- local profile, если включён, имеет volumes/retention/auth и не публикует порты;
|
||||
- secret/PII canary отсутствует в logs/traces/metrics;
|
||||
- cardinality и sampling tests проходят;
|
||||
- SLO queries воспроизводимы и исключения документированы;
|
||||
- runbooks связаны с alerts;
|
||||
- dashboards и alerts provisioned из versioned files для SigNoz; до полного provisioning это остаётся acceptance/TBD, а не выполненный production DoD.
|
||||
|
||||
Профильный DoD VM дополняет этот список своими сервисами и не переопределяет контракт.
|
||||
|
||||
## 18. Допущения, TBD и конфликты
|
||||
|
||||
### Решения
|
||||
|
||||
- O1: обязательный архитектурный минимум — local Collector на каждой application VM; выбран self-hosted SigNoz на отдельной VM `192.168.0.5`, доступный по приватному OTLP gRPC.
|
||||
- O2: Prometheus/Grafana/Loki/Tempo — отдельный operable profile, не скрытая обязательная нагрузка основной VM.
|
||||
- O3: JSON stdout — аварийный локальный buffer; audit App DB — durable.
|
||||
- O4: telemetry fail-open для business path, но потеря telemetry alertится.
|
||||
- O5: ID/PII не labels; source redaction обязательна до Collector.
|
||||
|
||||
Поля JSON, resource attributes, redaction, sampling, `service.namespace` и адрес SigNoz меняются только здесь.
|
||||
|
||||
### TBD
|
||||
|
||||
- O-TBD1 закрыт: self-hosted SigNoz, `192.168.0.5:4317`, plaintext только внутри доверенной приватной сети; UI через SSH jump host.
|
||||
- O-TBD2: утвердить SLO/RPS/error-budget с product owner.
|
||||
- O-TBD3: legal retention/erasure и допустимость IP/user-agent.
|
||||
- O-TBD4: точные sampling и resource limits после load test.
|
||||
- O-TBD5: поддерживаемый nginx OTEL module и Keycloak native tracing по pinned versions.
|
||||
- O-TBD6: нужен ли local observability profile в первой VM — решает спецификация ВМ1.
|
||||
|
||||
### Обнаруженные архитектурные конфликты
|
||||
|
||||
1. `arch-03` разрешает stdout/platform exporter как минимум, но production-like расследования и alerts без backend ограничены. Закрыто выбором SigNoz (O1 / O-TBD1); stdout остаётся аварийным buffer.
|
||||
2. `module-05` использует test-only terminal `400` и non-sticky verdict вместо canonical `403`/sticky production verdict. Dashboards обязаны маркировать сервис `stub`; production SLO Safety на нём недостоверен. Детали — спецификация ВМ2.
|
||||
3. `module-07` задаёт full sync target; до code/portal cutover dashboard обязан показывать `sync_disabled`, а не синтетический CRM success. Queue/CRM SLI включаются только после module-07 preflight. Детали — спецификация ВМ2.
|
||||
4. Retention, RPO/RTO и production SLO открыты в module-01/04/06/08; значения этого документа являются initial ops policy, не закрывают legal/product TBD.
|
||||
5. Observability env (`OTEL_REMOTE_*`, service names, sampling/queue limits) добавлены в arch-04 и `.env.example`; sampling/queue limits уточняются после load test.
|
||||
|
||||
## 19. Ссылки
|
||||
|
||||
- Профильные спецификации: [`module-09-observability-vm1.md`](../VM1_app/documentation/module-09-observability-vm1.md), [`module-09-observability-vm2.md`](../VM2_services/documentation/module-09-observability-vm2.md).
|
||||
- VM/Docker logging и firewall: [`../../HAN_chat/deploy/setup-vm-han-chat.sh`](../../HAN_chat/deploy/setup-vm-han-chat.sh).
|
||||
- Прототипный nginx stdout/access log: [`../../HAN_chat/bitrix-local-app/deploy/nginx/nginx.conf`](../../HAN_chat/bitrix-local-app/deploy/nginx/nginx.conf).
|
||||
- HTTP/OTLP registry: [`arch-02-api-contracts.md`](arch-02-api-contracts.md).
|
||||
- Compose topology: [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).
|
||||
@@ -0,0 +1,233 @@
|
||||
# arch-08. Контракт корневого nginx
|
||||
|
||||
> Канонический контракт nginx для всех application VM.
|
||||
> Compose-топология, сети и published ports — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).
|
||||
> Реализация на конкретной VM — [`module-03-nginx-vm1.md`](../VM1_app/documentation/module-03-nginx-vm1.md) и [`module-03-nginx-vm2.md`](../VM2_services/documentation/module-03-nginx-vm2.md).
|
||||
> Telemetry access log — [`arch-07-observability.md`](arch-07-observability.md). Env — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). Host security — [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
Документ фиксирует то, что **должно совпасть между ВМ1 и ВМ2**: независимый nginx на каждой машине, TLS/ACME, request id, forwarded headers, запрет internal paths, JSON access log, hardening Compose и failure/reload policy.
|
||||
|
||||
Routing matrix, upstreams, SPA, WebSocket, CSP и allow-list конкретной машины здесь не детализируются и **не кастомизируются** так, чтобы сломать этот контракт.
|
||||
|
||||
## 1. Обязательная топология
|
||||
|
||||
В production-like контуре каждая VM имеет собственный nginx в своём root Compose и собственный deployment lifecycle.
|
||||
|
||||
- публичный трафик одной VM не проходит через nginx другой;
|
||||
- отказ или deploy одной VM не обязан прерывать ingress другой;
|
||||
- контейнеры приложений не публикуют host ports; на каждой VM наружу смотрит только её nginx;
|
||||
- если перед конкретной VM есть WAF/LB, trusted proxy CIDR задаются отдельно (N1).
|
||||
|
||||
Между public route ВМ1 и ВМ2 нет reverse-proxy chain и нет SPA/API fallback на другую VM.
|
||||
|
||||
## 2. Общие правила routing
|
||||
|
||||
Порядок location критичен. Exact locations объявляются до prefix.
|
||||
|
||||
На **обоих** public hosts:
|
||||
|
||||
- `/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files возвращают `404`;
|
||||
- fallback на другую VM или чужой SPA запрещён;
|
||||
- query не участвует в exact location matching;
|
||||
- адрес из недоверенного `X-Forwarded-For` не используется для allow-list;
|
||||
- при внешнем LB сначала настраиваются его trusted CIDR и нормализация real IP.
|
||||
|
||||
Upstream failures не перенаправляются на другой сервис и не попадают в SPA. API-подобные locations возвращают `502/504` с безопасным nginx body и `X-Request-ID`; custom JSON error допустим только там, где профильная спецификация это разрешает, и не имитирует backend domain code.
|
||||
|
||||
Проверка prompt injection / malware не выполняется в nginx: это `message-safety` через `api-backend` (arch-02).
|
||||
|
||||
## 3. HTTP/HTTPS и TLS
|
||||
|
||||
- public host каждой VM на `:80` обслуживает только ACME challenge и `308 https://$host$request_uri`, кроме явно зафиксированных исключений профильной спецификации;
|
||||
- выделенный API host, если появится, не имеет listener `:80`;
|
||||
- `:443 ssl http2`, TLS 1.2/1.3, современные cipher suites, session tickets по ops policy;
|
||||
- сертификат доверенного CA, private key read-only и недоступен приложению;
|
||||
- OCSP stapling при поддержке CA/DNS;
|
||||
- HSTS включается только после успешной проверки HTTPS: `max-age` из env, затем по решению ops `includeSubDomains`; preload не включать автоматически;
|
||||
- OIDC redirects, cookies и external URLs всегда HTTPS;
|
||||
- `Server` tokens скрыты; upstream `X-Powered-By` удаляется.
|
||||
|
||||
ВМ2 дополнительно слушает private `8443` с сертификатом internal CA — детали в спецификации ВМ2.
|
||||
|
||||
## 4. ACME lifecycle
|
||||
|
||||
Выбран webroot Certbot/ACME client с общими named volumes:
|
||||
|
||||
```text
|
||||
nginx-certs -> /etc/letsencrypt (rw у certbot, ro у nginx)
|
||||
nginx-acme-webroot -> /var/www/certbot
|
||||
```
|
||||
|
||||
Каждая VM выпускает **свой** сертификат на свой public host. Секреты и volumes двух projects не общие.
|
||||
|
||||
Bootstrap:
|
||||
|
||||
1. DNS указывает на VM; 80/443 разрешены.
|
||||
2. Запустить временный HTTP config с `/.well-known/acme-challenge/`.
|
||||
3. Выпустить certificate без остановки nginx.
|
||||
4. Проверить `nginx -t`, атомарно активировать TLS config, reload.
|
||||
|
||||
Renew container/host timer выполняет `certbot renew` минимум дважды в сутки;
|
||||
после фактического renewal проверяет рабочую конфигурацию командой
|
||||
`docker compose exec -T nginx nginx -t -c /tmp/nginx.conf` и отправляет
|
||||
master-процессу `docker compose kill -s HUP nginx`. Bare-команды `nginx -t` и
|
||||
`nginx -s reload` запрещены: контейнер read-only, а рабочие config/PID находятся
|
||||
в `/tmp`. При ошибке остаётся старый worker/config/cert и срабатывает alert.
|
||||
Успешный deploy/renew hook возвращает `0` с пустым stderr: вывод успешного
|
||||
`nginx -t` и progress signal command перехватывается или подавляется; при
|
||||
ошибке сохранённая диагностика полностью печатается в stderr. Любой stderr на
|
||||
success path считается дефектом интеграции с Certbot.
|
||||
Контролируются expiry days и последняя успешная попытка. Staging CA используется
|
||||
в rehearsal, чтобы не исчерпать лимиты.
|
||||
|
||||
Non-root nginx не монтирует root-only дерево Let's Encrypt целиком: только необходимые cert/key files по arch-03/arch-06.
|
||||
|
||||
## 5. Request ID и forwarded headers
|
||||
|
||||
На edge формируется trusted request id. Базовый nginx не генерирует UUID штатной переменной, поэтому используется njs/Lua либо модуль request-id, включённый в закреплённый image. Входящий `X-Request-ID` принимается только если соответствует UUID/ULID и длине; иначе генерируется новый.
|
||||
|
||||
Upstream получает:
|
||||
|
||||
```text
|
||||
Host: original host
|
||||
X-Real-IP: trusted real client IP
|
||||
X-Forwarded-For: normalized proxy chain
|
||||
X-Forwarded-Proto: https
|
||||
X-Forwarded-Host: original host
|
||||
X-Forwarded-Port: 443
|
||||
X-Request-ID: edge request id
|
||||
traceparent: входной валидный либо новый согласно OTEL integration
|
||||
```
|
||||
|
||||
Ответ всегда содержит `X-Request-ID`. Клиентские `X-Forwarded-*` от недоверенного адреса перезаписываются. `Authorization`, `Cookie`, query string и body не попадают в access log.
|
||||
|
||||
Private listener доверяет forwarded headers только от allow-listed private caller; public listener применяет собственную trusted proxy policy.
|
||||
|
||||
## 6. Timeouts и body limits — общие ориентиры
|
||||
|
||||
| Группа | connect/send/read | Владелец |
|
||||
|---|---|---|
|
||||
| обычный API | 3s / 30s / 30s | ВМ1 |
|
||||
| auth | 3s / 30s / 60s | ВМ1 |
|
||||
| Bitrix local callback | 3s / 30s / 60s | ВМ1 |
|
||||
| Direct SMS callback | 3s / 30s / 60s | ВМ1 |
|
||||
| WS | 3s / 30s / 75s+ | ВМ1 |
|
||||
| message POST | 3s / 30s / `MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s` минимум | ВМ1 |
|
||||
| CRM webhook | 3s / 30s / 60s | ВМ2 |
|
||||
| private Safety | по контракту Safety, не короче caller budget | ВМ2 |
|
||||
|
||||
Значение timeout генерируется из env template до startup; nginx не выполняет арифметику env runtime.
|
||||
|
||||
`client_max_body_size` global 8m по arch-04, но JSON locations получают более строгие limits, где возможно. Байты вложения не проходят через nginx/API: клиент PUT напрямую в S3. Buffering request допустим для малого JSON; для callback устанавливается bounded temp storage. Header count/size ограничены.
|
||||
|
||||
## 7. Edge rate limits — общие правила
|
||||
|
||||
`limit_req_zone` использует binary remote address. Ответ превышения — `429`, `Retry-After` (статический/вычисляемый для зоны) и request id. Edge не реализует user-level бизнес-лимит; это делает API/Redis.
|
||||
|
||||
Карта зон принадлежит спецификации VM. Новые зоны добавляются только там, затем при необходимости сюда как реестр имён.
|
||||
|
||||
## 8. Health
|
||||
|
||||
- внутренний `GET /nginx-health/live` возвращает static 200 и доступен Docker healthcheck;
|
||||
- внешний health публикуется только если нужен мониторингу, с allow-list (N3);
|
||||
- nginx health не утверждает готовность upstream;
|
||||
- Docker healthcheck использует только binary, гарантированно присутствующий
|
||||
и проверенный внутри exact pinned nginx digest; `wget`/`curl` запрещены, если
|
||||
их наличие не подтверждено image inventory;
|
||||
- upstream `/health/ready` не агрегируется публично без решения ops.
|
||||
|
||||
## 9. Логи и OTEL correlation
|
||||
|
||||
JSON access log: timestamp, request_id, trace_id (если извлечён), remote IP/hashed policy, host, method, route class, normalized URI на основе `$uri` без `$request_uri`/`$args`, status, bytes, duration, upstream addr/status/time, cache status, TLS protocol/cipher, user agent при принятой retention.
|
||||
|
||||
Не логируются Authorization, cookies, request/response body, OTP, tokens, query token, presigned query, PII. Error log структурирован настолько, насколько позволяет nginx; debug выключен production.
|
||||
|
||||
Nginx передаёт W3C trace context; native OTEL module допустим при закреплённой версии (arch-07 O-TBD5). Если edge создаёт span, request id остаётся отдельным correlation key. Логи идут stdout/stderr; Docker/collector отвечает за доставку и rotation.
|
||||
|
||||
Parser и `service.name=nginx` — arch-07 §8.4.
|
||||
|
||||
## 10. Layout конфигурации
|
||||
|
||||
Общий каркас репозитория nginx:
|
||||
|
||||
```text
|
||||
nginx/
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
nginx.conf
|
||||
templates/
|
||||
00-maps.conf.template
|
||||
10-upstreams.conf.template
|
||||
20-http-redirect.conf.template
|
||||
30-https-site.conf.template
|
||||
snippets/
|
||||
proxy-common.conf
|
||||
security-headers.conf
|
||||
tls.conf
|
||||
rate-limits.conf
|
||||
njs/request_id.js
|
||||
scripts/{render,validate,reload-after-renew}.sh
|
||||
tests/
|
||||
```
|
||||
|
||||
Профильная спецификация добавляет только нужные snippets (`websocket.conf`, private server block, webhook allow-list). Image и modules pin по digest/version. Render использует allow-list env и fail-fast для пустых host/cert/upstream/timeouts. Секреты в rendered config не требуются.
|
||||
|
||||
## 11. Docker Compose
|
||||
|
||||
`nginx` подключён к `public` и `backend` (или эквивалентным сетям своей VM). Публикация host ports разрешена только nginx. Filesystem read-only, tmpfs для cache/run/temp/`/etc/nginx/conf.d` по arch-03, non-root где позволяет bind ports/capabilities. Cert volumes read-only для nginx. ACME client имеет только необходимые volumes/network.
|
||||
|
||||
Non-root nginx получает writable tmpfs только для `/etc/nginx/conf.d`, `/var/cache/nginx`, `/var/run` и `/tmp`; tmpfs задаёт явные UID/GID/mode. Основной `nginx.conf` подключает конкретный rendered include, не неограниченный wildcard, который позволил бы обойти `nginx -t`.
|
||||
|
||||
`depends_on` health не заменяет retry/readiness. После healthy upstream
|
||||
обязателен config test с production service DNS names и reload/recreate nginx.
|
||||
После recreate upstream повторяется reload policy либо используется явно
|
||||
протестированный dynamic resolver. Nginx может временно отдавать bounded 502,
|
||||
но не считается ready до этой post-ready проверки.
|
||||
|
||||
Published Docker ports сопоставляются original host destination через conntrack/`DOCKER-USER` по arch-06.
|
||||
|
||||
## 12. Failure behavior
|
||||
|
||||
- upstream down: bounded 502/504, без SPA fallback и без переноса на другую VM;
|
||||
- cert renewal failed: текущий cert продолжает работу, alert до expiry;
|
||||
- invalid new config: reload отменяется, старые workers остаются;
|
||||
- disk/cache full: public cache bypass/evict, requests продолжаются где безопасно;
|
||||
- DNS upstream changed: resolver/restart policy восстанавливает адрес;
|
||||
- overload: 429/503 на edge, bounded queues; не накапливать неограниченные connections.
|
||||
|
||||
## 13. Общий Definition of Done
|
||||
|
||||
- на каждой VM ровно один nginx; независимые public `80/443` и собственные сертификаты;
|
||||
- TLS/ACME bootstrap, renewal и safe reload испытаны;
|
||||
- internal endpoints/ports извне недоступны;
|
||||
- request id и trusted forwarded headers корректны;
|
||||
- JSON logs коррелируют request/trace и не содержат секретов;
|
||||
- health/synthetic checks и failure tests проходят;
|
||||
- image non-root/read-only насколько возможно, versions pinned;
|
||||
- runbooks для cert, reload, upstream outage и rollback готовы.
|
||||
|
||||
Профильный DoD VM дополняет routing matrix, allow-list и listener этой машины.
|
||||
|
||||
## 14. Решения, допущения и TBD
|
||||
|
||||
**Решения:** независимые public nginx ВМ1/ВМ2; njs/module для UUID; public internal paths → 404; webroot ACME; CRM webhook приходит прямо на ВМ2; private `8443` ВМ2 только server-to-server.
|
||||
|
||||
**Допущения:** ВМ1 и ВМ2 используют разные public hosts и сертификаты; upstream service names стабильны внутри каждого Compose; S3 CORS настраивается отдельно.
|
||||
|
||||
**TBD:**
|
||||
|
||||
- N1: доверенные WAF CIDR — по VM.
|
||||
- N2: production cipher suite/OCSP.
|
||||
- N3: нужен ли публичный health.
|
||||
- N6: финальные burst/connection limits — по зонам VM.
|
||||
- N7: certbot vs другой ACME client после ops review.
|
||||
|
||||
N4 (CSP Expo) и N5 (Bitrix frame ancestors) — спецификация ВМ1.
|
||||
|
||||
## 15. Ссылки
|
||||
|
||||
- Compose/сети: [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).
|
||||
- ВМ1: [`module-03-nginx-vm1.md`](../VM1_app/documentation/module-03-nginx-vm1.md).
|
||||
- ВМ2: [`module-03-nginx-vm2.md`](../VM2_services/documentation/module-03-nginx-vm2.md).
|
||||
@@ -0,0 +1,205 @@
|
||||
# arch-09. Контракт Redis
|
||||
|
||||
> Канонический контракт Redis для всех application VM.
|
||||
> Реализация на конкретной VM — [`module-04-redis-vm1.md`](../VM1_app/documentation/module-04-redis-vm1.md) и [`module-04-redis-vm2.md`](../VM2_services/documentation/module-04-redis-vm2.md).
|
||||
> Compose/сети — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md). Env — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). Метрики exporter — [`arch-07-observability.md`](arch-07-observability.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
Документ фиксирует то, что **должно совпасть между ВМ1 и ВМ2**: Redis не source of truth, формат ключей, TTL, Lua governance, AOF/RDB, ACL/network, eviction и restore. Карты ключей DB0/DB1 и Redis Safety здесь не детализируются и не копируются в чужой репозиторий.
|
||||
|
||||
Этот контракт **нельзя** независимо кастомизировать так, чтобы Redis стал очередью, OTP store или единственной защитой от дублей.
|
||||
|
||||
## 1. Инварианты
|
||||
|
||||
Redis разделён по deployment/security boundary:
|
||||
|
||||
- Redis ВМ1: DB0 (`api-backend` idempotency/rate) и DB1 (realtime/coordination);
|
||||
- Redis Safety ВМ2: отдельный instance для hot cache, rate limiting и optional worker wake-up;
|
||||
- legacy DB2 ВМ1 существует только для test stub v1 до cutover и после него удаляется.
|
||||
|
||||
Redis не является бизнес-очередью, source of truth сообщений, sync tasks, audit, профилей или delivery checkpoint. Надёжные состояния остаются в managed PostgreSQL/S3. Потеря Redis может ухудшить сервис, но не должна создавать потерю подтверждённых сообщений либо дубль side effect.
|
||||
|
||||
OTP counters `api-backend` в Redis не хранит; они принадлежат Keycloak/SPI. `bitrix-sync` Redis не использует: `sync_queue`, leases и durable wake-up — PostgreSQL.
|
||||
|
||||
Logical DB — изоляция имён, не security boundary. ACL prefix и разные credentials обязательны.
|
||||
|
||||
## 2. Версия и topology
|
||||
|
||||
Redis 7.x, image закреплён по digest. На каждой VM одна нужная primary instance без replica/Sentinel в MVP. Клиенты используют connection pool, bounded timeouts и не выполняют опасные команды.
|
||||
|
||||
ВМ2 никогда не использует hostname Redis ВМ1 и наоборот.
|
||||
|
||||
## 3. Общие правила ключей
|
||||
|
||||
Формат: `han:{domain}:{purpose}:{hashed-or-public-id}:{version}`. Только ASCII lowercase separators. Public UUID допустим; IP, phone, email, token, text и filename — только HMAC/SHA-256 с server-side pepper там, где нужна защита dictionary attack.
|
||||
|
||||
- key length желательно ≤ 200 bytes;
|
||||
- значения versioned (`v=1`);
|
||||
- timestamps — Unix ms/seconds или RFC3339, формат фиксирован для каждого key;
|
||||
- wildcard `KEYS` production запрещён; только `SCAN` для ops;
|
||||
- каждый non-channel key имеет TTL, кроме явно обоснованных bounded structures;
|
||||
- large payload/presigned URL/token/message text запрещены.
|
||||
|
||||
Новый key без TTL запрещён contract test, кроме Pub/Sub channel (не key) и ops metadata с явным обоснованием.
|
||||
|
||||
## 4. Serialization и limits
|
||||
|
||||
- простые counters — integer;
|
||||
- locks — opaque random 128-bit token;
|
||||
- metadata — Redis HASH либо компактный JSON с `schema_version`;
|
||||
- max value target 32 KiB, hard application guard 128 KiB;
|
||||
- response cache хранит только allow-listed sanitized JSON;
|
||||
- decode error считается cache miss, key удаляется/карантинируется и поднимается metric.
|
||||
|
||||
## 5. TTL policy — реестр
|
||||
|
||||
| Категория | TTL | Владелец |
|
||||
|---|---|---|
|
||||
| idempotency completed | 24h по arch-02 | ВМ1 |
|
||||
| idempotency in-progress lock | 30s, heartbeat bounded | ВМ1 |
|
||||
| rate limit | window + 10–30% deterministic jitter | ВМ1 / ВМ2 по своим зонам |
|
||||
| realtime connection | 90s; set membership 120s | ВМ1 |
|
||||
| coordination lock | 30s | ВМ1 |
|
||||
| safety file hot cache | ≤30d; authoritative row/version в PostgreSQL | ВМ2 |
|
||||
| safety text-rules cache | 48h; invalidation by rules version | ВМ2 |
|
||||
| safety stable link policy cache | 48h | ВМ2 |
|
||||
| safety DNS cache | actual DNS TTL, hard max 900s | ВМ2 |
|
||||
|
||||
## 6. Atomicity и Lua governance
|
||||
|
||||
Scripts/functions хранятся в репозитории рядом с клиентом, versioned и тестируются на real Redis. Запрещены unbounded loops/SCAN внутри Lua. Входные массивы ограничены. Script timeout отслеживается; `SCRIPT KILL` runbook применяется только если нет writes либо после оценки.
|
||||
|
||||
Clock в rate/limit scripts — Redis `TIME`, не client wall clock. Script загружается при startup, SHA кэшируется; после `NOSCRIPT` выполняется контролируемый reload.
|
||||
|
||||
Redis transaction не координирует PostgreSQL/S3/HTTP. Cross-system consistency обеспечивается DB checkpoint/outbox и idempotent finalize.
|
||||
|
||||
Карта обязательных scripts — в спецификации VM.
|
||||
|
||||
## 7. Persistence
|
||||
|
||||
Решение MVP: AOF `appendonly yes`, `appendfsync everysec` плюс RDB snapshots (`save 900 1`, `300 100`, `60 10000` либо tuned). Это ускоряет восстановление ephemeral state, но не превращает Redis в authoritative store.
|
||||
|
||||
`aof-use-rdb-preamble yes`, automatic rewrite с порогами; volume `redis-data`. При corruption используется `redis-check-aof`/restore clean instance, а сервисы восстанавливают authoritative state из PostgreSQL.
|
||||
|
||||
RPO Redis до ~1 секунды приемлем, потому что бизнес-RPO задаётся PostgreSQL/S3. Backup Redis не обязателен для бизнес-восстановления, но периодическая копия RDB/AOF полезна для ops forensic без secrets.
|
||||
|
||||
## 8. Memory и eviction
|
||||
|
||||
`maxmemory` задаётся относительно container limit (ориентир 70–75%, оставляя overhead/fork). Начальная оценка для одной VM — 512 MiB, уточняется load test.
|
||||
|
||||
Eviction MVP: `volatile-lru`/`volatile-ttl`, так как все application keys имеют TTL. `allkeys-lru` опасен для idempotency при memory pressure; `noeviction` может полностью закрыть writes. Окончательный выбор после нагрузки: предпочтительно `volatile-lru` + alerts, а при разделении instances DB0 idempotency получает отдельную noeviction policy.
|
||||
|
||||
Контролируются `used_memory`, RSS, fragmentation, evicted_keys, expired_keys, key count/avg TTL по DB. OOM/eviction не должен создавать дубль бизнес-эффекта: это гарантирует PostgreSQL fallback, не Redis.
|
||||
|
||||
## 9. Sizing
|
||||
|
||||
Общая формула:
|
||||
|
||||
```text
|
||||
instance working set × 1.5 allocator/fragmentation × 1.3 growth reserve
|
||||
```
|
||||
|
||||
Состав working set считает спецификация VM. Pub/Sub output buffers и slow consumers имеют hard/soft limits. Load test фиксирует peak RPS, connections, record size и AOF rewrite headroom.
|
||||
|
||||
## 10. Auth, ACL и network boundary
|
||||
|
||||
Оба Redis не публикуют `6379` на host и подключены только к local Docker `backend` своей VM. `protected-mode yes`, default user отключён.
|
||||
|
||||
ACL users (имена общие, credentials разные на каждой VM):
|
||||
|
||||
- application user — только свои key prefixes и command categories;
|
||||
- `ops_health`: `PING`, ограниченный `INFO`;
|
||||
- `redis_exporter` — только `INFO`/`PING` и безопасные latency/keyspace metrics (arch-07 §8.3).
|
||||
|
||||
Redis ACL не ограничивает logical DB напрямую надёжно; key-prefix patterns и разные credentials обязательны. `SELECT` запрещается, клиент URL сразу задаёт DB, но ACL prefix остаётся основной защитой.
|
||||
|
||||
Dangerous/admin commands (`FLUSHALL`, `FLUSHDB`, `CONFIG`, `MODULE`, broad `KEYS`, replication changes) запрещены application users; rename-command не считается основной защитой.
|
||||
|
||||
Пароли сильные, только env/secret mount, rotation current/new через rolling deploy. Внутри одной VM TLS Redis опционален при закрытой Docker network; при выносе за host/VPC TLS обязателен (`rediss://`) и plaintext отключается.
|
||||
|
||||
## 11. Docker/runtime
|
||||
|
||||
```text
|
||||
redis/
|
||||
docker-compose.yml
|
||||
redis.conf
|
||||
users.acl.template
|
||||
scripts/
|
||||
tests/
|
||||
```
|
||||
|
||||
Compose: pinned Redis image, `expose: 6379`, без `ports`, `backend` network, `redis-data:/data`, config/ACL read-only, non-root UID, no-new-privileges, dropped capabilities, resource/memory/ulimit settings.
|
||||
|
||||
Startup валидирует config и ACL, permissions volume, затем Redis. Healthcheck использует ACL health user и `redis-cli --no-auth-warning PING`, secret не печатается. Graceful stop timeout позволяет AOF flush.
|
||||
|
||||
Credential URL не входит в общий публичный `.env` как секрет: secret file / arch-06.
|
||||
|
||||
## 12. Health — общие правила
|
||||
|
||||
`PING` проверяет liveness Redis; readiness приложений проверяет auth, correct DB и выполнение малого read/write/expire script без оставления key.
|
||||
|
||||
При latency выше threshold clients используют short timeout/circuit, не создают бесконечные retry storms. Reconnect — exponential backoff+jitter.
|
||||
|
||||
Degraded policy конкретного сервиса — спецификация VM.
|
||||
|
||||
## 13. Backup и restore
|
||||
|
||||
Redis backup не используется для бизнес restore. Runbook:
|
||||
|
||||
1. остановить/изолировать corrupted instance;
|
||||
2. при целостном AOF/RDB восстановить на отдельном instance и проверить;
|
||||
3. иначе поднять пустой Redis;
|
||||
4. приложения прогревают ephemeral state из PostgreSQL / reconnect;
|
||||
5. не копировать Redis dump в небезопасное место: keys содержат UUID и hashed identifiers.
|
||||
|
||||
Детали прогрева — спецификация VM.
|
||||
|
||||
## 14. Metrics и alerts — общие
|
||||
|
||||
- availability, commands/sec, latency percentiles;
|
||||
- connected/blocked clients, rejected connections;
|
||||
- memory/RSS/fragmentation, maxmemory ratio;
|
||||
- evictions/expirations/keyspace hits/misses;
|
||||
- AOF fsync latency/rewrite status/last save;
|
||||
- replication metrics зарезервированы;
|
||||
- key count/avg TTL по DB без key values;
|
||||
- script errors/NOSCRIPT/slowlog;
|
||||
- Pub/Sub subscribers/output buffer/slow disconnect.
|
||||
|
||||
Alerts: unavailable, p99 latency, >80/90% memory, any sustained evictions, AOF error, no recent persistence, client buffer pressure, unexpected keys without TTL.
|
||||
|
||||
Бизнес-метрики rate/idempotency/Safety cache — спецификация VM и arch-07. Keys/values не экспортируются.
|
||||
|
||||
## 15. Общий Definition of Done
|
||||
|
||||
- Lua scripts atomic, bounded, versioned и покрыты real Redis tests;
|
||||
- AOF/RDB, volume, restart и clean-instance recovery проверены;
|
||||
- maxmemory/eviction/resource limits основаны на load test либо явно TBD;
|
||||
- ACL users и network isolation работают, порт не published;
|
||||
- все application keys имеют TTL;
|
||||
- logs/metrics не содержат secret/value/PII;
|
||||
- Redis не используется как `sync_queue`, delivery queue, message/audit source of truth или OTP store.
|
||||
|
||||
Профильный DoD VM дополняет свои prefixes, URL и degraded policy.
|
||||
|
||||
## 16. Решения, допущения и TBD
|
||||
|
||||
**Решения:** по одной primary instance на каждую VM без replica/Sentinel в MVP; AOF everysec + RDB; Pub/Sub best effort; PostgreSQL durable fallback; prefix ACL; все application keys с TTL. Историческая схема «один instance / три DB» заменена разделением ВМ1 DB0/DB1 и Redis Safety ВМ2.
|
||||
|
||||
**Допущения:** Redis loss допустим без потери business truth; `bitrix-sync` Redis не использует.
|
||||
|
||||
**TBD:**
|
||||
|
||||
- R1: точный maxmemory после load profile — по VM.
|
||||
- R2: eviction policy после измерений.
|
||||
- R3: credential env names в arch-04.
|
||||
- R5: TLS при изменении network topology.
|
||||
- R6: момент разделения DB на instances — спецификация ВМ1.
|
||||
- R7: RPO/RTO ops target.
|
||||
- R4: Safety task TTL/recovery margin — спецификация ВМ2 (PostgreSQL, не Redis).
|
||||
|
||||
## 17. Ссылки
|
||||
|
||||
- ВМ1: [`module-04-redis-vm1.md`](../VM1_app/documentation/module-04-redis-vm1.md).
|
||||
- ВМ2: [`module-04-redis-vm2.md`](../VM2_services/documentation/module-04-redis-vm2.md).
|
||||
@@ -0,0 +1,340 @@
|
||||
# arch-10. Контракт развёртывания
|
||||
|
||||
> Канонический контракт rollout для всех application VM. OS-роли, SSH/sudo, secrets delivery и hardening — [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md); этот документ их не ослабляет.
|
||||
> Процедуры конкретной машины — [`module-10-deployment-vm1.md`](../VM1_app/documentation/module-10-deployment-vm1.md) и [`module-10-deployment-vm2.md`](../VM2_services/documentation/module-10-deployment-vm2.md).
|
||||
> Nginx TLS — [`arch-08-nginx.md`](arch-08-nginx.md). Redis — [`arch-09-redis.md`](arch-09-redis.md). Telemetry — [`arch-07-observability.md`](arch-07-observability.md). Compose — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
Документ фиксирует то, что **должно совпасть между ВМ1 и ВМ2** и то, что связывает их как систему: независимый deploy, VPC/SG, managed PG, S3, роли `deploy`/`admin`, секреты, TLS/ACME процедура, порядок cutover Safety, backup/rollback/DR.
|
||||
|
||||
Команды сервисов конкретной машины и её Compose **не** дублируются здесь. Агент репозитория VM читает этот контракт плюс свой runbook.
|
||||
|
||||
Прямые `docker compose` команды выполняются `admin` только при bootstrap/recovery либо инкапсулируются в утверждённые root-owned systemd-units. Они не являются основанием выдавать `deploy` доступ к Docker daemon. Значения в `<УГЛОВЫХ_СКОБКАХ>` — placeholders.
|
||||
|
||||
## 1. Неподвижные правила
|
||||
|
||||
1. Один root Compose project описывается в `<BACKEND_ROOT>` своей VM; в steady state его запускает root-owned systemd-unit/helper, а не пользователь из группы `docker`.
|
||||
2. На каждой VM ровно один nginx; ВМ1 и ВМ2 имеют независимые public DNS/TLS ingress и deployment lifecycle.
|
||||
3. Application containers не имеют public host ports. Nginx ВМ1 публикует свой `80/443`; nginx ВМ2 — отдельный `80/443` только для exact CRM webhook и private `8443` для Message Safety/internal access.
|
||||
4. `/internal/*` не маршрутизируется публично.
|
||||
5. Managed PostgreSQL находится вне Compose, в той же VPC, без public IP.
|
||||
6. S3 — внешний Selectel-compatible storage; клиент получает только presigned URL.
|
||||
7. Секреты не коммитятся, не вставляются в команды shell history и не выводятся в отчёты.
|
||||
8. Миграции выполняются отдельными one-shot steps до новой версии приложения.
|
||||
9. Message Safety запускается как documented stub до замены; это не production antivirus/moderation.
|
||||
10. `bitrix-sync` вводится только после выполнения preflight/cutover gates module-07; до этого `BITRIX_SYNC_ENABLED=false`, public webhook закрыт на edge.
|
||||
11. На ВМ1 и ВМ2 отдельные root Compose projects/systemd units; deploy/rollback выполняются независимо.
|
||||
12. ВМ2 — самостоятельная service VM с минимальным public webhook ingress, allow-listed egress, отдельным IAM principal и service-specific secret files.
|
||||
13. OS-роли, SSH/sudo, secrets delivery, container hardening и private-VM lockdown подчиняются arch-06.
|
||||
|
||||
## 2. Роли и обозначения
|
||||
|
||||
- **Cloud admin**: VPC, VM, PG, S3, DNS/security groups.
|
||||
- **Deploy operator / OS user `deploy`**: запуск утверждённых release/rollback/migration systemd-units; без группы `docker`, записи в production compose/unit/scripts/config и общего sudo.
|
||||
- **Break-glass OS user `admin`**: bootstrap и аварийное восстановление; не используется для штатного деплоя.
|
||||
- **OS user `tunnel`**: только allow-listed local TCP forwarding к private endpoints; без sudo/shell operations.
|
||||
- **Bitrix admin**: local app, connector, Open Line 8, callbacks.
|
||||
- **Security owner**: secrets, Keycloak admin MFA, firewall, retention.
|
||||
- **Safety Service Owner**: API/data contract, capacity result и v2 cutover/rollback sign-off.
|
||||
- **Rule Pack Owner**: rules bundle, corpus, monitor report и version release.
|
||||
- **Product Owner**: mnemonic `safety.chat.blocked` и business acceptance chat flow.
|
||||
- **Operations Owner**: VM2 alerts, ClamAV signatures, incident/reprovision/restore rehearsal.
|
||||
|
||||
```text
|
||||
<PUBLIC_HOST> например chat.example.ru
|
||||
<VM_PUBLIC_IP> публичный IPv4 ВМ1
|
||||
<PROCESSING_PUBLIC_HOST> отдельный public host ВМ2, например processing.example.ru
|
||||
<VM2_PUBLIC_IP> публичный IPv4/LB address ВМ2
|
||||
<VM_PRIVATE_IP> приватный IPv4 VM
|
||||
<VPC_CIDR> например 10.20.0.0/24
|
||||
<PG_PRIVATE_HOST> private FQDN/IP managed PG
|
||||
<PG_PORT> 5432 или 6432
|
||||
<PG_DATABASE> han_chat
|
||||
<BACKEND_REPO_URL> URL репозитория этой VM
|
||||
<BACKEND_ROOT> /opt/han-chat/backend
|
||||
<RELEASE> immutable tag/git SHA
|
||||
<ACME_EMAIL> адрес ops, не placeholder в реальном запуске
|
||||
<BITRIX_PORTAL> разрешённый портал
|
||||
<IDGTL_SENDER_NAME> согласованное в Direct имя отправителя
|
||||
<IDGTL_STATIC_EGRESS_IP> фактический статический egress IP `sms-worker`
|
||||
<IDGTL_TEST_PHONE> контролируемый номер для provider smoke
|
||||
```
|
||||
|
||||
`<BACKEND_ROOT>` и `<BACKEND_REPO_URL>` **разные** у ВМ1 и ВМ2.
|
||||
|
||||
## 3. Stage 0 — решения до provisioning
|
||||
|
||||
Зафиксировать: region/AZ и VPC; hostnames и TTL DNS; VM image Ubuntu 24.04 LTS; sizing каждой VM; PG plan/storage/backups/PITR; S3 region/endpoint/bucket names; container registry и immutable image tags/digests; remote observability backend; RPO/RTO и maintenance window; ответственных за alerts/Bitrix/Keycloak; Safety Service/Rule Pack/Product/Security/Operations owners и approvals cutover.
|
||||
|
||||
Sizing и load gates конкретной машины — профильный runbook. Monthly availability SLO для MVP не задаётся; это не отменяет latency/load gates и alerts.
|
||||
|
||||
### Gate 0
|
||||
|
||||
- [ ] Владельцы и maintenance window назначены.
|
||||
- [ ] RPO/RTO приняты хотя бы временно: ориентир RPO PG ≤15 минут/PITR, RTO ≤4 часа.
|
||||
- [ ] Решено: images pull из registry или build на VM.
|
||||
- [ ] Remote telemetry backend выбран либо явно принят ограниченный debug-only режим.
|
||||
- [ ] Риск mock OTP до SMS cutover и Safety stub письменно принят; real SMS не включается без gates module-11.
|
||||
|
||||
**Ожидаемый результат:** есть release checklist с конкретными values; не создано ни одной публичной БД/Redis.
|
||||
|
||||
## 4. Stage 1 — VPC, DNS и security groups
|
||||
|
||||
Создать private subnet для ВМ1, ВМ2, SigNoz и managed PG. ВМ1 и ВМ2 имеют отдельные public IP/LB только для своих nginx; service-to-service и PostgreSQL traffic остаётся private. SSH к обеим VM — только ops VPN/bastion.
|
||||
|
||||
| Source | Destination | Port | Rule |
|
||||
|---|---|---:|---|
|
||||
| trusted ops CIDR/VPN | ВМ1, ВМ2 | SSH `<SSH_PORT>` | allow |
|
||||
| internet | ВМ1 | TCP 80/443 | allow edge redirect/ACME/application |
|
||||
| internet | nginx ВМ2 | TCP 80/443 | allow ACME/redirect + exact CRM webhook |
|
||||
| ВМ1 SG | ВМ2 | TCP 8443 | private TLS only |
|
||||
| ВМ1/ВМ2 service SG | managed PG | `<PG_PORT>` | allow по нужным DB roles |
|
||||
| ВМ2 collector | private SigNoz | TCP 4317 | allow |
|
||||
| ВМ2 workers | S3 endpoints | TCP 443 | allow |
|
||||
| ВМ2 `bitrix-sync` | approved Bitrix portal | TCP 443 | allow |
|
||||
| ВМ2 `freshclam` | approved signature CDN | TCP 443/80 по vendor manifest | allow |
|
||||
| ВМ2 | trusted DNS/NTP | UDP/TCP 53, UDP 123 | allow |
|
||||
| internet | managed PG | any | deny |
|
||||
| internet | ВМ2 | any кроме nginx 80/443 | deny ingress |
|
||||
| internet | обе VM | 6379, 4317, 4318, 8000, 8080, 8443, 9000 | deny public |
|
||||
|
||||
ВМ2 использует default-deny egress. Registry/OS repositories открываются только в bootstrap/controlled window и затем снова закрываются. Если provider SG не умеет destination allow-list, применяется host firewall/proxy/NAT policy; постоянный open egress для ВМ2 не является допустимым production состоянием.
|
||||
|
||||
DNS: `A <PUBLIC_HOST> → <VM1_PUBLIC_IP>`, `A <PROCESSING_PUBLIC_HOST> → <VM2_PUBLIC_IP>`, private DNS `processing.internal → <VM2_PRIVATE_IP>`. Public host ВМ2 используется только CRM webhook; private name не публикуется во внешнем DNS.
|
||||
|
||||
### Gate 1
|
||||
|
||||
- [ ] PG не имеет public endpoint.
|
||||
- [ ] SSH доступен только trusted source.
|
||||
- [ ] Снаружи открыты только 80/443/ограниченный SSH.
|
||||
- [ ] DNS стабильно разрешается с нескольких resolver.
|
||||
- [ ] Каждая VM достигает private PG и своих внешних HTTPS endpoints.
|
||||
|
||||
**Ожидаемый результат:** `nc -vz <PG_PRIVATE_HOST> <PG_PORT>` с VM успешен; с внешней машины PG недоступен.
|
||||
|
||||
## 5. Stage 2 — hardening Ubuntu
|
||||
|
||||
Процедура первичная для **каждой** application VM. Скрипт-прототип [`../../HAN_chat/deploy/setup-vm-han-chat.sh`](../../HAN_chat/deploy/setup-vm-han-chat.sh) полезен для UFW, fail2ban, Docker и `DOCKER-USER`. Перед production: review версии; не передавать IP/ключи в git; проверить unattended upgrades; `deploy` не в группе `docker`; `AllowTcpForwarding no` по умолчанию; отдельный `tunnel` при необходимости; root-owned systemd-units и `/etc/sudoers.d/deploy` без wildcard.
|
||||
|
||||
`PUBLIC_DOCKER_PORTS` задаёт профильный runbook (ВМ1: `80,443`; ВМ2 — свои public 80/443, private 8443 не internet).
|
||||
|
||||
Проверки: `sshd -t`, UFW, fail2ban, Docker/Compose versions, `iptables -L HAN-CHAT-DOCKER`, timedatectl, disk/swap. Второй SSH session как `deploy`, затем отключить root/password login.
|
||||
|
||||
### Gate 2
|
||||
|
||||
- [ ] SSH key login `deploy` проверен во втором сеансе.
|
||||
- [ ] Root/password auth выключены.
|
||||
- [ ] `deploy` не состоит в группе `docker`; `sudo -l` содержит только утверждённые конкретные systemd-команды.
|
||||
- [ ] Production compose, units, deploy scripts и secret mappings принадлежат root и недоступны `deploy` на запись.
|
||||
- [ ] UFW и DOCKER-USER активны после restart Docker.
|
||||
- [ ] Для published Docker ports allow rules сопоставляют original host destination через `conntrack --ctorigdstport`; positive/negative probes увеличивают counters нужных allow/deny rules после restart Docker и reboot.
|
||||
- [ ] После обновления firewall helper active `oneshot RemainAfterExit` unit явно перезапущен; `enable --now` не считается применением новой версии.
|
||||
- [ ] Docker Engine/Compose plugin закреплены поддерживаемой версией.
|
||||
- [ ] NTP active; disk/swap соответствуют sizing.
|
||||
- [ ] Break-glass процедура сохранена вне VM.
|
||||
|
||||
**Ожидаемый результат:** reboot VM не теряет SSH, firewall и Docker service.
|
||||
|
||||
### 5.1. Lockdown private/no-egress VM
|
||||
|
||||
Для SigNoz и другой VM, которая после раскатки не должна иметь internet ingress/egress, bootstrap выполняется по lifecycle arch-06. Checklist закрытия public IP/SSH/egress и запрет постоянного open egress — arch-06; повторное открытие только break-glass с повтором lockdown.
|
||||
|
||||
## 6. Stage 3 — managed PostgreSQL
|
||||
|
||||
До создания схем включить daily backup, PITR, encryption at rest, TLS `sslmode=verify-full` + provider CA, alerts (disk/connections/CPU/IO/replication/backup/CA), deletion protection.
|
||||
|
||||
CA: `/opt/han-chat/secrets/pg/ca.pem`, `root:deploy` `0440` (либо `0400`). Режимы `disable`/`allow`/`prefer`/`require` без проверки CA для production запрещены.
|
||||
|
||||
Публичные HTTPS-сертификаты `<PUBLIC_HOST>` и `<PROCESSING_PUBLIC_HOST>` выпускаются отдельно через Let's Encrypt на Stage 9 nginx соответствующей VM, не в PostgreSQL.
|
||||
|
||||
Целевая модель ролей: admin/bootstrap; migration role каждого schema с DDL; runtime без DDL. Прототип `init-managed-postgres.py` слишком широк для production runtime.
|
||||
|
||||
Шесть schemas: `han_app`, `bitrix_local`, `bitrix_sync`, `keycloak`, `message_safety`, `sms`. Schema owner = migration role; runtime: `USAGE`, DML и sequence grants только на свои objects; `bitrix_sync_user` — только grants module-07 §13.
|
||||
|
||||
Ownership миграций:
|
||||
|
||||
1. `api-backend` Alembic — `han_app`;
|
||||
2. `bitrix-local-app` — `bitrix_local`;
|
||||
3. `message-safety` stub не создаёт PG tables до production implementation;
|
||||
4. `bitrix-sync` — schema `bitrix_sync`; api-backend отдельно мигрирует shared `han_app.sync_queue`;
|
||||
5. Keycloak — standard tables; custom provider — свои migrations;
|
||||
6. `sms-service` — schema `sms`; runtime `sms_user` без доступа к `han_app`/`keycloak`.
|
||||
|
||||
Только expand/migrate/contract. Destructive migration — отдельный backup, approval и release. Downgrade data migrations не обещается.
|
||||
|
||||
### Gate 3
|
||||
|
||||
- [ ] Backups/PITR/TLS/deletion protection включены.
|
||||
- [ ] Шесть schemas/roles созданы, включая `sms`/`sms_user`.
|
||||
- [ ] Runtime roles не имеют DDL/чужого доступа.
|
||||
- [ ] Migration credentials отделены от runtime.
|
||||
- [ ] Empty/previous-version migration test успешен.
|
||||
- [ ] PITR restore point создан перед первым release.
|
||||
|
||||
## 7. Stage 4 — S3
|
||||
|
||||
Три приватных bucket: `<PREFIX>-quarantine`, `<PREFIX>-attachments`, `<PREFIX>-documents`. Public ACL/listing выключены. Versioning для data buckets по policy; SSE включить.
|
||||
|
||||
IAM: API role — exact prefixes, presign PUT quarantine, Head/copy/delete quarantine, write/read data; Safety role — **read-only quarantine**; backup/ops отдельно; frontend — никаких permanent credentials. Отдельный IAM principal ВМ2.
|
||||
|
||||
CORS quarantine: `AllowedOrigins` только `https://<PUBLIC_HOST>`; PUT; headers `Content-Type`, `If-None-Match`, checksum; не `*` origin с credentials.
|
||||
|
||||
Lifecycle: quarantine failed/orphan expire 48 ч только при отсутствии active `safety_tasks`; incomplete multipart abort 1 день; attachments/documents без auto-delete до legal retention.
|
||||
|
||||
### Gate 4
|
||||
|
||||
- [ ] Все buckets private.
|
||||
- [ ] API key не может list/write вне exact scope.
|
||||
- [ ] Safety key не может write/delete.
|
||||
- [ ] Browser test origin выполняет presigned PUT с checksum и `If-None-Match: *`; повтор того же key получает `412`.
|
||||
- [ ] Complete фиксирует authoritative `version_id`, ETag и checksum; Safety читает только эту version.
|
||||
- [ ] Wrong version/ETag и изменённый source дают deny/error и не promote-ятся.
|
||||
- [ ] Conditional promote mismatch не создаёт delivery outbox/Bitrix call.
|
||||
- [ ] Quarantine lifecycle не удалит active `safety_tasks`.
|
||||
- [ ] Data lifecycle соответствует retention.
|
||||
|
||||
## 8. Общий layout релиза и секретов
|
||||
|
||||
На каждой VM:
|
||||
|
||||
```text
|
||||
/opt/han-chat/
|
||||
backend/ # checkout текущего release
|
||||
releases/<RELEASE>/ # optional immutable release dirs
|
||||
secrets/ # не в git
|
||||
backups/ # только metadata/short-lived encrypted artifacts
|
||||
```
|
||||
|
||||
Рекомендуемый rollout — immutable images из registry. Deploy из mutable branch без recorded SHA запрещён. `latest` отсутствует.
|
||||
|
||||
`.env` — только несекретный config и `SECRETS_SOURCE=file|selectel`. Секреты выдаёт `deployment/secrets/han-secrets`. `docker compose config` может раскрыть secrets; stdout не публиковать.
|
||||
|
||||
Validation: нет `change-me`, paired tokens equal, PG TLS, public HTTPS, `FRONTEND_DEV_PROXY_ENABLED=false` на production-like.
|
||||
|
||||
Состав `.env`/secret groups — профильный runbook.
|
||||
|
||||
## 9. TLS/ACME процедура
|
||||
|
||||
Webroot two-phase, независимо в root Compose каждой VM, без `compose down`. Staging CA rehearsal, затем production `--cert-name` текущего host. Сертификат и ACME volume между VM не разделяются.
|
||||
|
||||
Renew: systemd timer дважды в сутки. Скрипт `<BACKEND_ROOT>/deploy/ssl-renew.sh`:
|
||||
|
||||
1. взять `flock`;
|
||||
2. `docker compose --profile certbot run --rm certbot renew --webroot -w /var/www/certbot --quiet`;
|
||||
3. при обновлении проверить `docker compose exec -T nginx nginx -t -c /tmp/nginx.conf`;
|
||||
4. только после успеха `docker compose kill -s HUP nginx`;
|
||||
5. записать результат и метрику expiry;
|
||||
6. ненулевой exit при ошибке;
|
||||
7. не удалять действующий сертификат;
|
||||
8. success path — `0` и пустой stderr.
|
||||
|
||||
Пример unit `/etc/systemd/system/han-chat-cert-renew.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Renew HAN Chat Let's Encrypt certificate
|
||||
Requires=docker.service
|
||||
After=docker.service network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=deploy
|
||||
WorkingDirectory=<BACKEND_ROOT>
|
||||
ExecStart=<BACKEND_ROOT>/deploy/ssl-renew.sh
|
||||
```
|
||||
|
||||
Timer `OnCalendar=*-*-* 03,15:20:00`, `RandomizedDelaySec=30m`, `Persistent=true`. `enable --now`, `list-timers`, `certbot renew --dry-run`. Alert <21 дней, page <7 дней. Ошибка renew не останавливает nginx.
|
||||
|
||||
Staging issuance, затем production `--cert-name` текущего host. Host для ВМ1 — `<PUBLIC_HOST>`; для ВМ2 — `<PROCESSING_PUBLIC_HOST>`; private `8443` — internal CA, не Let's Encrypt. Сертификат и ACME volume между VM не разделяются.
|
||||
|
||||
## 10. Сквозной порядок startup и cutover
|
||||
|
||||
1. На ВМ2 unit поднимает Redis Safety и local Collector.
|
||||
2. Затем `clamd`/`freshclam`, Safety API/worker и `bitrix-sync`.
|
||||
3. Последним на ВМ2 — nginx public `80/443` и private `8443`.
|
||||
4. На ВМ1 — Redis/Collector, API, SMS, Keycloak, local app, edge nginx.
|
||||
5. Только private `MESSAGE_SAFETY_URL` ВМ1 переключается на ВМ2 после Safety gates. Public CRM webhook DNS/routes ВМ2 не требуют изменения ВМ1.
|
||||
|
||||
Legacy single-VM `docker compose up` не является evidence готовности target ВМ2. Один `message_id` нельзя одновременно отправлять в v1 и v2. После cutover ВМ1 не содержит local Safety/Redis DB2.
|
||||
|
||||
При потере ВМ2 fail-open запрещён. Reprovision ВМ2 из immutable image/config; Redis пустой; gates повторяются. RTO ≤4 ч; restore rehearsal минимум дважды в год.
|
||||
|
||||
## 11. Observability, opening traffic, backup, rollback
|
||||
|
||||
Gate 5–14 не дублируются в этом общем контракте: это профильные release gates конкретной VM, описанные в `module-10-deployment-vm1.md` / `module-10-deployment-vm2.md` и executable runbook соответствующего репозитория. Нумерация внутри VM2 runbook локальна его технической приёмке; системное открытие трафика всегда требует подписанных результатов обоих контуров.
|
||||
|
||||
**Gate 15 — observability:** arch-07 и профильный module-09 VM spec; сквозной `request_id` через nginx ВМ1 → API → Safety ВМ2 обязателен до объявления production-ready.
|
||||
|
||||
**Gate 16 — opening traffic:** снять maintenance, включить HSTS после TLS test, сохранить digests/revisions, restore point и on-call, затем наблюдать 60 минут. До открытия должны быть подписаны Gate 0–4 этого документа, применимые Gate 5–14 профильных runbook обеих VM и Gate 15.
|
||||
|
||||
Backup: PG daily+PITR — business restore; S3 versioning; Redis не business backup; Keycloak в PG. Restore rehearsal в isolated VPC без production DNS/Bitrix callbacks.
|
||||
|
||||
Rollback приложения: previous immutable digests, без Alembic downgrade, `SCHEMA_BACKWARD_COMPATIBLE_CONFIRMED=true`. Backward-incompatible migration — только forward-fix или PITR. TLS: оставить старый config/cert при failure.
|
||||
|
||||
Запрещены: `docker compose down -v`, `docker system prune -a --volumes`, `DROP DATABASE` / `DROP SCHEMA`, recursive S3 delete, `certbot delete` active cert, unbounded logs, Redis `KEYS/FLUSH*`, ad-hoc DB DELETE.
|
||||
|
||||
Routine (автомат ежедневно): health/synthetics, PG backup/PITR, TLS expiry, disk/OTEL queue, Redis AOF/memory, DLQ/quarantine age. Еженедельно: image CVE, login/rate-limit trend, S3 inventory. Ежемесячно: OS patch, secret review, capacity, runbook drill.
|
||||
|
||||
```bash
|
||||
cd <BACKEND_ROOT>
|
||||
docker compose ps
|
||||
docker compose logs --since=15m <SERVICE>
|
||||
docker stats --no-stream
|
||||
docker system df
|
||||
docker compose exec -T nginx nginx -t
|
||||
```
|
||||
|
||||
Incident triage:
|
||||
|
||||
```bash
|
||||
cd <BACKEND_ROOT>
|
||||
date -Is
|
||||
docker compose ps
|
||||
docker stats --no-stream
|
||||
docker compose logs --since=10m --tail=500 <SERVICE>
|
||||
df -h
|
||||
free -h
|
||||
sudo ss -lntp
|
||||
sudo iptables -L HAN-CHAT-DOCKER -n -v
|
||||
```
|
||||
|
||||
PG: readonly `select now(), count(*) from pg_stat_activity`. Redis — только ops ACL `PING`. Secret literal не вставлять в ticket. Не replay message/DLQ до idempotency и ambiguous Bitrix outcome.
|
||||
|
||||
Типовые сценарии: API 503 — DB/Redis/JWKS/Safety circuits; send timeout — checkpoint, не новый key; Bitrix down — OAuth/circuit/DLQ; Redis loss — clean restart + polling; PG outage — не restart storm; disk full — known cache/old images после inventory; cert expiry — webroot/staging; secret leak — rotate, telemetry deletion.
|
||||
|
||||
Upgrades: notes → compatibility → PITR → staging → expand migration → one service at a time → E2E → contract later. Keycloak не пропускать unsupported majors.
|
||||
|
||||
DR потеря VM: новая Ubuntu в VPC, hardening, DNS, secrets из vault не со старого disk, exact images, Redis можно clean, existing PG/S3, TLS, ordered startup. Потеря PG: PITR в new instance, остановить writes, reconcile S3 orphans. Потеря S3: без versioning полное восстановление невозможно; отключить file ops. Compromise: isolate, forensic snapshot, rotate all, clean deploy, notify.
|
||||
|
||||
Перед teardown: inventory без secrets, revoke Bitrix/credentials, legal hold, DNS drain, deletion protection — отдельное approval, shared VPC/PG/S3 проверить.
|
||||
|
||||
## 12. Общий Definition of Done
|
||||
|
||||
- VM/VPC/DNS/SG/hardening соответствуют Gate 1–2;
|
||||
- managed PG private/TLS/backups/least privilege/migrations работают;
|
||||
- S3 private/IAM/CORS/lifecycle проверены;
|
||||
- `deploy` не имеет Docker/root-equivalent доступа;
|
||||
- независимые public 80/443; ВМ2 дополнительно private 8443;
|
||||
- после Safety cutover ВМ1 вызывает ВМ2 только по private HTTPS с проверенным CA bind;
|
||||
- container hardening — arch-06;
|
||||
- для каждой private/no-egress VM завершён lockdown;
|
||||
- backup restore и rollback rehearsed;
|
||||
- ops/incident/upgrade/DR owners назначены;
|
||||
- все assumptions/TBD приняты до открытия traffic.
|
||||
|
||||
Профильный DoD VM дополняет свои сервисы, Compose и cutover steps.
|
||||
|
||||
## 13. Допущения, TBD и конфликты
|
||||
|
||||
**Допущения:** D-A1 отдельные public hosts; D-A2 обязательный минимум — local Collector; D-A3 managed PG private/TLS/PITR; D-A4 Bitrix portal/connector/line — значения architecture; D-A5 mock OTP до SMS cutover как documented risk.
|
||||
|
||||
**TBD:** D-TBD1 domains/Expo redirect/Bitrix frame ancestors; D-TBD2 final VM1/PG sizing, public SLO, RPO/RTO; D-TBD3 legal retention; D-TBD4 secret manager; D-TBD5 Safety v2 cutover; D-TBD6 bitrix-sync cutover; D-TBD7 pinned versions; D-TBD8 exact CLI после реализации; D-TBD9 Keycloak admin VPN/MFA; D-TBD10 CSP/CORS/IAM; D-TBD11 SigNoz auth/retention/alert route.
|
||||
|
||||
**Конфликты:** module-07 documentation ≠ cutover; v1 stub ≠ production Safety; prototype PG init слишком широк; prototype TLS downtime; prototype `/bitrix-internal/*` запрещён; Compose/env могут ещё не содержать VM2/SMS artifacts.
|
||||
|
||||
## 14. Ссылки
|
||||
|
||||
- ВМ1: [`module-10-deployment-vm1.md`](../VM1_app/documentation/module-10-deployment-vm1.md).
|
||||
- ВМ2: [`module-10-deployment-vm2.md`](../VM2_services/documentation/module-10-deployment-vm2.md).
|
||||
- Прототипы: [`../../HAN_chat/Deploy_steps.md`](../../HAN_chat/Deploy_steps.md), [`../../HAN_chat/deploy/setup-vm-han-chat.sh`](../../HAN_chat/deploy/setup-vm-han-chat.sh), [`../../HAN_chat/deploy/init-managed-postgres.py`](../../HAN_chat/deploy/init-managed-postgres.py).
|
||||
Reference in New Issue
Block a user