Files
fission-src/doc/thinking/2026-05-15-multitenant-architecture-summary.md
T
“Naeel” 5f0ab79f00 doc: add multitenant architecture summary (2026-05-15)
Единый сводный документ, описывающий полную архитектуру мультитенантного Fission.
Заменяет необходимость читать 50+ пошаговых thinking-файлов.

Содержит:
- Причина и концепция решения
- Архитектурная карта изменений (ASCII diagram)
- Таблица ключевых файлов с ролями
- Инженерные решения: Snapshot API, NamespaceManager event bus,
  EnsureNamespaceSA, buildermgr dedup bug, router nil guard
- RBAC: что и почему (включая нетривиальные events:create и LSAR)
- Backward compatibility guarantees
- Описание test scenario (Layer 1, PASS=5)
- Порядок деплоя нового форка
- Направления дальнейшей работы
2026-05-15 07:08:32 +04:00

11 KiB
Raw Blame History

Мультитенантный Fission: сводная архитектура и инженерная логика

Дата: 2026-05-15
Контекст: форк Fission v1.22.0, ветка feature/multitenant
Статус: реализовано, тесты зелёные


Зачем это было нужно

Стандартный Fission требует, чтобы все namespace-ы, в которых живут функции, были перечислены в переменной окружения FISSION_RESOURCE_NAMESPACES до старта процессов. Добавление нового namespace = rolling restart всех компонентов (executor, router, buildermgr). На сотнях тенантов — постоянный restart loop, каскадные сбои.

Наша задача: добавить новый tenant (namespace) без какого-либо рестарта.


Концепция решения

Единственный public contract для внешних систем — label на Namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: tenant-abc123
  labels:
    fission.io/managed: "true"

Никакого другого coupling с Fission internals не требуется.

После появления namespace с этим label Fission автоматически:

  1. Регистрирует namespace во всех компонентах (executor, router, buildermgr)
  2. Создаёт SA fission-fetcher и необходимый RBAC в namespace
  3. Подключает informer factory для CRD (Functions, Environments, HTTPTriggers и т.д.)
  4. Тенант может деплоить функции без задержки

Архитектурная карта изменений

Kubernetes Namespace API
        │
        │  watch: label fission.io/managed=true
        ▼
utils.RunManagedNamespaceWatcher(...)
        │
        │  (shared utility, один и тот же вызов из трёх компонентов)
        ▼
utils.NamespaceManager (interface)
        │
        ├─ Bootstrap(envNamespaces)    ← уже существующие NS при старте
        ├─ DispatchAdd(ns)             ← новый NS от watcher
        └─ DispatchRemove(ns)          ← NS удалён (track-only)
                │
                ▼
        NamespaceSubscriber.OnNamespaceAdd(...)
                │
    ┌───────────┼───────────┐
    ▼           ▼           ▼
executor     router      buildermgr
    │           │           │
registerNS  AddNS(ts)   envw+pkgw
    │                   .AddNamespace
    ├─ DefaultNSResolver().AddNamespace(ns)  ← thread-safe, dedup
    ├─ EnsureNamespaceSA(ctx, client, log, ns)  ← SA + RBAC provisioning
    └─ et.AddNamespace(ns, mgr)  ← для каждого executor type

Ключевые файлы

Файл Роль
pkg/utils/namespace.go NamespaceResolver — хранит список NS, thread-safe Snapshot/AddNamespace
pkg/utils/namespace_manager.go NamespaceManager — lifecycle, subscribers, event dispatch
pkg/utils/namespace_manager_model.go Типы: Record, Phase, Event, Source, Summary
pkg/utils/serviceaccount.go EnsureNamespaceSA — создаёт fission-fetcher SA/Role/RoleBinding
pkg/executor/multitenant/ns_watcher.go Executor NSWatcher + registerNamespace
pkg/executor/multitenant/namespace_subscriber.go Executor subscriber adapter
pkg/router/ns_watcher.go Router NSWatcher (1 строка, через shared utility)
pkg/router/namespace_subscriber.go Router subscriber adapter
pkg/buildermgr/ns_watcher.go BuilderMgr NSWatcher (1 строка, через shared utility)
pkg/buildermgr/namespace_subscriber.go BuilderMgr subscriber adapter
deploy/multitenant/rbac.yaml ClusterRole/ClusterRoleBinding для всех трёх компонентов

Инженерные решения и почему именно так

1. Snapshot API вместо прямого чтения map

Проблема: NamespaceResolver.FissionResourceNS — mutable map, защищённая mutex только на запись. Читатели в разных горутинах обращались к ней напрямую — data race.

Решение: Snapshot() []string — под read lock копирует map в sorted slice. Потребители итерируют по стабильной копии, безопасно даже при конкурентных AddNamespace.

