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

12 KiB
Raw Blame History

Состояние разработки НСИ (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 (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)