Files
fission-console/doc/descriptions/function-lifecycle-full.md
T

13 KiB
Raw Blame History

Полный жизненный цикл функции в Fission Console

Актуально: v1.3.56, май 2026


1. Два способа создания функции

Способ Content-Type Обработчик
Inline-код (редактор) application/json handleCreateFunctionhandleCreateFunctionFromCode
Архив (zip-файл) multipart/form-data handleCreateFunctionhandleCreateFunctionFromArchive

Определяется автоматически по заголовку Content-Type в handleCreateFunction (handlers.go ~строка 343).


2. Создание из inline-кода (шаг за шагом)

2.1 UI → Backend

Файл: console/ui/js/functions.jssubmitCreateFunction()

  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.gohandleCreateFunction (строка ~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.gobuildDeployArchive()

Язык Что происходит
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.jssubmitCreateArchive()

  1. Читает поля: name, language, entrypoint, route, methods, timeout
  2. Читает файл из <input id="ca-archive-file">
  3. Валидация на UI: файл должен заканчиваться на .zip — иначе ошибка до отправки
  4. Отправляет POST /console/api/functions с Content-Type: multipart/form-data

3.2 Backend обработка

Файл: console/internal/api/handlers.gohandleCreateFunctionFromArchive() (строка ~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.gouploadToStoragesvc()

Входные данные: []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.goEnsureEnvironment() Конфиг образов: console/internal/model/types.goLangEnvMap

5.1 Lazy creation

Environment создаётся только когда пользователь создаёт первую функцию на языке X в своём namespace. Не заранее — экономия ресурсов.

5.2 Текущие образы (АКТУАЛЬНО)

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:

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 уже создан с неправильным образом

kubectl patch environment -n <NS> console-python-env --type=json \
  -p '[{"op":"replace","path":"/spec/runtime/image","value":"naeel/fission-python-env:v1.0"}]'

Имя environment всегда: console-<lang>-env (например console-python-env).


6. Линтер (lint-archive)

Файл: console/internal/api/lint_archive.gohandleLintArchive()

Эндпоинт: POST /console/api/ai/lint-archive

6.1 Когда вызывается

Из UI при выборе файла в форме архива (fn-archive.jslintArchiveFile(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 <function>(...) должна быть определена в файле

6.3 Ответ

{
  "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 вызовы