276 lines
15 KiB
Markdown
276 lines
15 KiB
Markdown
# Анализ репозитория 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/<ns>/<name>`.
|
||
Ответ: `{status, body, headers, cold_start, response_raw, pod, duration_ms}`.
|
||
Записывает в billing.
|
||
- **`/fn/<route>`** — публичный gateway. Строит URL по маршруту, проксирует
|
||
тело/заголовки (кроме служебных). Таймаут из `spec.functionTimeout`.
|
||
- **`/fission-function/<ns>/<name>`** — внутренний 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** — тесты запускаются вручную.
|