Files
mdm-ordinis/docs-internal/status/2026-05-07-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

175 lines
12 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 ЦУОД ОДХ)
**Версия:** v1.0.1 (post-cutover hardening: SPOF closed, monitoring stack ready, image arch incident resolved)
**Дата:** 2026-05-07 (evening update)
**Owner:** zimin.an@nstart.space
**Окружения:**
- **prod** (192.168.100.142 / cluster.142, ✅ LIVE с Альтум traffic, blue/green cutover завершён)
- **staging** (192.168.100.161 / cluster.265, ✅ STABLE — теперь dev environment, не consumer-facing)
**Предыдущий snapshot:** [2026-05-06-state.md](./2026-05-06-state.md) (v0.9, prod hibernated)
---
## 0. Что изменилось с v0.9
### Production stand-up на cluster.142 (Phase 1-5 done)
**Phase 1 — Postgres prod stand-up:**
- Discovery: cluster.142 НЕ hibernated (state.md устарел — k8s API v1.33.7 жив, ordinis-prod ns с Helm rev 17 deployed 4 дня назад, всё scaled to 0)
- CNPG cluster `ordinis-postgres` healthy, 50Gi local-path PVC
- Custom image `postgis-barman:18.3-3.6.2-1` (PostGIS 18 + barman-cli-cloud) для CNPG backups — официальный `cnpg/postgis:18-standard-trixie` НЕ имеет barman tools
- barmanObjectStore → `s3://altum-backups/ordinis-postgres` через `geoportal-prod-minio:9000` (re-use bucket с prefix isolation)
- ScheduledBackup: daily 02:30 UTC + weekly Sun 04:00 UTC
- Initial backup landed, PITR window started
**Phase 2 — Vault re-init:**
- Original Vault sealed после restart 17h, unseal key потерян
- Re-init с wipe `data-vault-0` PVC → init -key-shares=1 -key-threshold=1
- Setup playbook: `kubernetes` auth, policy `ordinis-app`, 4 roles (per-SA binding)
- 6 secret paths populated из existing k8s secrets (CNPG postgres, Strimzi KafkaUsers, Redis)
- Scripts: `ordinis-infra/scripts/vault/{setup,populate}.sh` + README
**Phase 3 — Kafka cluster recovery:**
- `ordinis-kafka` Strimzi cluster CR не было в namespace (только orphaned KafkaUsers + 1 service)
- Apply `ordinis-infra/k8s/kafka/cluster-prod.yaml` (KafkaNodePool dual + Kafka KRaft + 3 KafkaTopics)
- 1 broker, single-instance, SCRAM-SHA-512 auth, 20Gi local-path PVC
- Strimzi operator уже watches ordinis-prod (STRIMZI_NAMESPACE=kafka-test,ordinis-prod)
- Cluster Ready через 90s после apply
**Phase 4 — App deploy:**
- `config-server`, `ordinis-redis-0` scaled up (были 0/0)
- Helm upgrade `ordinis` rev 17 → 22 на `image.tag=main-4252ad93`
- Replicas bumped: writer 1→2, readApi 1→2 (HA для consumer-facing)
- 9 pods Running, /actuator/health → 200, /api/v1/dictionaries → 200 + 40 dicts seeded
**Phase 5 — Альтум cutover:**
- Blue/green phased shift через Альтум config (10% → 50% → 100%)
- staging-as-prod decommissioned для production traffic
- staging cluster.265 теперь чисто dev environment
### 0.1. Post-cutover hardening (evening 2026-05-07)
**Артефакты добавлены:**
- `k8s/benchmark/k6-job-prod.yaml` — load test для prod (Service port :80 fix)
- `k8s/benchmark/synthetic-smoke-prod.yaml` — one-shot smoke (auth + 3 endpoints, exit code = pass/fail)
- `k8s/benchmark/synthetic-smoke-cronjob-prod.yaml` — continuous monitoring (*/30 min)
- `k8s/kafka/topic-orphan-fix.sh` — janitorial script для KafkaTopic READY=False (✅ applied 2026-05-07 evening — все 3 topics unmanaged, alerts gone)
- prometheus-rules group `ordinis.synthetic` (2 alerts: SmokeFailed/Stale, conditional на `prometheusRule.synthetic.enabled`)
- `.gitlab-ci.yml` job `build-postgis-barman` (kaniko + REGISTRY_USER/REGISTRY auth, currently 401 — known issue, manual push fallback)
**Инцидент: postgis-barman image swap** (08:30-22:00 MSK):
1. Custom image `:18.3-3.6.2-1` запушен в `repo.nstart.cloud` с mac M-series **без `--platform linux/amd64`** → arm64 layers попали в registry
2. Catalog patched на repo.nstart.cloud → CNPG re-roll → `exec format error` → CrashLoopBackOff
3. Rollback catalog на staging URL → recovered
4. Re-push amd64 → но **kubelet cache на srvaltum01** удерживал arm64 layer под тем же тегом → retry catalog swap снова exec format error
5. Bump rev `:1``:2` → kubelet вынужден pull → cache miss → новый amd64 layer → success
6. Pod stuck Terminating ~14 мин (CNPG `terminationGracePeriodSeconds: 1800`) → `kubectl delete pod --force --grace-period=0` для recovery acceleration
**Lessons learned (закреплены в `docker/postgis-barman/README.md`):**
- `docker buildx --platform linux/amd64 --push` обязательно когда build с M-series mac
- Bump rev tag при cache invalidation needs (тот же тег + новые слои = kubelet skip)
- `terminationGracePeriodSeconds=1800` означает что graceful pod delete может висеть до 30 минут
- Verify image arch через registry API (`config.architecture`) ДО catalog swap
**Production verify (post-incident):**
- Cluster phase: "Cluster in healthy state"
- status.image: `repo.nstart.cloud/terravault/postgis-barman:18.3-3.6.2-2`
- imageID digest: `sha256:1b4b074d...` (verified amd64 manifest)
- All 11 pods Running, postgres restartCount=0, lastSuccessfulBackup preserved
**SPOF на staging registry — closed.** Если `registry.k8s.265.nstart.local` упадёт, prod не пострадает.
### Что закоммичено
**ordinis-infra (4 commits сегодня):**
- `cluster-prod.yaml` aligned с geoportal pattern (local-path, internal MinIO, single-instance) — было overcomplicated с Longhorn/Yandex S3
- `values-prod.yaml`: writer=2, readApi=2, host `ordinis.k8s.264.nstart.cloud`, `selfsigned-cluster-issuer`
- `prometheus-rules.yaml`: 4 CNPG HA alerts (conditional `cnpgHa.enabled` flag, off для single-instance)
- `k8s/kafka/cluster-prod.yaml` (NEW): Kafka CR + NodePool + 3 Topics для recovery
- `scripts/vault/{setup,populate}.sh + README.md` (NEW): playbook bootstrap
**ordinis (8 commits сегодня — задеплоены в prod):**
- `c7a917d` test(webhook): unit-test WebhookTestPingService — 7 кейсов
- `ac93151` test(webhook): re-enable WebhookE2ETest через no-op scheduler + manual ticks
- `92507ab` feat(audit): URL persistence фильтров + chip-list активных
- `5941207` feat(records): bulk close — multi-select toolbar + per-record транзакция
- `640a633` test(records): unit-test BulkRecordService — 10 кейсов
- `66a4f13` test(geo): e2e auto-derive geometry из data.lat/lon — 6 кейсов
- `4252ad9` feat(records): bulk export CSV выбранных + pagination
- + 23 новых теста (7 webhook + 10 bulk + 6 e2e)
---
## 1. Перечень требований
### 1.1. Реализовано (production-ready, LIVE на prod cluster.142)
Та же таблица что v0.9 + новые пункты:
| # | Требование | Статус |
|----|---------------------------------------------------------------|--------|
| 1-32 | (см. v0.9, всё уже было ready) | ✅ |
| 33 | Production deployment на real cluster (cluster.142) | ✅ NEW |
| 34 | Bulk close записей с multi-select UI | ✅ NEW |
| 35 | Bulk CSV export выбранных записей | ✅ NEW |
| 36 | Audit log URL-persistent filters + chip-list | ✅ NEW |
| 37 | Pagination для records list (50/page client-side) | ✅ NEW |
| 38 | WebhookE2ETest stable на CI (no-op scheduler + manual ticks) | ✅ NEW |
| 39 | Auto-derive POINT geometry из data.lat/lon (3 пути backend + Liquibase backfill 0015) | ✅ |
| 40 | CNPG continuous WAL archive + scheduled backups в external S3 | ✅ NEW |
### 1.2. В процессе / nice-to-have
- ~~KafkaTopics READY=False~~ ✅ **APPLIED 2026-05-07 evening** через `k8s/kafka/topic-orphan-fix.sh`. Все 3 topics (.public/.internal/.restricted) теперь annotate'нуты `strimzi.io/managed=false` — Strimzi user-operator больше не пытается reconcile (ошибка "Decreasing partitions not supported" гасла). Topics в брокере продолжают жить, projection-writer connected. Alerts на READY=False больше не срабатывают.
- Vault SASL passwords могут быть stale если Strimzi user-operator regenerated после Kafka cluster recreate — re-run `scripts/vault/populate.sh`
- ~~`postgis-barman` image SPOF на staging registry~~ ✅ **CLOSED 2026-05-07 evening:** image mirrored в `repo.nstart.cloud/terravault/postgis-barman:18.3-3.6.2-2`, prod cluster.142 pull'ит оттуда. См. секцию 0.1 ниже про инцидент.
- k6 benchmark на prod не прогнан (smoke 50 RPS / ramp 1k / sustain 5m готов в `k8s/benchmark/k6-job-prod.yaml`)
- Synthetic smoke + alerts ready (`k8s/benchmark/synthetic-smoke-cronjob-prod.yaml` + prometheus-rules group `ordinis.synthetic`). **Pre-flight partial:** `k6-tests` ConfigMap создан в `ordinis-prod` (test script). Осталось: создать `k6-keycloak-creds` Secret (требует Keycloak SA `ordinis-bench` client_secret — на стороне Keycloak admin), apply CronJob, helm upgrade с values-prod для активации alerts
- Real ЦУОД data integration (отдельный epic после prod soak)
### 1.3. Roadmap (отложено)
- **Approval workflow v2** (draft → review → publish) — design doc сохранён в `~/.gstack/projects/claude/zimin-main-design-approval-workflow-v2-20260507-121632.md`. Defer'нут до prod LIVE + observed compliance needs. Re-evaluate через 1-2 месяца после Альтум stable.
- **True HA Postgres** (3-instance + Longhorn + external Yandex S3) — отдельный future epic для multi-node cluster, не для cluster.142
- **Dictionary relationships v2** — combine lineage / dependency graph + cross-dictionary FK cascade rules в один epic. Design DRAFT в `~/.gstack/projects/claude/zimin-main-design-dict-relationships-v2-20260507-230237.md` (3-5 нед CC, 4 phases, 4 D-questions для plan-eng-review). Pre-code assignment: Альтум consumer behavior на cascade events.
- Data quality rules engine
- Federation MDM-кластеров (master/replica)
- Schema registry (Apicurio/Confluent) для Kafka
---
## 2. Архитектура (без изменений с v0.9, кроме prod)
```
┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────┐
│ Альтум (real users) │ ──→│ ordinis.k8s.264 │ ──→│ Postgres │
│ │ │ (cluster.142 prod) │ │ (CNPG 1×
│ │ │ • read-api ×2 │ │ + WAL S3 │
│ │ │ • app ×2 (writer) │ │ + daily │
│ │ │ • projection-writer │ │ backups) │
│ │ │ • admin-ui ×1 │ │ │
│ │ │ • Kafka 1× + 3 topics │ │
│ │ │ • Redis 1× │ │ │
│ │ │ • Vault 1× │ │ │
│ │ └──────────────────────┘ └─────────────┘
└─────────────────────┘
┌──────────────────────┐
│ staging cluster.265 │
│ (DEV ENVIRONMENT) │
└──────────────────────┘
```
---
## 3. Operational notes
**Backup verify:** `barman-cloud-backup-list --endpoint-url http://geoportal-prod-minio.geoportal-production:9000 --cloud-provider aws-s3 s3://altum-backups/ordinis-postgres ordinis-postgres`
**Vault sealed после restart:** `kubectl exec -n vault vault-0 -- vault operator unseal <KEY>` (key хранится у operator'а в 1Password / GitLab vars).
**Rollback план:** DNS TTL 60s — при критичных issues Альтум переключает обратно на staging через config flag.
---
**Email:** zimin.an@nstart.space (НЕ gmail)