# Анализ репозитория fission-console Составлен: 2026-05-21. Основан только на чтении исходного кода + истории git. --- ## Что это **Web UI + API proxy** для управления Fission (Kubernetes-native serverless). Написан на Go. Работает внутри K8s-кластера. Пользователи аутентифицируются через Deck API (облачный SSO). ## Архитектура — поток данных ``` Browser (SPA) → Console API → K8s API (CRD — dynamic client) → Fission Router (invoke proxy — /fission-function) → storagesvc (архивы → S3 ngcloud msk-1, бакет sless-functions) → Deck API (auth — prod/dev/test) → PostgreSQL (billing invocations) → Grafana (дашборды — по одному Org на namespace) ``` ### Пакеты | Пакет | Роль | |---|---| | `cmd/server/` | Точка входа: парсинг env vars, создание зависимостей, запуск HTTP | | `internal/api/` | **Ядро**: все HTTP-хендлеры (29 файлов) | | `internal/auth/` | Аутентификация: Deck API + Demo (test fallback) | | `internal/cloud/` | Namespace lifecycle: квата, NetworkPolicy, одноразовый создаватель | | `internal/fission/` | Константы GVR + SetupFissionNamespace + EnsureEnvironment | | `internal/model/` | Общие типы: CreateFunctionRequest, LangEnvMap | | `internal/runtime/` | Упаковка кода: zip, ESM-обёртка NodeJS, python/phh/ruby/go | | `internal/billing/` | Статистика: PostgreSQL (NoopStore если BILLING_DSN пуст) | | `internal/stats/` | Grafana: Organization/dashboard per namespace | | `ui/` | Embed SPA (index.html), отдельный /cron/ UI | --- ## Фичи — детальный разбор ### 1. Аутентификация **Файлы:** `auth.go`, `middleware.go`, `handlers.go` (строка handleAuth) - `POST /console/api/auth` — тело `{token, env}`. Валидация через Deck API. Возвращает `{ok, env, namespace, email}`. - **Middleware** — каждый приватный роут → `Authenticator.Authenticate(token, env)`. Без токена → 401. - **Namespace** = `"fission-"` + hex(SHA256(sub)[:8]). Для test-пользователей: `"fission-test-"` + hex(SHA256(sub)[:8]). - **DeckAuthenticator** — HTTP-запрос к `deck-api-{env}.ngcloud.ru/api/v1/user`. Кеш в `sync.Map` (ключ = `env:token`, значение = expiry time). Таймаут 5s. - **DemoAuthenticator** — для тестовых стендов: sub = email = token, без проверки. - **MultiAuthenticator** — последовательно пробует JWT → Demo. ### 2. Управление Namespace **Файлы:** `cloud/tenant.go`, `cloud/quota.go`, `cloud/network.go` - **EnsureUserNS** — единая точка входа. Singleflight (параллельные запросы для одного NS ждут на chan). Семафор: не более 3 одновременных созданий. In-memory кэш уже созданных NS. - **Что создаётся при первом запросе пользователя:** - Namespace с лейблами `managed-by=fission-console`, `fission.io/managed=true` - ServiceAccounts: `fission-fetcher`, `fission-builder` - RoleBindings: для executor, router, buildermgr, kubewatcher, timer, fetcher, builder - ResourceQuota (из env vars, по умолчанию: CPU req 1/lim 12, mem 2Gi/6Gi, pods 30, functions 20) - LimitRange (дефолтный CPU 500m, mem 256Mi на контейнер) - NetworkPolicy `deny-cross-tenant` — ingress только из своего NS, fission NS, kube-system - **ExpiryReaper** — горутина в main, раз в 5 минут (REAPER_INTERVAL). Удаляет функции с аннотацией `expires_at` в прошлом. - **NSReconciler** — сверяет FISSION_RESOURCE_NAMESPACES. ### 3. CRUD функций **Файлы:** `function_code.go`, `function_archive.go`, `function_crud.go`, `function_clone.go`, `storagesvc.go` #### Создание из кода (POST /console/api/functions, JSON) - Тело: `{name, language, code, deps?, entrypoint?, route?, methods?, timeout?, ttl?}` - **Валидация:** имя `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`, ≤57 символов, код ≤1MB. - **Языки:** python, nodejs, php, ruby, go. - **Порядок:** Package CRD → Function CRD → HTTPTrigger CRD. При ошибке откат. - **Типы пакетов:** - Python: zip с кодом + опционально requirements.txt (если deps не пуст) - NodeJS: ESM-wrapped zip (package.json + main.js с `__invoke` dispatcher) - PHP: zip с `main.php` + опционально composer.json - Ruby: zip с `handler.rb` + опционально Gemfile - Go: source package (builder компилирует .so плагин) - **Загрузка:** через storagesvc → S3 (ngcloud msk-1, бакет `sless-functions`). Fallback на type:literal если storagesvcURL пуст. - **TTL:** парсит `1h`, `30m`, `7d`, `24h`. Ставит аннотацию `expires_at` на Function CRD в `metadata.annotations`. - **Env vars:** сохраняются в аннотации `fission-console/env-vars` (JSON). #### Создание из архива (POST /console/api/functions, multipart/form-data) - Поле формы: `archive` (zip-файл). - Проверка magic bytes: `PK` (0x50 0x4B). - Размер ≤32MB. - Загружается в storagesvc как есть, без модификации. - Аннотация `fission-console/source-type: "archive"`. - Поля формы: `name, language/environment, entrypoint, route, methods, timeout, ttl`. #### GET функции (GET /console/api/functions/:name) - Возвращает: name, namespace, environment, package, entrypoint, timeout, created_at, updated_at, code, deps, source_type, archive_filename, route, methods, env_vars, raw CRD. - **Извлечение кода:** пробует source.literal → deployment.literal → URL (через storagesvc download для type:url). Для NodeJS распознаёт и анрапает ESM-обёртку (`decodeArchiveBytesToSource` в `package.go`). #### Обновление кода (PUT /console/api/functions/:name/code) - Тело: `{code, deps?, timeout?}` - Создаёт **новый Package** (новое имя = cache miss в executor). Старый удаляется. - Та же сборка архива что и при создании (buildDeployArchive). #### Обновление архива (PUT /console/api/functions/:name/archive) - multipart/form-data, поле `archive`. Загружается в storagesvc, новый Package. #### Клонирование (POST /console/api/functions/:name/clone) - Тело: `{new_name, route?}` - Для type:literal — копирует base64 literal в новый Package. - Для type:url — скачивает архив из storagesvc → заливает новый → новый Package. - Создаёт новый Function + HTTPTrigger. Для Go копирует source-пакет. - При ошибке откатывает созданные объекты. #### DELETE (DELETE /console/api/functions/:name) - Каскадное: удаляет HTTPTrigger → Package CRD → удаляет архив из storagesvc (DELETE /v1/archive?id=... через storagesvc). - Проверяет что функция реально удалена из K8s. #### Timeout, Logs, EnvVars - `PUT /functions/:name/timeout` — патчит только `spec.functionTimeout` (без замены кода/архива). - `GET /functions/:name/logs` — читает логи пода функции через K8s API (rest client, `pod/log`). - `GET /functions/:name/envvars` / `PUT /functions/:name/envvars` — аннотация `fission-console/env-vars`. ### 4. Invoke (вызов функций) **Файл:** `function_invoke.go` - **`POST /console/api/functions/:name/invoke`** — для UI. Проксирует через Fission Router. URL: `routerURL/fission-function//`. Ответ: `{status, body, headers, cold_start, response_raw, pod, duration_ms}`. Записывает в billing. - **`/fn/`** — публичный gateway. Строит URL по маршруту, проксирует тело/заголовки (кроме служебных). Таймаут из `spec.functionTimeout`. - **`/fission-function//`** — внутренний gateway для cron/timer/webhook. - JWT-токен router: ServiceAccount token из `/var/run/secrets/...` (или SA_TOKEN_PATH), кешируется на 12ч в `sync.Mutex`. ### 5. Триггеры **Файлы:** `timetriggers.go`, `mqtriggers.go`, `kwtriggers.go` - **HTTP Triggers** — создаются автоматически при создании функции. Чтение списка: GET `/console/api/httptriggers`. - **Time Triggers** — CRUD через `/console/api/timetriggers`. Поля: `name, functionName, cron, method, subpath`. Создаются как `fission.io/v1 TimeTrigger` CRD. - **MQ Triggers** — `/console/api/mqtriggers`. Поля: `name, functionName, queue, sqsEndpoint, accessKey, secretKey`. Создаёт Deployment (sqs-consumer) + Secret с credentials в NS пользователя. sqs-consumer поллит SQS → вызывает функцию через JWT-authenticated router. - **Kubernetes Watch Triggers** — `/console/api/kwtriggers`. Поля: `name, functionName, resourceType, namespace, labelSelector`. ### 6. AI/Lint фичи **Файлы:** `ai_check.go`, `lint_archive.go`, `explain_archive.go`, `ai_prompts.go` - **`POST /ai/check`** — синтаксическая проверка кода. Реальные линтеры (не LLM): `node --check`, `python3 -m py_compile`, `ruby -c`, `php -l`, Go через `go/parser` прямо в процессе. - **`POST /ai/lint-archive`** — принимает zip, линтит каждый файл с известным расширением. Защита от zip bomb: max 100KB архив, max 100KB распакованных. Опционально: проверка entrypoint и соответствия языка. - **`POST /ai/explain-archive`** — извлекает код из архива (max 20KB), отправляет в LLM через OpenRouter API с вопросом "что делает?". - **`POST /ai/ask`** — chat/генерация кода через LLM. 4 режима codegen: генерация с нуля, объяснение, исправление ошибки, универсальный. Плюс explain и chat режимы. - **Prompts** (`ai_prompts.go`) — шаблоны для каждого языка с сигнатурами функций (Python: `def main(event, context)`, NodeJS: `module.exports`, PHP: `function handler(...)`, Ruby: `def handler(context)`, Go: `func Handler(...)`). ### 7. Billing (статистика) **Файлы:** `billing/billing.go`, `billing/pg.go`, `billing/noop.go`, `billing/factory.go` - **Интерфейс Store** — `RecordInvocation(Invocation)`. Асинхронный, fire-and-forget. - **pgStore** — PostgreSQL через pgxpool. Таблица `invocations`. Пулы: max 4 conn, min 1. Каждая запись в своей горутине. - **NoopStore** — если `BILLING_DSN` не задан или не удалось подключиться — статистика молча отключена. - **Поля Invocation:** Namespace, FunctionName, TriggerType (http/cron/console/event), Route, HTTPMethod, StartedAt, DurationMS, StatusCode, ColdStart, RequestBytes, ResponseBytes, ErrorMsg, RecordedBy ("console"/"router"), EventType (create/delete/clone/update/invoke). - **Где записывается:** handleInvokeFunction, handleInvokeRoute, handleFissionFunctionGateway, handleCreateFunction, handleDeleteFunction, handleUpdateFunctionCode, handleCloneFunction. ### 8. Stats / Grafana **Файлы:** `stats/grafana.go`, `stats/factory.go`, `stats/noop.go`, `stats/provider.go` - **GrafanaProvider** — для каждого namespace создаёт Grafana Organization (имя = namespace), PostgreSQL datasource (тот же DSN что billing, uid="fission-user-pg"), hardcoded dashboard (SQL: WHERE namespace='...'), public dashboard link (accessToken). - **Кеш:** `sync.RWMutex` + per-namespace кеш orgID и accessToken. Идемпотентно — если Org уже есть, повторно не создаётся. - **NoopProvider** — если `GRAFANA_INTERNAL_URL` не задан. - **Провизия:** вызывается в `handleAuth` (fire-and-forget горутина), и в `handleStatsDashboard`. ### 9. UI **Файлы:** `ui/embed.go`, `ui/cron.go` - **Embedded SPA** — `//go:embed index.html`. Раздаётся на `/console/`. - **Cron UI** — отдельный handler для `/cron/` (Cron метрики, статика). - **CORS:** `Access-Control-Allow-Origin: *` (все операции защищены токеном, не cookie). - **Security headers:** nosniff, X-Frame-Options DENY, CSP (default-src 'self'), Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy (нет камере/микрофону). ### 10. Мониторинг - `/health`, `/console/health` — `ok\n` - `/console/api/ns/status` — статус namespace (существует, кол-во функций) - `/console/api/ns/debug` — дебаг-инфо (NS, user, labels) - `/console/api/stats/dashboard-url` — возвращает Grafana public dashboard URL --- ## Эволюция storagesvc (история git) - **До 19 мая 2026:** S3 (изначальная архитектура) - **19 мая:** `3eb4f56 feat: PV storage backend` — перешли на PVC (временное решение) - **20 мая:** `7539680 chore: switch storagesvc to S3 backend (ngcloud msk-1)` — вернулись на S3. PV-конфиг сохранён в `helm/fission-values-pv.yaml` для быстрого отката. - **Текущее состояние (21 мая):** S3, бакет `sless-functions`, endpoint `https://s3.msk-1.ngcloud.ru`. Ветка `feat/s3-replicated`. --- ## Чего нет - **Нет аутентификации через cookie** — только токен в заголовке. - **Нет rate limiting** — только ResourceQuota на уровне K8s. - **Нет WebSocket** — только REST. - **Нет аудит-лога** — billing таблица не immutable, можно переписать. - **Нет swagger/OpenAPI** — документация только в doc/. - **Нет gRPC** — только HTTP/JSON. - **Нет метрик Prometheus** — только billing в PostgreSQL. - **Нет CI/CD** — тесты запускаются вручную.