# Полный жизненный цикл функции в Fission Console Актуально: **v1.3.56**, май 2026 --- ## 1. Два способа создания функции | Способ | Content-Type | Обработчик | |--------|-------------|------------| | **Inline-код** (редактор) | `application/json` | `handleCreateFunction` → `handleCreateFunctionFromCode` | | **Архив** (zip-файл) | `multipart/form-data` | `handleCreateFunction` → `handleCreateFunctionFromArchive` | Определяется автоматически по заголовку `Content-Type` в `handleCreateFunction` (handlers.go ~строка 343). --- ## 2. Создание из inline-кода (шаг за шагом) ### 2.1 UI → Backend **Файл:** `console/ui/js/functions.js` → `submitCreateFunction()` 1. Читает поля: `name`, `language`, `entrypoint`, `route`, `methods`, `timeout`, `code` (из CodeMirror) 2. Отправляет `POST /console/api/functions` с `Content-Type: application/json` ### 2.2 Backend обработка **Файл:** `console/internal/api/handlers.go` → `handleCreateFunction` (строка ~343) ``` POST /console/api/functions (JSON) ↓ Валидация имени (regex ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$, max 57 символов) ↓ EnsureUserNS → создаёт K8s namespace если не существует ↓ EnsureEnvironment → создаёт Fission Environment CRD если не существует ↓ buildDeployArchive(lang, code) → упаковывает код в байты ↓ buildDeploySpec(ctx, bytes) → uploadToStoragesvc → S3 ↓ Создаёт Fission Package CRD (type: url → S3) ↓ Создаёт Fission Function CRD (ссылается на Package) ↓ Создаёт Fission HTTPTrigger CRD (роут) ↓ Аннотация fission-console/source-type = "code" ``` ### 2.3 buildDeployArchive — упаковка кода **Файл:** `console/internal/api/handlers.go` → `buildDeployArchive()` | Язык | Что происходит | |------|---------------| | Python | код → сырые байты (*.py файл) | | Node.js | код → zip с `package.json` + `main.js` (ESM wrapper) | | PHP | код → zip с `main.php` | | Ruby | код → zip с `handler.rb` | | Go | отдельный путь: `BuildGoSourceZip` → source package → builder job | ### 2.4 Entrypoint по умолчанию **Файл:** `console/internal/runtime/` → `DefaultEntrypoint(lang)` | Язык | Entrypoint по умолчанию | |------|------------------------| | python | `user.main` | | nodejs | `main` | | go | `Handler` | | php | `main` | | ruby | `main` | --- ## 3. Создание из архива (шаг за шагом) ### 3.1 UI → Backend **Файл:** `console/ui/js/fn-archive.js` → `submitCreateArchive()` 1. Читает поля: `name`, `language`, `entrypoint`, `route`, `methods`, `timeout` 2. Читает файл из `` 3. **Валидация на UI:** файл должен заканчиваться на `.zip` — иначе ошибка до отправки 4. Отправляет `POST /console/api/functions` с `Content-Type: multipart/form-data` ### 3.2 Backend обработка **Файл:** `console/internal/api/handlers.go` → `handleCreateFunctionFromArchive()` (строка ~1302) ``` POST /console/api/functions (multipart/form-data) ↓ ParseMultipartForm (лимит 32 MB) ↓ Валидация имени ↓ r.FormFile("archive") → читает байты архива ↓ ⛔ ПРОВЕРКА magic bytes: первые 2 байта должны быть 0x50 0x4B (PK = zip) Если нет → HTTP 400 "загруженный файл не является валидным zip-архивом" ↓ EnsureUserNS ↓ EnsureEnvironment (по полю "language") ↓ buildDeploySpec(ctx, archiveBytes) → uploadToStoragesvc → S3 ↓ Создаёт Fission Package CRD (type: url → S3) ↓ Создаёт Fission Function CRD ↓ Создаёт Fission HTTPTrigger CRD ↓ Аннотация fission-console/source-type = "archive" ↓ Аннотация fission-console/archive-file = <оригинальное имя файла> ``` --- ## 4. Загрузка в S3 через storagesvc **Файл:** `console/internal/api/handlers.go` → `uploadToStoragesvc()` ``` Входные данные: []byte (байты zip-архива) ↓ Строим multipart/form-data вручную: boundary = "fission" + UnixNano поле: name="uploadfile", filename="archive.zip" Content-Type: application/octet-stream тело: байты архива ↓ POST http://storagesvc.fission.svc.cluster.local/v1/archive заголовок X-File-Size: <размер байт> ↓ Storagesvc сохраняет в S3 (bucket: sless-functions) ↓ Ответ: {"id": "fission/UUID"} ↓ Возвращаем URL: http://storagesvc.../v1/archive?id=fission/UUID ``` **Если storagesvcURL не задан** → fallback на `type: literal` (base64 в etcd). Нежелательно для больших файлов. **Переменная окружения:** `STORAGESVC_URL=http://storagesvc.fission.svc.cluster.local` ### 4.1 Удаление из S3 При удалении функции → `deleteFromStoragesvc()`: ``` DELETE http://storagesvc.../v1/archive?id=fission/UUID ``` Best-effort: ошибка логируется но не прерывает удаление Function/Package CRD. --- ## 5. Environment — КРИТИЧЕСКИ ВАЖНО **Файл:** `console/internal/fission/environment.go` → `EnsureEnvironment()` **Конфиг образов:** `console/internal/model/types.go` → `LangEnvMap` ### 5.1 Lazy creation Environment создаётся **только когда пользователь создаёт первую функцию** на языке X в своём namespace. Не заранее — экономия ресурсов. ### 5.2 Текущие образы (АКТУАЛЬНО) ```go var LangEnvMap = map[string]LangEnvDef{ "python": {Image: "naeel/fission-python-env:v1.0"}, // ⛔ НЕ МЕНЯТЬ НА ОФИЦИАЛЬНЫЙ "nodejs": {Image: "ghcr.io/fission/node-env"}, "go": {Image: "ghcr.io/fission/go-env", BuilderImage: "naeel/go-builder-fast:v1"}, "php": {Image: "ghcr.io/fission/php-env"}, "ruby": {Image: "ghcr.io/fission/ruby-env"}, } ``` ### 5.3 ⛔⛔⛔ PYTHON — ТОЛЬКО НАШ ОБРАЗ `naeel/fission-python-env:v1.0` содержит патч `inspect.signature`: ```python def _count_required_params(func): # inspect.signature → считает обязательные аргументы def userfunc_call(self, *args, **kwargs): required = _count_required_params(self.userfunc) if required == 0: return self.userfunc() # def main(): elif required == 1: return self.userfunc(request) # def main(request): elif required >= 2: return self.userfunc(request, {}) # def main(event, context): ``` **Официальный `ghcr.io/fission/python-env`** вызывает `userfunc()` без аргументов → `TypeError: missing 2 required positional arguments` → HTTP 500 для любой функции `def main(event, context)`. ### 5.4 Если environment уже создан с неправильным образом ```bash kubectl patch environment -n console-python-env --type=json \ -p '[{"op":"replace","path":"/spec/runtime/image","value":"naeel/fission-python-env:v1.0"}]' ``` Имя environment всегда: `console--env` (например `console-python-env`). --- ## 6. Линтер (lint-archive) **Файл:** `console/internal/api/lint_archive.go` → `handleLintArchive()` **Эндпоинт:** `POST /console/api/ai/lint-archive` ### 6.1 Когда вызывается Из UI при выборе файла в форме архива (`fn-archive.js` → `lintArchiveFile(prefix)`). Поля: `archive` (zip), `entrypoint` (опционально), `language` (опционально). ### 6.2 Порядок проверок ``` 1. Размер архива ≤ 100 KB (maxArchiveBytes) 2. Суммарный распакованный размер ≤ 100 KB (защита от zip bomb) 3. Синтаксический линтер по каждому файлу: .py → python3 -m py_compile .js → node --check .rb → ruby -c .php → php -l 4. Проверка language vs расширения файлов в архиве: python → нужен хотя бы один .py nodejs → нужен хотя бы один .js ...и т.д. 5. Проверка entrypoint (формат "module.function"): → файл module.py должен быть в архиве → def (...) должна быть определена в файле ``` ### 6.3 Ответ ```json { "results": [ {"file": "demo.py", "ok": true}, {"file": "utils.py", "ok": false, "output": "SyntaxError: ..."}, {"file": "язык", "ok": false, "output": "выбран python, но .py файлов нет"}, {"file": "entrypoint", "ok": false, "output": "demo.py найден, но def main не определён"} ], "ok": false } ``` --- ## 7. Fission: как функция выполняется (runtime) ``` Пользователь вызывает HTTP URL роута ↓ Fission Router (JWT auth если включён) ↓ Executor (poolmgr) — выбирает под из пула ↓ Fetcher sidecar — скачивает архив из S3: GET http://storagesvc.../v1/archive?id=fission/UUID Распаковывает zip → /userfunc/ (для inline Python: кладёт .py как /userfunc/user.py) ↓ Python runtime pod специализируется: POST /specialize на порт 8888 пода server.py импортирует module из /userfunc/ ↓ Вызов функции: _count_required_params(userfunc) → 0/1/2 аргументов 0 → userfunc() 1 → userfunc(request) 2 → userfunc(request, {}) ``` --- ## 8. Namespace пользователя Каждый пользователь → отдельный K8s namespace. | Пользователь | Namespace | |-------------|-----------| | chiken | `fission-54a1d63ee5e0921b` | | livetest@test.local | `fission-199e39e44b809ec1` | Namespace создаётся при первом обращении через `EnsureUserNS()`. Имя environment всегда `console-python-env` (не уникально на пользователя) — каждый пользователь имеет СВОЙ environment в своём namespace. --- ## 9. Роут функции Формат по умолчанию: `/<последние 12 символов namespace>/<имя функции>` Пример: namespace `fission-54a1d63ee5e0921b` → роут `/d63ee5e0921b/funcname` --- ## 10. Аннотации на Function CRD | Аннотация | Значение | |-----------|---------| | `fission-console/source-type` | `code` или `archive` | | `fission-console/archive-file` | оригинальное имя загруженного файла (только для архивов) | | `fission-console/expires-at` | Unix timestamp протухания (если задан TTL) | --- ## 11. Что НЕ ДОЛЖНО происходить (зафиксированные баги) | Баг | Причина | Последствие | |-----|---------|-------------| | `ghcr.io/fission/python-env` в LangEnvMap | Забыли про наш патч | `def main(event, context)` → 500 | | Загрузка `.py` файла вместо `.zip` | UI не проверял расширение | Fission не может распаковать → 500 | | 0 байт в S3 | Файл не zip (magic bytes ≠ PK) | Fetcher не может распаковать → 500 | --- ## 12. Файловая структура кода (консоль) ``` console/ cmd/server/ — точка входа (main.go) internal/ api/ handlers.go — HTTP handlers: CRUD функций, invoke, edit lint_archive.go — POST /ai/lint-archive ai_check.go — LLM-анализ кода через OpenAI-совместимый API server.go — Server struct, маршрутизация auth/ — JWT/Basic auth middleware fission/ environment.go — EnsureEnvironment, CleanupEnvironmentIfUnused namespace.go — EnsureUserNS gvr.go — GVR константы для K8s dynamic client model/ types.go — LangEnvMap ⚠️ ОБРАЗЫ ЯЗЫКОВ ЗДЕСЬ runtime/ python.go — DefaultEntrypoint, BuildPythonZip nodejs.go — BuildJSDeployZip go.go — BuildGoSourceZip ui/ js/ functions.js — создание/редактирование inline-функций fn-archive.js — создание из архива fn-edit-archive.js — редактирование архивной функции app.js — общая логика, auth, API вызовы ```