Почему slice а не map: потребителям нужен обход, а не lookup. Sorted slice даёт детерминированный порядок — важно для тестов и для startup factory generation.

2. NamespaceManager как event bus

Проблема: каждый компонент реализовывал свой namespace watcher с нуля — дублирование кода watcher setup, event handlers, deduplication, logging.

Решение: единый utils.NamespaceManager + NamespaceSubscriber interface. Компонент реализует только OnNamespaceAdd/Remove/Resync, всё остальное — shared utility.

Это сократило router/ns_watcher.go до 5 строк, buildermgr/ns_watcher.go до 5 строк.

3. EnsureNamespaceSA — одно место, один вызов

Проблема: при динамической регистрации нового NS executor пытался создать pool pod, но SA fission-fetcher ещё не существовал → FailedCreate, pod не стартует.

Решение: в registerNamespace (executor) вызывается utils.EnsureNamespaceSA до вызова et.AddNamespace. SA всегда существует к моменту создания первого pod.

Важно: EnsureNamespaceSA — идемпотентная. Повторный вызов = safe no-op.

4. Buildermgr dedup bug

Проблема: buildermgr.StartNSWatcher при добавлении NS вызывал envw.AddNamespace и pkgw.AddNamespace. Но глобальный DefaultNSResolver().AddNamespace() вызывался внутри каждого watcher — dedup срабатывал после первого и блокировал второй.

Решение: buildermgr/namespace_subscriber.go вызывает DefaultNSResolver().AddNamespace() один раз в registerBuilderNamespace, а затем оба watcher добавляют NS независимо.

5. Router informer maps — guard против nil panic

Проблема: в router/httpTriggers.go AddNamespace мог вызываться до инициализации внутренних informer maps → nil pointer dereference.

Решение: добавлена explicit проверка nil перед операцией, с логом предупреждения.


RBAC — что и почему

deploy/multitenant/rbac.yaml содержит три ClusterRole:

fission-executor-ns-watcher

namespaces: list, watch

Нужен executor для регистрации Namespace informer. Без этого NSWatcher не стартует.

fission-router-ns-watcher

namespaces: list, watch

То же для router.

fission-executor-sa-provisioner

serviceaccounts: get, list, watch, create, update, patch
roles: get, list, watch, create, update, patch
rolebindings: get, list, watch, create, update, patch
events: create
authorization.k8s.io/localsubjectaccessreviews: create

Нетривиальные пункты:

  • events:create — Kubernetes запрещает создавать Role, выдающую право, которого нет у создающего субъекта. fission-fetcher получает events:create, значит executor тоже должен его иметь.

  • localsubjectaccessreviews:createsetupSAAndRoleBindings проверяет существующие права через LSAR перед созданием Role. Без этого — 403.


Что НЕ изменилось (backward compatibility)

  • FISSION_RESOURCE_NAMESPACES env var работает как раньше — namespace-ы из него регистрируются при старте через Bootstrap().
  • Существующие tenant namespace-ы, добавленные через env var, не нуждаются в label.
  • Поведение функций, HTTP-триггеров, builder — неизменно.
  • Helm chart стандартный; RBAC применяется отдельно: kubectl apply -f deploy/multitenant/rbac.yaml.

Тест-сценарий (Layer 1)

Проверяет сквозной сценарий без рестарта:

  1. Создать namespace l1-test-XXXXX
  2. Добавить label fission.io/managed=true
  3. Подождать, пока executor зарегистрирует NS (лог registered namespace)
  4. Создать Environment + Function + HTTPTrigger в namespace
  5. Вызвать функцию через router — ожидаемый ответ 200 OK

Все шаги (PASS=5 FAIL=0) проходят стабильно после полного RBAC fix.


Порядок деплоя нового форка

# 1. Применить RBAC (один раз на кластер)
kubectl apply -f deploy/multitenant/rbac.yaml

# 2. Деплоить fission-bundle с нашим образом
# (helm upgrade или kubectl apply с новым image tag)

# 3. Создать tenant
kubectl create namespace tenant-abc123
kubectl label namespace tenant-abc123 fission.io/managed=true

# 4. Готово. Можно деплоить функции в tenant-abc123.

Направления дальнейшей работы

  1. e2e тесты — автоматизированный test_layer1.sh-подобный тест в Go
  2. Helm chart — включить deploy/multitenant/rbac.yaml как условный template
  3. Мониторинг — expose namespace lifecycle events в metrics (Prometheus)
  4. Remove lifecycle — сейчас при удалении NS стратегия track-only;
    нужен dispatch-remove + cleanup informers
  5. Feature branch portability — при выходе Fission 1.23 сделать rebase
    этой ветки поверх нового upstream тега