13 KiB
Полный жизненный цикл функции в 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()
- Читает поля:
name,language,entrypoint,route,methods,timeout,code(из CodeMirror) - Отправляет
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()
- Читает поля:
name,language,entrypoint,route,methods,timeout - Читает файл из
<input id="ca-archive-file"> - Валидация на UI: файл должен заканчиваться на
.zip— иначе ошибка до отправки - Отправляет
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 Текущие образы (АКТУАЛЬНО)
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.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 <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 вызовы