162 lines
10 KiB
Markdown
162 lines
10 KiB
Markdown
# Решения и обоснования
|
||
|
||
## 2026-03-06 — Отдельная репа для сервиса
|
||
|
||
**Решение:** Serverless service в отдельной репе, не вместе с Terraform provider.
|
||
|
||
**Причина:** Разные зоны ответственности, разные релизы, потенциально разные команды.
|
||
|
||
---
|
||
|
||
## 2026-03-06 — Один бинарник для v1
|
||
|
||
**Решение:** Один Go бинарник вместо микросервисов.
|
||
|
||
**Причина:** Нагрузки изначально нет. Проще деплоить, проще отлаживать. Разделим при необходимости.
|
||
|
||
---
|
||
|
||
## 2026-03-06 — Аутентификация через облачный токен
|
||
|
||
**Решение:** Использовать Bearer token облака, без Keycloak.
|
||
|
||
**Причина:** Terraform provider уже работает с токенами облака. Keycloak — лишняя зависимость для v1.
|
||
|
||
---
|
||
|
||
## 2026-03-06 — S3 облачный, остальное в кубере
|
||
|
||
**Решение:** S3 (Ceph) использовать облачный (`ceph.tst.nubes.ru`), PostgreSQL/Redis — в кластере.
|
||
|
||
**Причина:** S3 имеет внешний доступ и уже готов. Для PostgreSQL/Redis сетевого связывания с облаком пока нет — настраивается через devops облака.
|
||
|
||
---
|
||
|
||
## 2026-03-06 — Текущий кластер для разработки
|
||
|
||
**Решение:** Использовать существующий k8s кластер (namespace `sless`), потом перенести на новый.
|
||
|
||
**Причина:** Новый кластер ещё не готов. Изоляция через namespace — безопасно для существующих сервисов.
|
||
|
||
---
|
||
|
||
## 2026-03-06 — RabbitMQ откладываем
|
||
|
||
**Решение:** В v1 только HTTP и Cron триггеры. RabbitMQ/event triggers — в v2.
|
||
|
||
**Причина:** Упрощение первой итерации.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — DockerHub вместо внутреннего registry
|
||
|
||
**Решение:** Образы функций и runtime базовые образы публикуются на DockerHub (user `naeel`).
|
||
|
||
**Причина:** Namespace `registry` в кластере — это Apache NiFi Registry (NOT Docker). Отдельный Docker registry не поднят. DockerHub доступен и достаточен для разработки.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Terraform провайдер sless — отдельный модуль в той же репе
|
||
|
||
**Решение:** `terraform/provider/` — независимый Go-модуль внутри репы `sless`.
|
||
|
||
**Причина:** Удобно держать рядом. Код провайдера писался с прицелом на перенос в nubes провайдер (`/home/naeel/remote_dev/terraform`). Клиент (`internal/client/`) → `internal/core/` nubes, ресурсы → `internal/resources_gen/`.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — WaitReady в Terraform провайдере при создании функции
|
||
|
||
**Решение:** После `UploadCode` провайдер ждёт `phase=Ready` (polling каждые 5 сек, таймаут 5 мин).
|
||
|
||
**Причина:** Kaniko-сборка занимает ~1 минуту. Без ожидания `terraform apply` завершился бы с `phase=Building` в state, что неверно отображало бы реальное состояние ресурса.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — code_hash для детектирования изменений кода функции
|
||
|
||
**Решение:** Атрибут `code_hash` в `sless_function` — пользователь задаёт через `filemd5("./handler.zip")`. Изменение hash → провайдер перезагружает zip и запускает пересборку.
|
||
|
||
**Причина:** Terraform не отслеживает содержимое файлов автоматически. Это стандартный паттерн (аналогично `aws_lambda_function.source_code_hash`).
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Scale-to-zero откладываем до v2
|
||
|
||
**Решение:** В v1 функции работают как Deployment с постоянно живым подом (always-on). Scale-to-zero — в v2 через KEDA HTTP Add-on.
|
||
|
||
**Причина:** Scale-to-zero меняет архитектуру контроллера и routing. Для MVP это несоразмерная сложность. Пользователь может управлять ресурсами вручную через `replicas = 0/1/N` (планируется в v1.1).
|
||
|
||
**v2 план:** Заменить Deployment на `HTTPScaledObject` (KEDA), минимальные реплики = 0. KEDA буферизует запросы во время cold start (~1-3 сек).
|
||
|
||
---
|
||
|
||
## 2026-03-07 — replicas как ручное управление масштабом (TODO v1.1)
|
||
|
||
**Решение:** Добавить поле `replicas *int32` в `FunctionSpec`. Пользователь задаёт через Terraform: `replicas = 0` (выключить), `replicas = 1` (включить), `replicas = N` (масштабировать).
|
||
|
||
**Причина:** Без этого функция жрёт ресурсы 24/7 даже если не нужна. Это минимальный механизм контроля потребления до реализации scale-to-zero.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — PostgreSQL опционален для базового Function Hosting
|
||
|
||
**Решение:** Postgres нужен только для логов вызовов (`invocations`). Для базового деплоя функций — не нужен. Оператор работает без него (просто не пишет логи).
|
||
|
||
**Минимальные зависимости для production:** k8s кластер + S3 + Docker registry + Ingress.
|
||
|
||
## 2026-03-07 — Версионированные теги для runtime образов (не :latest)
|
||
|
||
**Решение:** Runtime базовые образы (`sless-runtime-python3.11`, `sless-runtime-nodejs20`) и образ оператора (`sless-operator`) тегируются по схеме `v<major>.<minor>.<patch>`. `:latest` не используется.
|
||
|
||
**Причина:**
|
||
- `:latest` приводит к непредсказуемому поведению: kaniko может взять старый кешированный образ, pod не перезапускается если `imagePullPolicy: IfNotPresent`.
|
||
- Версионированные теги дают явный контроль: при изменении runtime нужно обновить тег в `upload.go` → это принудительно пересобирает все функции с новым базовым образом.
|
||
- Аудит и откат: можно пинить конкретную версию runtime.
|
||
|
||
**Соглашение:**
|
||
- Runtime образы: `naeel/sless-runtime-{lang}:v{версия}` (например `v0.1.0`)
|
||
- Оператор: `naeel/sless-operator:v{версия}`
|
||
- При изменении runtime — инкрементировать минорную версию образа и обновить константу в `upload.go`
|
||
|
||
---
|
||
|
||
## 2026-03-07 — nodejs20 как второй поддерживаемый runtime
|
||
|
||
**Решение:** Добавлен nodejs20 runtime (`node:20-alpine` base, `server.js` HTTP wrapper, `exports.handle(event)`).
|
||
|
||
**Причина:** Node.js — стандарт для serverless (AWS Lambda, Vercel). Покрывает JS/TypeScript аудиторию. Паттерн идентичен python3.11: runtime image → kaniko → Deployment.
|
||
|
||
**Детали реализации:**
|
||
- `runtimes/nodejs20/server.js` — `http.createServer`, динамический `require(HANDLER_PATH)`
|
||
- Зависимости через `package.json` → `npm install --omit=dev` (аналог `requirements.txt` → `pip install`)
|
||
- `entrypoint` в HCL игнорируется для Node.js (всегда `handler.js` + `exports.handle`) — TODO: поддержать произвольный entrypoint в v1.1
|
||
|
||
---
|
||
|
||
## 2026-03-07 — FunctionJob CRD: одноразовые запуски функций
|
||
|
||
**Решение:** Добавлен `FunctionJob` CRD для одноразового запуска функции с произвольным JSON-событием.
|
||
**Причина:** Нужны sync-вызовы без HTTP — для батч-обработки, миграций, крон-задач через Terraform.
|
||
**Реализация:**
|
||
- `api/v1alpha1/job_types.go` — CRD: `FunctionRef`, `EventJSON`, phases: Pending/Running/Succeeded/Failed
|
||
- `controllers/functionjob_controller.go` — создаёт k8s Job, ждёт завершения, синхронизирует статус
|
||
- `internal/api/handler/jobs.go` — REST: CreateJob/GetJob/DeleteJob
|
||
- `terraform/provider/internal/resources/job_resource.go` — ресурс `sless_job`
|
||
- Настраиваемые таймауты: `build_timeout_sec` (sless_function), `wait_timeout_sec` (sless_job)
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Прокси /fn/ вместо wildcard Ingress
|
||
|
||
**Проблема:** wildcard DNS `*.fn.kube5s.ru` недоступен (провайдер не позволяет).
|
||
**Решение:** HTTP-прокси внутри оператора — маршрут `GET|POST|... /fn/{namespace}/{name}` на `sless-api.kube5s.ru`.
|
||
**Реализация:**
|
||
- `internal/api/handler/invoke.go` — форвардит запрос к `http://{fn}.sless-fn-{ns}.svc.cluster.local:8080`
|
||
- `internal/api/router.go` — `/fn/` регистрируется **до** auth middleware, публично доступен; `/v1/` — по-прежнему с Bearer токеном (gorilla `Use()`)
|
||
- `internal/config/config.go` — новое поле `ExternalURL` (env `EXTERNAL_URL`)
|
||
- `controllers/trigger_controller.go` — если `ExternalURL` задан, `Trigger.Status.URL = ExternalURL/fn/{ns}/{name}`; иначе fallback: создаёт Ingress с поддоменом (прежнее поведение)
|
||
- `deployments/k8s/operator.yaml` — `EXTERNAL_URL=https://sless-api.kube5s.ru`
|
||
|
||
**URL функции:** `https://sless-api.kube5s.ru/fn/{namespace}/{name}`
|
||
**E2E:** `curl https://sless-api.kube5s.ru/fn/default/hello-node` → `{"message":"Hello, Naeel! (nodejs20)"}`
|