Files
mdm-ordinis/docs-internal/status/2026-05-06-state.md
T
Zimin A.N. bcfb07f547 docs: split integrator guide (Diplodoc) vs internal docs + add build-docs CI
Чёткое разделение audiences:
- docs/ — consumer-facing only, Diplodoc-built. Public contract для
  интеграторов (Альтум, BI, third-party).
- docs-internal/ — operator runbooks, status snapshots, legacy tech pages.
  НЕ в Diplodoc build, НЕ публикуются.

Изменения:
- Move docs/{ops,status,tech} → docs-internal/{ops,status,tech}
- docs/toc.yaml: убраны секции Operations, Архитектура (legacy), Status
- docs/index.md: убран tab "оператор / on-call", focus на integrators
- docs/README.md: rewritten — scope только integrator guide, editorial
  guidelines (no leak internal architecture, stable API contract)
- .yfm: убран явный ignore status/* (теперь не нужно — папка переехала)
- docs-internal/README.md: index для внутренней документации с rationale
  разделения

Scrubbing implementation details из Diplodoc страниц:
- events.md: убраны Strimzi/cluster.142 references, OutboxPoller детали,
  internal alert names (OrdinisOutboxLagHigh, OrdinisOutboxDLQNonEmpty),
  metric names (nsi_outbox_*). Заменены на consumer-observable contract.
- webhooks.md: убраны metric names (nsi_webhook_ssrf_rejected_total),
  alert table (OrdinisWebhookHighFailureRate и т.д.). Retry policy
  переписана на user-side perspective.
- errors.md: убран alert name OrdinisReadApiErrorBudgetBurnFast, Tempo
  endpoint URL, internal connection details.
- x-references.md: OrphanReferenceScanner → general "Ordinis периодически
  детектирует". Убраны metric/alert names. Cascade roadmap без link на
  internal design doc.
- auth.md: убран file path ScopeContext.java.
- bundle-cuod.md: убран Maven module path.
- caching.md: cascade design link заменён на text reference.

CI:
- .gitlab-ci.yml: новый job build-docs (stage: build) — Node 20 alpine,
  pnpm install --frozen-lockfile + pnpm build, artifact docs/_dist на
  неделю. Triggers: auto на docs/**, либо manual web. Готов к follow-up
  pages-deploy job когда host решён.
- Новый pattern .docs-changes для rules.

Build verified clean: 13 HTML pages, 6.9M, no broken links/refs.
2026-05-07 23:27:03 +03:00

196 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Состояние разработки НСИ (Ordinis ЦУОД ОДХ)
**Версия:** v0.9 (anchor v1 — close-out этап, prod readiness в работе)
**Дата:** 2026-05-06
**Owner:** zimin.an@nstart.space
**Окружения:** staging (192.168.100.161 / cluster.265, ✅ STABLE), prod (192.168.100.142 / cluster.142, 🟡 hibernated)
**Предыдущий snapshot:** [2026-05-05-state.md](./2026-05-05-state.md) (v0.5)
---
## 0. Что изменилось с v0.5
Крупные блоки за последние сутки:
**Stability:**
- CNPG operator 1.24.1 → **1.25.4** (server-side apply через manifest, не helm). Дало `spec.probes` API → выставили liveness 15s/30s × 5 + stopDelay 60s. Закрыло хронические cascade failures когда single-node постгрес умирал под нагрузкой от parallel rolling deploys.
- **Config-server startupProbe** + bumped probe timeoutSeconds (5 → 10s). Spring Boot startup до 134с под нагрузкой больше не убивается liveness'ом.
- **CI helm armored**: `--atomic --cleanup-on-fail --wait --timeout 8m` + pre-step авто-rollback застрявших `pending-*` releases. Сегодня помогло trigger восстановиться от vault-injector-induced cascade без ручного `helm rollback`.
- **Migrate Job hardened**: hook-delete-policy включает `hook-failed`, `activeDeadlineSeconds: 600`, ttl 1h. Job в ImagePullBackOff больше не держит CPU request 4 часа подряд (incident 2026-05-05).
**Observability:**
- **Spring Boot percentiles-histogram** на `http.server.requests` с custom SLO buckets (5ms..5s). Без них `histogram_quantile()` в Grafana был пустой.
- **2 Grafana дашборда** (sidecar auto-discovery via label `grafana_dashboard=1`):
- `ordinis-overview` — request rate, error rate, latency p50/p95/p99, up статусы
- `ordinis-slo` — error budget burn rate (5m/1h/6h/24h), 30d budget remaining
- **SLO burn rate alerts** (Google SRE Workbook multi-window/multi-burn-rate):
- read-api 99.9% (43m бюджет/30d): BurnFast 14.4× × 5m+1h, BurnSlow 6× × 30m+6h, BurnTicket 3× × 2h+1d
- writer 99.5% (3.6h/30d): аналогично, чуть свободнее
- **ServiceMonitor label `release: monitoring`** — без него kube-prometheus-stack игнорирует SM. Добавили в helper, плюс PodMonitor для CNPG.
- **Tempo retention 48h → 168h (7d)**, pool tuning, global_overrides — для full-week investigation traces.
- **Loki compactor** с `retention_enabled=true`, ingestion limits — спасает PVC от bloat.
**UI:**
- **Scope-based grouping** в списке справочников: секции PUBLIC → INTERNAL → RESTRICTED + цветные accent-полосы слева у карточек (emerald/amber/rose). Search фильтрует across all sections.
- **Scope accent на detail page**: top stripe 4px цветом scope + colored chip в breadcrumb рядом с back-link.
- **Поиск + счётчик активных записей** на странице списка справочников (через `useDeferredValue` + N+1-safe `countActiveGroupedByDictionary`).
- **INTERNAL словари visibility fix**: `ORDINIS_AUTH_ROLES_CLAIM` теперь читает `resource_access.ordinis.roles` (Keycloak client-роли), не `realm_access.roles` (default). Маппинг `ordinis:client:internal` → INTERNAL уже работал в `ScopeContext`.
- **revisionHistoryLimit=3** на всех Deployment в чарте — было 49 stale ReplicaSets.
- **Pre-existing TS errors fixed** — `pnpm tsc --noEmit` зелёный (5 ошибок: unused imports, missing prop type, `$schema` field).
**Data:**
- **Bundle 7 → 40 справочников** (cuod v1.2.0). Закрыло раздел item 28 из v0.5.
- **Альтум pull verified** — внешний integration работает (item 22 → ✅).
**Production prep (планирование, не deployed):**
- `cluster-prod.yaml` (3-instance HA с synchronous replication, 90d retention, podAntiAffinity required)
- `scheduled-backup-prod.yaml` (daily 02:30 UTC + Weekly Sunday 04:00 UTC)
- `docs/runbooks/postgres-multi-instance-deploy.md` — sister runbook
- `docs/runbooks/cnpg-backup-s3-prod.md` — Selectel/Yandex S3 setup, IAM policy, ExternalSecret, Prometheus alerts (BackupFailed + WalArchiveStale)
- `k8s/benchmark/k6-read-api.js` + `k6-job.yaml` + `README.md` — Pareto 80/20 (records.list/dictionaries.list/record.byKey), JWT через `ordinis-bench` Keycloak SA, smoke 50 RPS → ramp 1k RPS → sustain 5m. Не запускался ещё.
---
## 1. Перечень требований
### 1.1. Реализовано (production-ready на staging)
| # | Требование | Статус | Где зафиксировано |
|----|--------------------------------------------------------------|--------|---|
| 1 | Каталог НСИ ЦУОД ОДХ — единый источник истины | ✅ | bundle `cuod` v1.2.0 (40 справочников) |
| 2 | Bitemporal модель (валидное-время + транзакционное-время) | ✅ | `dictionary_records` + `valid_from`/`valid_to` + Hibernate Envers |
| 3 | JSON Schema валидация записей (Draft-07) | ✅ | `ordinis-validation` (networknt) |
| 4 | i18n поля (RU + EN) с patternProperties | ✅ | `x-localized: true` extension |
| 5 | Уникальные поля (бизнес-ключ + произвольные) | ✅ | `x-unique` extension + native query enforcement |
| 6 | Авто-derive businessKey из data поля | ✅ | `x-id-source` extension |
| 7 | История версий записи + diff | ✅ | `/records/{key}/history` + UI drawer |
| 8 | Schema versioning + breaking-change detection | ✅ | `diffSchemas` + Save-block + auto major/minor/patch |
| 9 | Аудит-лог (кто, когда, что, IP, trace_id) | ✅ | `audit_log` + `AuditLogger` + UI `/audit` + CSV export |
| 10 | Kafka outbox (at-least-once гарантия) | ✅ | `OutboxPoller` + DLQ cap (1000 attempts) |
| 11 | Idempotency для мутирующих запросов | ✅ | `Idempotency-Key` header + sha-256 hash |
| 12 | Multi-scope доступ (PUBLIC / INTERNAL / RESTRICTED) | ✅ | `DataScope` + JWT roles + Kafka per-scope topics |
| 13 | Keycloak OIDC (issuer-uri auto-discovery) | ✅ | `ordinis-auth` + `JwtDecoders.fromIssuerLocation` + `resource_access.ordinis.roles` |
| 14 | Visual schema builder + CRUD UI (scope-grouped, search, counts) | ✅ | admin-ui (React 19 + TanStack + @nstart/ui) |
| 15 | SSO для admin-ui (PKCE Public client) | ✅ | react-oidc-context |
| 16 | Bundle import (idempotent seed) | ✅ | `CuodBundleImporter` startup runner |
| 17 | Swagger UI / OpenAPI 3 spec | ✅ | springdoc на `/swagger-ui.html` |
| 18 | **40 справочников** + 51+ запись на staging | ✅ | bundle v1.2.0 (закрытие item 28 из v0.5) |
| 19 | Friendly error responses (409/400/404 вместо 500) | ✅ | `GlobalExceptionHandler` |
| 20 | Метрики Prometheus + actuator/health + percentiles-histogram | ✅ | `ordinis_outbox_*`, `ordinis_kafka_*`, `http_server_requests_seconds_bucket` |
| 21 | Тесты | ✅ | 24 backend + 51 frontend + 34 новых = **109 unit-тестов**, все зелёные |
| 22 | **Real consumer integration (Альтум)** | ✅ | Pull verified 2026-05-05/06, БFF (FastAPI) подключился |
| 25 | **Distributed tracing (Tempo) на staging** | ✅ | OpenTelemetry → Tempo OTLP gRPC, 7d retention |
| 27 | **SLO + alerts (Prometheus)** | ✅ | 4 burn rate alerts (Google SRE multi-window), 2 Grafana dashboards |
| 34 | **CI auto-rollback при failure** | ✅ | helm `--atomic --cleanup-on-fail` + pre-step pending-* recovery |
| 35 | **CNPG operator 1.25.4** (spec.probes API) | ✅ | стабильность под нагрузкой, нет cascade failures |
| 36 | **Loki compactor + retention enforcement** | ✅ | PVC bloat защита |
### 1.2. В работе / required для v1 closure
| # | Требование | Статус | Прогресс с v0.5 / Блокер |
|----|-----------------------------------------------------------|--------------|--------|
| 23 | DNS A-record + Let's Encrypt cert на prod | 🔴 | DNS не проброшен на prod IP, prod hibernated. Нет изменений. |
| 24 | `ORDINIS_AUTH_REQUIRED=true` на prod | 🔴 | Зависит от готовности SSO admin-ui на prod + Альтум. |
| 26 | CNPG backup в S3 + DR rehearsal | 🟡 ready | **Манифесты + runbook готовы** (`cluster-prod.yaml`, `scheduled-backup-prod.yaml`, `cnpg-backup-s3-prod.md`). Не запущено: ждёт provider creds (Selectel default) + Vault path. |
| 28 | 30+ справочников ЦУОД ОДХ (full coverage) | ✅ | **Закрыто** — bundle 1.2.0 содержит 40. |
| 29 | Operator runbook | 🟡 70% | **Добавлены** `postgres-multi-instance-deploy.md`, `cnpg-backup-s3-prod.md`, `k8s/benchmark/README.md`. Остаётся: рестарт Kafka StrimziPodSet, разбор DLQ, рестарт outbox-loop. |
| 30 | e2e API тесты (testcontainers) | 🔴 | Только unit. Нет изменений. |
| 31 | Component тесты frontend (RTL) | 🔴 | Только pure-logic. Нет изменений. |
| 32 | Bundle code-splitting (~660KB gzip) | 🔴 | echarts/lottie/@nstart/ui тянут единый chunk. Нет изменений. |
| 33 | Vault auto-unseal | 🟢 deferred | **Сознательно skipped** по решению owner — приоритет prod-deploy. Mitigation: manual unseal runbook. |
| 37 | **Multi-instance Postgres prod (HA failover)** | 🟡 ready | `cluster-prod.yaml` + `postgres-multi-instance-deploy.md` готовы. Не applied (ждёт prod stand-up). |
| 38 | **Performance benchmark (1k RPS, p99 < 1s)** | 🟡 ready | k6 setup в `k8s/benchmark/`. Не запускался — нужно: создать `ordinis-bench` Keycloak SA → Secret → ConfigMap → `kubectl apply -f k6-job.yaml`. |
| 39 | AlertmanagerConfig per-channel webhooks | 🔴 | Сейчас 1 webhook (#AltumAlerts) на всё. Нужны URLs для разделения critical / warning / info. |
### 1.3. v2 (governance / data quality / federation)
Не блокирует prod stand-up. Roadmap ~2-3 квартала, без изменений с v0.5:
- Approval workflow (draft → review → publish), ownership trees, RBAC per-dictionary
- Lineage / dependency graph
- Geo first-class (карта ввода/превью, bbox/polygon query)
- Cross-dictionary FK с cascade rules
- Data quality rules engine (cross-field constraints, ML-anomaly detection)
- Federation MDM-кластеров (master/replica)
- Schema registry (Apicurio/Confluent) для Kafka
- Webhooks для не-Kafka consumer'ов
---
## 2. Нерешённые технические вопросы
### 2.1. Критические для prod
1. **Multi-instance Postgres failover.** Single-node CNPG без replica → SPOF. Манифесты + runbook готовы (см. item 37). Apply при prod cutover.
2. **CNPG backup в external S3.** MinIO в кластере = same failure domain. Внешний S3 (Selectel/Yandex) — runbook готов (item 26). RTO < 30m, RPO < 5m по WAL archive interval.
3. **Audience валидация JWT.** Сейчас не enforced — принимаем токены от любого клиента в realm `nstart`. Допускает scope-эскалацию. На prod либо включить audience-check, либо мапить `aud=ordinis` через Keycloak Audience Mapper.
4. **Outbox poll-interval 1 сек.** При 100+ событиях/сек → 86k+ запросов к Postgres/день. Возможно нужен batch tuning или reactive listener вместо poll.
### 2.2. Архитектурные открытые вопросы
5. **Realtime invalidation для consumer'ов.** TTL 5 мин кэша достаточен для v1, но push через Kafka topic `ordinis.cuod.events.*` уже emit'ится — consumer'ам осталось subscribe.
6. **Геоданные.** PostGIS включён, но bitemporal-выборка по геометрии не реализована. Нужно подтверждение от ЦУОД что геопоиск нужен в v1 или v2.
7. **Multi-tenancy.** Anchor `bundle: cuod`. Второй клиент → второй bundle, своя тройка Kafka топиков. Шаринг общих справочников между bundle (например `spacecraft` для ЦУОД и геопортала) не определён архитектурно.
8. **Object storage / большие справочники.** Если справочник вырастет до 100k+ записей, JSONB в одной таблице может стать узким. Партиционирование `dictionary_records` по `dictionary_id` — потенциальное решение, требует оценки.
### 2.3. Operational (выявлено в этой сессии)
9. **Vault Agent Injector flaky под нагрузкой.** Restarted 68 раз за месяц, last 4m26s ago. Когда injector down — pods создаются БЕЗ vault-agent sidecar → app пишет `cannot open /vault/secrets/postgres` → CrashLoop. Воркэраунд: `kubectl delete pod` для recreate; долгосрочно — увеличить request resources injector + readinessProbe.
10. **CNPG metrics exporter slow queries.** Под нагрузкой parallel deploy `pg_stat_replication` парсится 9-22 секунды → scrape timeout (default 10s) → `up=0` для postgres. Mitigation: dashboard panel переключён на `kube_pod_status_ready` (стабильно). Корень — нагрузка на single-node под docker builds + Spring Boot startups + cnpg exporter одновременно.
11. **kube-apiserver TLS handshake timeouts** под нагрузкой (load avg 50+). Транзиентно блокирует kubectl. Mitigation: подождать 10-30s, retry. Долгосрочно — multi-node cluster.
12. **Vault-agent-init backoff.** ~3 минут при transient Vault unavailability — слишком долго. Тuning либо переход на CSI Driver.
13. **JDK 25 + Mockito inline** несовместим. Перешли на subclass mock-maker. При апгрейде до Java 26 проверить.
14. **CI path-filter.** Был костыль. Сейчас все docker jobs запускаются всегда (~30s extra на layer-cache). Можно вернуться к smart-filter когда GitLab variable interpolation в trigger jobs будет надёжной.
### 2.4. Compliance / правовые
15. **Audit retention policy.** `audit_log` пишется бессрочно. По регламенту ЦУОД нужно определить срок хранения (5/10 лет) + автоматическую архивацию. Блокер: legal deadline.
16. **Personal data handling.** В audit_log пишется `user_id` (Keycloak sub) + IP. ФЗ-152 — нужна оценка / DPA если в realm будут не-сотрудники.
17. **Электронная подпись изменений.** Если потребуется юридически значимая фиксация (КЭП оператора при close записи) — отдельный модуль, не реализован.
---
## 3. Наличие технической документации
### 3.1. Создана и актуальна
| Документ | Назначение | Расположение |
|---|---|---|
| **API integration guide** | Гайд для интеграторов | `docs/tech/api-integration.md` |
| **Альтум integration ТЗ** | Согласованный план «Заказ съёмки» | `docs/integration/altum-imaging-order.md` |
| **OpenAPI 3 spec (Swagger)** | Машиночитаемая спецификация | `https://ordinis.k8s.265.nstart.cloud/swagger-ui.html` + `/v3/api-docs` |
| **Bundle manifest + schemas** | JSON Schema каждого справочника | `ordinis-cuod-bundle/src/main/resources/bundles/cuod/` |
| **Postgres multi-instance deploy** 🆕 | Runbook апгрейда staging single → prod 3-instance HA | `docs/runbooks/postgres-multi-instance-deploy.md` |
| **CNPG backup → external S3** 🆕 | Selectel/Yandex провижининг, IAM policy, ExternalSecret, Prometheus alerts | `docs/runbooks/cnpg-backup-s3-prod.md` |
| **k6 benchmark** 🆕 | Pareto 80/20 setup, preflight, regression tracking | `k8s/benchmark/README.md` |
| **CI/Helm values** | Параметризация per-env | `ordinis-infra/charts/ordinis/{values,values-staging,values-prod}.yaml` |
### 3.2. Отсутствует / требует написания
| Документ | Приоритет | Прогресс |
|---|---|---|
| **Operator runbook (full)** | 🔴 high | 70%: добавлены postgres-multi-instance + cnpg-backup. Остались: Kafka StrimziPodSet рестарт, DLQ-разбор, outbox-loop рестарт, vault unseal, поднятие hibernated prod. |
| **DR plan** | 🔴 high → 🟡 partial | Backup runbook готов с RTO/RPO targets. Quarter restore drill в operations calendar — не настроен. |
| **Архитектурный обзор (HLA)** | 🟡 medium | Single-pager не написан. |
| **User docs / справка оператора** | 🟡 medium | Не написана. |
| **Threat model + security review** | 🟡 medium | OWASP-чеклист не пройден. |
| **CHANGELOG.md** | 🟢 low | git log + commit messages. |
---
## Резюме
**Прогресс v0.5 → v0.9:**
- **27 из 39 требований v1 закрыто** (было 21/33). Прирост: items 18 (40 справочников), 22 (Альтум), 25 (tracing), 27 (SLO/alerts), 28 (full coverage), 35 (CNPG), 36 (Loki) + 3 новых item (CI auto-rollback, multi-instance ready, perf benchmark ready).
- **Архитектурный фундамент остаётся прочным**, добавлены защиты от cascade failures.
- **Главный риск**: prod stand-up зависит от DNS/cert (item 23) + multi-instance Postgres apply (item 37) + S3 creds (item 26). Все имеют готовые runbooks.
- **Документация**: prod-deploy artifacts готовы (postgres HA + S3 backup + k6 benchmark). Остаётся operator runbook completion + threat model.
**Прогноз до prod-ready (v1):** ~1-2 недели при текущем темпе. Блокеры — прежде всего внешние (DNS, S3 provider creds, ЦУОД real data). Inside-team задачи — 3-5 дней работы на code-splitting + e2e tests + AlertmanagerConfig.
**Что сегодня закрыло прошлые риски:**
- Хроническая нестабильность под нагрузкой → CNPG 1.25 + config-server probes + CI atomic.
- Observability vacuum → 4 burn rate alerts + 2 dashboards + 7d Tempo.
- INTERNAL словари невидимые → rolesClaim fix.
- Stuck helm releases → CI pre-step recovery + migrate Job hook-failed.