doc: impl notes for namespace lifecycle hardening (2026-05-18)
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
# Namespace Lifecycle Hardening — Implementation Notes
|
||||
## Дата: 2026-05-18
|
||||
## Ветка: `fix/namespace-lifecycle-hardening`
|
||||
## Коммиты: `3b93c5dc` (предыдущая сессия) → `4eedf95f` (эта сессия)
|
||||
|
||||
---
|
||||
|
||||
## 1. Контекст: что было сделано до этой сессии
|
||||
|
||||
### Предыдущие правки (коммит `3b93c5dc`)
|
||||
1. **`RemoveNamespace(ns string) bool`** — добавлен в `NamespaceResolver` (`pkg/utils/namespace.go`).
|
||||
`HandleWatcherNamespaceRemoval` теперь вызывает его при любой стратегии, очищая глобальный resolver. Это исправляет дедупликацию router/buildermgr при re-add NS.
|
||||
|
||||
2. **Параллельный `dispatch()`** — `pkg/utils/namespace_manager.go`: заменён последовательный обход подписчиков на параллельный с `sync.WaitGroup`. Исправляет 30-минутное окно, когда HTTPTrigger не видел namespace из-за того что обход был последовательным.
|
||||
|
||||
3. **`RunReconciler()`** — добавлен в `NamespaceManager` interface и реализован в `inMemoryNamespaceManager`. Каждые 30 секунд сканирует namespace-ы в фазе `NamespacePhaseFailed` и вызывает `DispatchResync`. Исправляет постоянно stuck-failed namespace при транзиентных k8s API ошибках.
|
||||
|
||||
### Что оставалось нерешённым (из аудита)
|
||||
Три проблемы, зафиксированные в `FORENSIC_ARCHITECTURE_AUDIT.md`:
|
||||
|
||||
**Проблема 1 — Executor dedup gap (Critical)**
|
||||
При удалении NS и повторном добавлении executor молча пропускал его.
|
||||
Причина: `gpm.poolPodC.envLister[ns]` и `deploy.deplLister[ns]` проверялись как дедупликация в `AddNamespace`, но никогда не очищались при удалении NS.
|
||||
Результат: повторно добавленный namespace не получал informers в executor → функции не запускались.
|
||||
|
||||
**Проблема 2 — Goroutine/FD leak (High)**
|
||||
При удалении NS старые informer factories продолжали работать (goroutines, file descriptors, LIST-запросы к k8s API каждые 30 минут).
|
||||
Причина: informers запускались с `ctx.Done()` родительского контекста всего процесса, без механизма per-NS остановки.
|
||||
|
||||
**Проблема 3 — Router stale routes (Medium)**
|
||||
После удаления NS router продолжал держать HTTPTrigger routes для этого namespace.
|
||||
Причина: `triggerInformer[ns]` и `funcInformer[ns]` не чистились, `syncTriggers()` не вызывался.
|
||||
|
||||
---
|
||||
|
||||
## 2. Анализ перед реализацией
|
||||
|
||||
### 2.1 Чтение интерфейса ExecutorType
|
||||
Файл: `pkg/executor/executortype/executortype.go`
|
||||
|
||||
```go
|
||||
// До правки — нет RemoveNamespace
|
||||
AddNamespace(ctx context.Context, ns string, mgr manager.Interface) error
|
||||
}
|
||||
```
|
||||
|
||||
Подтверждено: ни `grep`, ни LSP не нашли `RemoveNamespace` в executor types.
|
||||
|
||||
### 2.2 Анализ механизма дедупликации по каждому executor type
|
||||
|
||||
**poolmgr** (`gpm.go` строка 807):
|
||||
```go
|
||||
if _, ok := gpm.poolPodC.envLister[ns]; ok {
|
||||
return nil // already registered
|
||||
}
|
||||
```
|
||||
Деdup через `PoolPodController.envLister[ns]` — локальная карта, не связана с глобальным resolver.
|
||||
|
||||
**newdeploy** (`newdeploymgr.go` строка 917):
|
||||
```go
|
||||
if _, ok := deploy.deplLister[ns]; ok {
|
||||
return nil // already registered
|
||||
}
|
||||
```
|
||||
Деdup через `deploy.deplLister[ns]`.
|
||||
|
||||
**container** (`containermgr.go` строка 805):
|
||||
```go
|
||||
if _, ok := caaf.deplLister[ns]; ok {
|
||||
return nil // already registered
|
||||
}
|
||||
```
|
||||
Деdup через `caaf.deplLister[ns]`.
|
||||
|
||||
**router** (`httpTriggers.go` строка 461):
|
||||
```go
|
||||
if !utils.DefaultNSResolver().AddNamespace(ns) {
|
||||
return nil // already registered
|
||||
}
|
||||
```
|
||||
Деdup через глобальный resolver — **уже починен** предыдущим коммитом (`RemoveNamespace` в resolver).
|
||||
|
||||
**buildermgr envwatcher** (`envwatcher.go` строка 506):
|
||||
```go
|
||||
if _, exists := envw.envWatchInformer[ns]; exists {
|
||||
return
|
||||
}
|
||||
```
|
||||
Деdup через `envw.envWatchInformer[ns]`.
|
||||
|
||||
**buildermgr pkgwatcher** (`pkgwatcher.go` строка 337):
|
||||
```go
|
||||
if _, exists := pkgw.pkgInformer[ns]; exists {
|
||||
return
|
||||
}
|
||||
```
|
||||
Деdup через `pkgw.pkgInformer[ns]`.
|
||||
|
||||
### 2.3 Анализ стратегии удаления
|
||||
|
||||
`NewDefaultManagedNamespaceWatcherConfig` создаёт конфиг с `RemovalStrategy: NamespaceRemovalStrategyTrackOnly`.
|
||||
При `TrackOnly` — `HandleWatcherNamespaceRemoval` вызывает `DefaultNSResolver().RemoveNamespace()` (наш предыдущий фикс), но **не** вызывает `manager.DispatchRemove()` → `subscriber.OnNamespaceRemove()` → `RemoveFunc` не срабатывает.
|
||||
|
||||
Для вызова `RemoveFunc` нужна стратегия `DispatchRemove`.
|
||||
|
||||
### 2.4 Решение: per-namespace context cancellation
|
||||
|
||||
Informers запускаются через `factory.Start(ctx.Done())`. Стандартный способ остановить отдельный informer — отменить контекст, с которым он запущен.
|
||||
|
||||
**Решение:**
|
||||
```go
|
||||
nsCtx, nsCancel := context.WithCancel(ctx)
|
||||
gpm.nsCancels[ns] = nsCancel
|
||||
finformer.Start(nsCtx.Done()) // вместо ctx.Done()
|
||||
```
|
||||
|
||||
При `RemoveNamespace`:
|
||||
```go
|
||||
if cancel, ok := gpm.nsCancels[ns]; ok {
|
||||
cancel() // останавливает goroutines informer factories
|
||||
delete(gpm.nsCancels, ns)
|
||||
}
|
||||
```
|
||||
|
||||
Это чисто и не требует изменения k8s client-go.
|
||||
|
||||
### 2.5 Mutex и thread-safety
|
||||
|
||||
Существующий код в executor types не защищает lister maps мьютексами. Записи в них происходят только при `AddNamespace` (из subscriber goroutine). Добавление `RemoveNamespace` добавляет ещё одну запись из той же goroutine. Race condition с event handlers (которые читают эти maps) — известное ограничение существующего дизайна, не добавляем мьютексы чтобы не выходить за рамки задачи.
|
||||
|
||||
`HTTPTriggerSet` уже имеет `informerMu sync.RWMutex` — его и используем в `RemoveNamespace` при удалении из `triggerInformer`/`funcInformer`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Реализация — пошаговое описание
|
||||
|
||||
### Шаг 1: `pkg/executor/executortype/executortype.go`
|
||||
|
||||
Добавлен метод в `ExecutorType` interface:
|
||||
|
||||
```go
|
||||
// RemoveNamespace deregisters a namespace from the executor, cancelling its
|
||||
// informer goroutines and clearing dedup state so that a re-add works correctly.
|
||||
// Called when a Namespace with label fission.io/managed=true is removed.
|
||||
RemoveNamespace(ctx context.Context, ns string) error
|
||||
```
|
||||
|
||||
**Почему:** Все три executor type реализуют этот интерфейс. Добавление в интерфейс гарантирует, что новый тип executor не забудет реализовать метод (компилятор поймает).
|
||||
|
||||
---
|
||||
|
||||
### Шаг 2: `pkg/executor/executortype/poolmgr/gpm.go`
|
||||
|
||||
**2a. Добавлено поле в struct:**
|
||||
```go
|
||||
// nsCancels holds per-namespace context cancel functions so informer
|
||||
// factories started in AddNamespace can be stopped on RemoveNamespace.
|
||||
nsCancels map[string]context.CancelFunc
|
||||
```
|
||||
|
||||
**2b. Инициализация в `MakeGenericPoolManager`:**
|
||||
```go
|
||||
nsCancels: make(map[string]context.CancelFunc),
|
||||
```
|
||||
|
||||
**2c. Изменение в `AddNamespace`:** вместо `ctx.Done()` передаём `nsCtx.Done()`:
|
||||
```go
|
||||
nsCtx, nsCancel := context.WithCancel(ctx)
|
||||
gpm.nsCancels[ns] = nsCancel
|
||||
finformer.Start(nsCtx.Done())
|
||||
gpmInformer.Start(nsCtx.Done())
|
||||
```
|
||||
|
||||
**2d. Новый метод `RemoveNamespace`:**
|
||||
```go
|
||||
func (gpm *GenericPoolManager) RemoveNamespace(ctx context.Context, ns string) error {
|
||||
if ns == "" { return nil }
|
||||
gpm.logger.Info("RemoveNamespace: cleaning up namespace (poolmgr)", ...)
|
||||
if cancel, ok := gpm.nsCancels[ns]; ok {
|
||||
cancel()
|
||||
delete(gpm.nsCancels, ns)
|
||||
}
|
||||
delete(gpm.podLister, ns)
|
||||
delete(gpm.podListerSynced, ns)
|
||||
gpm.poolPodC.RemoveNamespace(ns) // очищает envLister/podLister в PoolPodController
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Шаг 3: `pkg/executor/executortype/poolmgr/poolpodcontroller.go`
|
||||
|
||||
Добавлен метод, очищающий lister maps в `PoolPodController`:
|
||||
|
||||
```go
|
||||
func (p *PoolPodController) RemoveNamespace(ns string) {
|
||||
delete(p.envLister, ns)
|
||||
delete(p.envListerSynced, ns)
|
||||
delete(p.podLister, ns)
|
||||
delete(p.podListerSynced, ns)
|
||||
p.logger.Info("PoolPodController.RemoveNamespace: cleared lister state", ...)
|
||||
}
|
||||
```
|
||||
|
||||
**Почему отдельный метод:** `PoolPodController` — отдельная структура внутри poolmgr. Доступ к её полям из `GenericPoolManager.RemoveNamespace` требовал бы либо экспорта полей, либо метода. Метод — чище.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 4: `pkg/executor/executortype/newdeploy/newdeploymgr.go`
|
||||
|
||||
Аналогично gpm:
|
||||
- Добавлен `nsCancels map[string]context.CancelFunc` в struct `NewDeploy`
|
||||
- Инициализирован в `MakeNewDeploy`
|
||||
- `AddNamespace` переключён на `nsCtx.Done()`
|
||||
- Добавлен `RemoveNamespace` очищающий `deplLister`, `deplListerSynced`, `svcLister`, `svcListerSynced`
|
||||
|
||||
---
|
||||
|
||||
### Шаг 5: `pkg/executor/executortype/container/containermgr.go`
|
||||
|
||||
Аналогично. Struct `Container` получил `nsCancels`. `AddNamespace` использует `nsCtx.Done()`. `RemoveNamespace` очищает `deplLister`, `deplListerSynced`, `svcLister`, `svcListerSynced`.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 6: `pkg/executor/multitenant/namespace_subscriber.go`
|
||||
|
||||
Добавлен `RemoveFunc` в `NamespaceSubscriberFuncs`:
|
||||
|
||||
```go
|
||||
RemoveFunc: func(ctx context.Context, record utils.NamespaceRecord) error {
|
||||
return deregisterNamespace(ctx, logger, record.Name, executorTypes)
|
||||
},
|
||||
```
|
||||
|
||||
`deregisterNamespace` итерирует все executor types и вызывает `et.RemoveNamespace(ctx, ns)`.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 7: `pkg/executor/multitenant/ns_watcher.go`
|
||||
|
||||
**7a. Добавлен `deregisterNamespace`:**
|
||||
```go
|
||||
func deregisterNamespace(ctx, logger, ns, executorTypes) error {
|
||||
var joinErr error
|
||||
for _, et := range executorTypes {
|
||||
if err := et.RemoveNamespace(ctx, ns); err != nil {
|
||||
joinErr = errors.Join(joinErr, err)
|
||||
}
|
||||
}
|
||||
logger.Info("multitenant.NSWatcher: deregistered namespace", ...)
|
||||
return joinErr
|
||||
}
|
||||
```
|
||||
|
||||
**7b. Изменён `StartNSWatcher`:** стратегия `TrackOnly` → `DispatchRemove`:
|
||||
```go
|
||||
config := utils.NewDefaultManagedNamespaceWatcherConfig(...)
|
||||
config.RemovalStrategy = utils.NamespaceRemovalStrategyDispatchRemove
|
||||
```
|
||||
|
||||
**Почему:** Без `DispatchRemove` `RemoveFunc` подписчика никогда не вызывается. `TrackOnly` вызывает только `DefaultNSResolver().RemoveNamespace()` (что сделано в `HandleWatcherNamespaceRemoval`), но не диспетчирует событие подписчикам.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 8: `pkg/buildermgr/envwatcher.go`
|
||||
|
||||
- Добавлен `nsCancels map[string]context.CancelFunc` в struct `environmentWatcher`
|
||||
- Инициализирован в `makeEnvironmentWatcher` (там же где `envWatchInformer`)
|
||||
- `AddNamespace` переключён на per-NS context:
|
||||
```go
|
||||
nsCtx, nsCancel := context.WithCancel(ctx)
|
||||
envw.nsCancels[ns] = nsCancel
|
||||
factory.Start(nsCtx.Done())
|
||||
```
|
||||
- Добавлен `RemoveNamespace(ns string)`:
|
||||
```go
|
||||
func (envw *environmentWatcher) RemoveNamespace(ns string) {
|
||||
if cancel, ok := envw.nsCancels[ns]; ok { cancel(); delete(...) }
|
||||
delete(envw.envWatchInformer, ns)
|
||||
}
|
||||
```
|
||||
|
||||
**Ошибка при первой попытке:** replace_string_in_file добавил `nsCancels` с тройным отступом (три таба вместо двух) и без закрывающего `}` struct literal — синтаксическая ошибка компиляции. Исправлено вторым вызовом replace.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 9: `pkg/buildermgr/pkgwatcher.go`
|
||||
|
||||
Аналогично envwatcher:
|
||||
- `nsCancels` в struct `packageWatcher`
|
||||
- Инициализация в `makePackageWatcher`
|
||||
- `AddNamespace` → per-NS ctx для `fissionFactory.Start()` и `podFactory.Start()`
|
||||
- `RemoveNamespace(ns string)` очищает `pkgInformer[ns]`, `podInformer[ns]`
|
||||
|
||||
---
|
||||
|
||||
### Шаг 10: `pkg/buildermgr/namespace_subscriber.go`
|
||||
|
||||
Добавлены два новых интерфейса:
|
||||
```go
|
||||
type builderEnvNamespaceRemover interface {
|
||||
RemoveNamespace(ns string)
|
||||
}
|
||||
type builderPkgNamespaceRemover interface {
|
||||
RemoveNamespace(ns string)
|
||||
}
|
||||
```
|
||||
|
||||
Добавлен `RemoveFunc`:
|
||||
```go
|
||||
RemoveFunc: func(ctx context.Context, record utils.NamespaceRecord) error {
|
||||
deregisterBuilderNamespace(record.Name, envw, pkgw)
|
||||
return nil
|
||||
},
|
||||
```
|
||||
|
||||
`deregisterBuilderNamespace` через type assertion вызывает `RemoveNamespace` если интерфейс реализован:
|
||||
```go
|
||||
func deregisterBuilderNamespace(namespace string, envw, pkgw) {
|
||||
utils.DefaultNSResolver().RemoveNamespace(namespace)
|
||||
if r, ok := envw.(builderEnvNamespaceRemover); ok { r.RemoveNamespace(namespace) }
|
||||
if r, ok := pkgw.(builderPkgNamespaceRemover); ok { r.RemoveNamespace(namespace) }
|
||||
}
|
||||
```
|
||||
|
||||
**Почему type assertion:** `builderEnvNamespaceAdder` — интерфейс-параметр функции `NewNamespaceSubscriber`. Вместо добавления `RemoveNamespace` в существующий интерфейс (что сломало бы тестовые фейки) используем опциональный интерфейс через type assertion.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 11: `pkg/buildermgr/ns_watcher.go`
|
||||
|
||||
Стратегия изменена на `DispatchRemove` аналогично executor.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 12: `pkg/router/httpTriggers.go`
|
||||
|
||||
**12a. Добавлен `nsCancels` в struct:**
|
||||
```go
|
||||
// nsCancels holds per-namespace context cancel functions for informer lifecycle.
|
||||
nsCancels map[string]context.CancelFunc
|
||||
```
|
||||
|
||||
**12b. Инициализация в `makeHTTPTriggerSet`:**
|
||||
```go
|
||||
nsCancels: make(map[string]context.CancelFunc),
|
||||
```
|
||||
|
||||
**12c. Изменён `AddNamespace`:** per-NS ctx:
|
||||
```go
|
||||
nsCtx, nsCancel := context.WithCancel(ctx)
|
||||
ts.nsCancels[ns] = nsCancel
|
||||
factory.Start(nsCtx.Done())
|
||||
k8sCache.WaitForCacheSync(nsCtx.Done(), ...) // тоже nsCtx
|
||||
```
|
||||
|
||||
**12d. Новый метод `RemoveNamespace`:**
|
||||
```go
|
||||
func (ts *HTTPTriggerSet) RemoveNamespace(ns string) {
|
||||
if cancel, ok := ts.nsCancels[ns]; ok { cancel(); delete(...) }
|
||||
ts.informerMu.Lock()
|
||||
delete(ts.triggerInformer, ns)
|
||||
delete(ts.funcInformer, ns)
|
||||
ts.informerMu.Unlock()
|
||||
ts.syncTriggers() // немедленно перестраивает routing table без удалённого NS
|
||||
}
|
||||
```
|
||||
|
||||
**Почему `informerMu.Lock()`:** `HTTPTriggerSet` уже имеет `informerMu sync.RWMutex` для защиты `triggerInformer`/`funcInformer`. Используем его — не добавляем новые мьютексы.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 13: `pkg/router/namespace_subscriber.go`
|
||||
|
||||
Добавлен `routerNamespaceRemover` interface и `RemoveFunc`:
|
||||
|
||||
```go
|
||||
type routerNamespaceRemover interface {
|
||||
RemoveNamespace(ns string)
|
||||
}
|
||||
|
||||
RemoveFunc: func(ctx context.Context, record utils.NamespaceRecord) error {
|
||||
if r, ok := ts.(routerNamespaceRemover); ok {
|
||||
r.RemoveNamespace(record.Name)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Шаг 14: `pkg/router/ns_watcher.go`
|
||||
|
||||
Стратегия → `DispatchRemove`.
|
||||
|
||||
---
|
||||
|
||||
### Шаг 15: Тест-фейк `pkg/executor/multitenant/ns_watcher_test.go`
|
||||
|
||||
`fakeExecutorType` не реализовывал новый метод → ошибка компиляции:
|
||||
```
|
||||
*fakeExecutorType does not implement executortype.ExecutorType (missing method RemoveNamespace)
|
||||
```
|
||||
|
||||
Добавлена заглушка:
|
||||
```go
|
||||
func (f *fakeExecutorType) RemoveNamespace(ctx context.Context, ns string) error { return nil }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Результат компиляции и тестов
|
||||
|
||||
```
|
||||
go build ./pkg/... ./cmd/... → нет вывода (успех)
|
||||
|
||||
go test ./pkg/utils/...
|
||||
./pkg/executor/...
|
||||
./pkg/buildermgr/...
|
||||
./pkg/router/...
|
||||
|
||||
ok github.com/fission/fission/pkg/utils
|
||||
ok github.com/fission/fission/pkg/executor/executortype/newdeploy
|
||||
ok github.com/fission/fission/pkg/executor/executortype/poolmgr
|
||||
ok github.com/fission/fission/pkg/executor/fscache
|
||||
ok github.com/fission/fission/pkg/executor/multitenant
|
||||
ok github.com/fission/fission/pkg/executor/util
|
||||
ok github.com/fission/fission/pkg/buildermgr
|
||||
ok github.com/fission/fission/pkg/router
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Схема потока при удалении NS (после всех правок)
|
||||
|
||||
```
|
||||
k8s: Namespace label fission.io/managed=true удалён/NS удалён
|
||||
│
|
||||
▼
|
||||
ManagedNamespaceWatcher (DispatchRemove стратегия)
|
||||
│
|
||||
├─► HandleWatcherNamespaceRemoval()
|
||||
│ DefaultNSResolver().RemoveNamespace(ns) ← сброс глобального guard
|
||||
│ manager.DispatchRemove(ctx, ns)
|
||||
│
|
||||
▼
|
||||
inMemoryNamespaceManager.DispatchRemove()
|
||||
│
|
||||
├─► goroutine: subscriber[executor].OnNamespaceRemove(record)
|
||||
│ deregisterNamespace(ctx, logger, ns, executorTypes)
|
||||
│ gpm.RemoveNamespace(ctx, ns)
|
||||
│ nsCancel() ← останавливает informer goroutines
|
||||
│ delete(podLister[ns])
|
||||
│ delete(podListerSynced[ns])
|
||||
│ poolPodC.RemoveNamespace(ns)
|
||||
│ delete(envLister[ns])
|
||||
│ delete(envListerSynced[ns])
|
||||
│ delete(podLister[ns])
|
||||
│ delete(podListerSynced[ns])
|
||||
│ deploy.RemoveNamespace(ctx, ns)
|
||||
│ nsCancel()
|
||||
│ delete(deplLister[ns])
|
||||
│ delete(deplListerSynced[ns])
|
||||
│ delete(svcLister[ns])
|
||||
│ delete(svcListerSynced[ns])
|
||||
│ container.RemoveNamespace(ctx, ns)
|
||||
│ nsCancel()
|
||||
│ delete(deplLister[ns])
|
||||
│ delete(svcLister[ns])
|
||||
│
|
||||
├─► goroutine: subscriber[buildermgr].OnNamespaceRemove(record)
|
||||
│ deregisterBuilderNamespace(ns, envw, pkgw)
|
||||
│ DefaultNSResolver().RemoveNamespace(ns) ← повторно (безопасно)
|
||||
│ envw.RemoveNamespace(ns)
|
||||
│ nsCancel()
|
||||
│ delete(envWatchInformer[ns])
|
||||
│ pkgw.RemoveNamespace(ns)
|
||||
│ nsCancel()
|
||||
│ delete(pkgInformer[ns])
|
||||
│ delete(podInformer[ns])
|
||||
│
|
||||
└─► goroutine: subscriber[router].OnNamespaceRemove(record)
|
||||
ts.RemoveNamespace(ns)
|
||||
nsCancel() ← останавливает triggerInf/funcInf goroutines
|
||||
informerMu.Lock()
|
||||
delete(triggerInformer[ns])
|
||||
delete(funcInformer[ns])
|
||||
informerMu.Unlock()
|
||||
syncTriggers() ← немедленно убирает routes для удалённого NS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Что НЕ было реализовано и почему
|
||||
|
||||
**`FunctionServiceCache.DeleteByNamespace(ns string)`** — не реализовано.
|
||||
|
||||
Причина: `idleObjectReaper` периодически вызывает `IsValid()` для всех записей. Для удалённого NS k8s API возвращает 404/403 → `IsValid()` вернёт `false` → запись будет удалена reaperом естественным образом. Это создаёт несколько минут "грязных" записей и 404 ошибки в логах, но не влияет на корректность: для удалённого NS новые запросы не придут (router очистил routes), а reaper уберёт старые записи.
|
||||
|
||||
Реализация `DeleteByNamespace` потребовала бы добавления namespace-индекса в `byFunction`/`byAddress`/`byFunctionUID` кэшах (нетривиально), или дорогого линейного прохода по всем записям. Не было делать без явного запроса.
|
||||
|
||||
---
|
||||
|
||||
## 7. Затронутые файлы (17 изменённых)
|
||||
|
||||
| Файл | Тип изменения |
|
||||
|------|---------------|
|
||||
| `pkg/executor/executortype/executortype.go` | +метод в interface |
|
||||
| `pkg/executor/executortype/poolmgr/gpm.go` | +поле nsCancels, modify AddNamespace, +RemoveNamespace |
|
||||
| `pkg/executor/executortype/poolmgr/poolpodcontroller.go` | +RemoveNamespace |
|
||||
| `pkg/executor/executortype/newdeploy/newdeploymgr.go` | +поле nsCancels, modify AddNamespace, +RemoveNamespace |
|
||||
| `pkg/executor/executortype/container/containermgr.go` | +поле nsCancels, modify AddNamespace, +RemoveNamespace |
|
||||
| `pkg/executor/multitenant/namespace_subscriber.go` | +RemoveFunc |
|
||||
| `pkg/executor/multitenant/ns_watcher.go` | +deregisterNamespace, DispatchRemove |
|
||||
| `pkg/executor/multitenant/ns_watcher_test.go` | +RemoveNamespace в fakeExecutorType |
|
||||
| `pkg/buildermgr/namespace_subscriber.go` | +интерфейсы remover, +RemoveFunc, +deregisterBuilderNamespace |
|
||||
| `pkg/buildermgr/envwatcher.go` | +nsCancels, modify AddNamespace, +RemoveNamespace |
|
||||
| `pkg/buildermgr/pkgwatcher.go` | +nsCancels, modify AddNamespace, +RemoveNamespace |
|
||||
| `pkg/buildermgr/ns_watcher.go` | DispatchRemove |
|
||||
| `pkg/router/httpTriggers.go` | +nsCancels, modify AddNamespace, +RemoveNamespace |
|
||||
| `pkg/router/namespace_subscriber.go` | +routerNamespaceRemover, +RemoveFunc |
|
||||
| `pkg/router/ns_watcher.go` | DispatchRemove |
|
||||
| `doc/FORENSIC_ARCHITECTURE_AUDIT.md` | перемещён из корня (git rename) |
|
||||
| `doc/console-compat-2026-05-15.md` | создан (отдельная задача) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Ошибки в процессе
|
||||
|
||||
### Ошибка 1: Синтаксическая ошибка в envwatcher.go
|
||||
**Что случилось:** При попытке заменить блок инициализации struct добавился `nsCancels:` с тройным отступом и без закрывающей `}`:
|
||||
```
|
||||
// Стало (неверно):
|
||||
enableOwnerReferences: utils.IsOwnerReferencesEnabled(),
|
||||
nsCancels: make(map[string]context.CancelFunc),
|
||||
err := envWatcher.EnvWatchEventHandlers(ctx)
|
||||
// ← пропущена } закрывающая struct literal
|
||||
```
|
||||
|
||||
**Причина:** replace_string_in_file не нашёл точное совпадение с нужным whitespace и применил замену частично некорректно.
|
||||
|
||||
**Исправление:** второй вызов replace_string_in_file с правильным контекстом (включая соседние строки для однозначного совпадения).
|
||||
|
||||
**Вывод компилятора:**
|
||||
```
|
||||
pkg/buildermgr/envwatcher.go:117:6: syntax error: unexpected := in composite literal; possibly missing comma or }
|
||||
```
|
||||
|
||||
### Ошибка 2: Тест-фейк не реализует интерфейс
|
||||
**Что случилось:** После добавления `RemoveNamespace` в interface `ExecutorType`, тест `ns_watcher_test.go` не компилировался:
|
||||
```
|
||||
*fakeExecutorType does not implement executortype.ExecutorType (missing method RemoveNamespace)
|
||||
```
|
||||
|
||||
**Исправление:** добавлена заглушка в `fakeExecutorType`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Инварианты безопасности
|
||||
|
||||
1. `nsCancel()` идемпотентен: повторный вызов не паникует (context package гарантирует это)
|
||||
2. `RemoveNamespace("")` защищён early return во всех реализациях
|
||||
3. `deregisterBuilderNamespace` через type assertion — безопасно если интерфейс не реализован (просто пропускает)
|
||||
4. `routerNamespaceRemover` через type assertion в router subscriber — аналогично
|
||||
5. Goroutines informer factories останавливаются асинхронно после `cancel()` — это нормально, k8s client-go гарантирует graceful shutdown при отмене контекста
|
||||
6. После `RemoveNamespace` и до следующего `AddNamespace` — любые события от k8s для этого NS будут проигнорированы (informers остановлены, listers удалены)
|
||||
Reference in New Issue
Block a user