From f5b57173f50d876e1a752334b6a5766769622030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 18 May 2026 11:32:13 +0400 Subject: [PATCH] doc(audit): mark stuck-failed as CLOSED after 919e8439 (RunReconciler + error propagation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated FORENSIC_ARCHITECTURE_AUDIT.md to reflect the fix from commit 919e8439: Status table: - 'Stuck-failed namespace без auto-recovery': ❌ ОТКРЫТ → ✅ ЗАКРЫТ (919e8439) - 'No Explicit State Machine': ❌ ОТКРЫТ → ⚠️ СМЯГЧЕНО (stuck-failed закрыт; явная state machine остаётся в backlog) Sections updated: - §1.4: полное описание что было (void-функции, мёртвый reconciler) и что сделано (error propagation chain, end-to-end flow retry) - Fragile Components: Stuck Failed Phase — вычеркнуто как закрытое - Risky Decisions table: No Explicit State Machine → частично закрыто - Lifecycle Management: добавлено что auto-recovery работает через RunReconciler - Operational Burden: убрано упоминание stuck-failed как активной проблемы - Maintainability/Production-Grade: обновлены под текущее состояние - §2 Stuck Failed Accumulation: ✅ ЗАКРЫТ - §3 P1 Reconcile-очередь: ✅ ЗАКРЫТ - §5 Sharded mutex вердикт: убрано упоминание stuck-failed --- doc/FORENSIC_ARCHITECTURE_AUDIT.md | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/doc/FORENSIC_ARCHITECTURE_AUDIT.md b/doc/FORENSIC_ARCHITECTURE_AUDIT.md index 77b7cd6a..c484929a 100644 --- a/doc/FORENSIC_ARCHITECTURE_AUDIT.md +++ b/doc/FORENSIC_ARCHITECTURE_AUDIT.md @@ -14,9 +14,9 @@ | Executor dedup dirty state при re-add NS | ✅ ЗАКРЫТ | `4eedf95f` | | Синхронный subscriber dispatch (onboarding latency) | ✅ ЗАКРЫТ | предыдущая сессия | | `DefaultNSResolver` только append (нет RemoveNamespace) | ✅ ЗАКРЫТ | предыдущая сессия | -| Stuck-failed namespace без auto-recovery | ❌ ОТКРЫТ | — | +| Stuck-failed namespace без auto-recovery | ✅ ЗАКРЫТ | `919e8439` | | AdoptExistingResources race при rolling update | ❌ ОТКРЫТ | — | -| No explicit state machine (implicit phase transitions) | ❌ ОТКРЫТ | — | +| No Explicit State Machine (implicit phase transitions) | ⚠️ СМЯГЧЕНО | `919e8439` | | Sharded mutex (bottleneck при >500 concurrent tenant) | ⏳ BACKLOG | не актуально при текущей нагрузке | --- @@ -67,7 +67,7 @@ - **RBAC/SA Drift**: Неконсистентность между созданием и удалением SA/ролей при сбое в `deregisterNamespace`. - **Cache Invalidation**: `FunctionServiceCache` не очищается при `RemoveNamespace` — расчёт на `idleObjectReaper`. При высоком churn rate может накапливать stale записи быстрее, чем reaper убирает. - **Adoption Race**: `AdoptExistingResources` vs `namespace_subscriber` — активная проблема (§1.3). -- **Stuck Failed Phase**: Namespace в `failed` не восстанавливается без рестарта — активная проблема (§1.4). +- **Stuck Failed Phase**: ~~Namespace в `failed` не восстанавливается без рестарта~~ — закрыто: `RunReconciler` + полная цепочка error propagation (§1.4). --- @@ -96,7 +96,7 @@ | Informer Lifecycle Management | ✅ Hardened | per-NS context cancel + RemoveNamespace во всех компонентах | | Centralized Mutex | ⚠️ Приемлемо | sharding в backlog, не актуально до >500 NS | | Manual Adoption | ❌ Активная проблема | race при rolling update | -| No Explicit State Machine | ❌ Активная проблема | stuck-failed без retry | +| No Explicit State Machine | ✅ Частично закрыто | stuck-failed закрыт (`919e8439`); явная state machine в backlog | | Eventual Consistency | ⚠️ Смягчено | параллельный dispatch уменьшает окно, но не устраняет | --- @@ -105,10 +105,10 @@ - **Multi-Tenancy**: Label-based discovery, каждый tenant — отдельный namespace, изоляция на уровне k8s. - **Isolation Model**: Namespace-level isolation, per-NS SA/RBAC, per-NS informer factory. -- **Lifecycle Management**: Фазы (discovered → registering → active → deregistering → removed / failed) реализованы, но без явной state machine и без auto-recovery из failed. +- **Lifecycle Management**: Фазы (discovered → registering → active → deregistering → removed / failed) реализованы. Auto-recovery из failed работает через `RunReconciler`. Явная state machine в backlog. - **State Handling**: Глобальный `DefaultNSResolver` + локальные lister-ы. После `RemoveNamespace` — оба синхронизованы. После re-add — оба корректно инициализируются заново. - **Reconciliation Logic**: Каждый компонент через subscribe. Отсутствует reconcile-очередь для failed state. -- **Operational Burden**: Средний — goroutine leak устранён, stale informer устранён. Требуется мониторинг: stuck-failed накопление, orphaned SA/RBAC при неудачном deregister. +- **Operational Burden**: Средний — goroutine leak устранён, stale informer устранён, stuck-failed закрыт. Требуется мониторинг: orphaned SA/RBAC при неудачном deregister. --- @@ -116,8 +116,8 @@ - **Maturity**: Архитектурно зрелый, хорошо документированный, с явным reasoning и поэтапным внедрением. - **Complexity**: Высокая в синхронизации и lifecycle. Снижена за счёт формализации `RemoveNamespace` контракта. -- **Maintainability**: Среднесрочная — без явной state machine и auto-recovery возможны stuck state при API нестабильности. -- **Production-Grade**: Близко — informer lifecycle корректен, dispatch параллелен, cleanup симметричен. Основной gap: stuck-failed и AdoptExistingResources race. +- **Maintainability**: Среднесрочная — без явной state machine сложность будет расти. Auto-recovery из failed работает. +- **Production-Grade**: Близко — informer lifecycle корректен, dispatch параллелен, cleanup симметричен, stuck-failed закрыт. Основной gap: AdoptExistingResources race. --- @@ -170,20 +170,29 @@ --- -### 1.4 No Explicit State Machine — ❌ АКТИВНАЯ ПРОБЛЕМА +### 1.4 No Explicit State Machine — ✅ ЗАКРЫТ (коммит `919e8439`) **Сценарий: stuck в `failed` без auto-recovery** -1. `registerNamespace()` вызывает `EnsureNamespaceSA()` — Kubernetes API возвращает 503. -2. Часть помечается `NamespacePartStateFailed` → фаза NS → `NamespacePhaseFailed`. -3. **Нет reconcile-цикла**: фаза остаётся `failed` до рестарта процесса. -4. Router уже создал informer-ы (параллельный dispatch), executor — нет (SA не создан). Dirty state: router видит NS активным, executor — нет. Вызовы → 503. +**Что было:** `EnsureNamespaceSA` и `registerNamespace` были void-функциями — ошибки только логировались, до `MarkPartFailed` не доходили. Executor subscriber всегда возвращал nil → namespace никогда не попадал в `NamespacePhaseFailed` → `RunReconciler` для executor был мёртвым кодом. -**Накопление при churn:** 10 ns/час × 1% API error rate = ~2.4 failed NS/сутки. За 30 дней = ~72 "мёртвых" записи. `Snapshot()` возвращает их в `idleObjectReaper` → лишние LIST запросы к k8s. +**Что сделано:** +- `setupSAAndRoleBindings` → возвращает `error` +- `EnsureNamespaceSA` → возвращает `error`, пробрасывает +- `registerNamespace` → возвращает `error` (SA + executorTypes) с `fmt.Errorf` wrapping +- Executor `AddFunc`/`ResyncFunc` → пробрасывают ошибку вместо `return nil` +- `RunReconciler` → принимает `*zap.Logger`, логирует каждый retry и исход -**Предложение (не реализовано):** reconcile-очередь в `inMemoryNamespaceManager` — см. FORENSIC_ARCHITECTURE_AUDIT_LEGACY_2026-05.md §3 для кода. +**End-to-end flow:** +1. `EnsureNamespaceSA` fails (k8s 503) → `registerNamespace` returns error +2. Executor AddFunc returns error → `dispatch()` → `MarkPartFailed("executor")` +3. `deriveNamespacePhase` → `NamespacePhaseFailed` +4. `RunReconciler` tick (30s) находит namespace → `DispatchResync` → retry +5. Если API восстановился: `MarkPartActive` → `NamespacePhaseActive` → лог `resync succeeded` -**Статус:** не исправлено. +**Накопление при churn:** ликвидировано — failed NS автоматически выходят из этой фазы при восстановлении API. + +**Ограничение:** нет max-retries. Namespace, у которого SA создать принципиально невозможно (например, удалённый k8s namespace), будет ретраиться вечно. Приемлемо на текущем масштабе. --- @@ -229,11 +238,9 @@ ## 3. Рекомендации (приоритизированные) -### P1 — Reconcile-очередь для failed NS +### P1 — Reconcile-очередь для failed NS — ✅ ЗАКРЫТ (`919e8439`) -Минимальное изменение без breaking interface change. Полный код — в `FORENSIC_ARCHITECTURE_AUDIT_LEGACY_2026-05.md §3`. Суть: горутина читает из `reconcileQueue chan`, делает exponential backoff retry для failed NS. Max 5 попыток. - -**Влияние:** устраняет stuck-failed накопление, dirty state между router и executor. +Error propagation исправлена во всей цепочке: `setupSAAndRoleBindings` → `EnsureNamespaceSA` → `registerNamespace` → executor subscriber. `RunReconciler` логирует retry и исход. ### P2 — AdoptExistingResources после BootstrapAndDispatch @@ -266,4 +273,4 @@ **Технически реализуемо** без breaking interface change. Полный код — в `FORENSIC_ARCHITECTURE_AUDIT_LEGACY_2026-05.md §5`. -**Вердикт:** не оправдано при текущей нагрузке. Реальные bottleneck-и — AdoptExistingResources race и stuck-failed, не mutex. Sharded mutex — в backlog, актуально при >500 concurrent tenant с >1 onboarding/sec. +**Вердикт:** не оправдано при текущей нагрузке. Реальный bottleneck — AdoptExistingResources race, не mutex. Sharded mutex — в backlog, актуально при >500 concurrent tenant с >1 onboarding/sec.