Files
sless/doc/decisions/log.md
T

16 KiB
Raw Blame History

Решения и обоснования

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.jshttp.createServer, динамический require(HANDLER_PATH)
  • Зависимости через package.jsonnpm install --omit=dev (аналог requirements.txtpip 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.yamlEXTERNAL_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)"}


2026-03-08 — Lifecycle control: trigger.enabled + job.run_id

Задача: управление жизненным циклом ресурсов без удаления.

trigger.enabled

Проблема: нет способа "заморозить" функцию без удаления Trigger/Function ( освобождение ресурсов под праздники, дебаггинг и т.д.).

Решение: enabled bool (по умолчанию true) в TriggerSpec.

  • enabled=false → trigger_controller масштабирует Deployment функции до 0 реплик.
  • Функция не принимает запросы, не потребляет CPU (pod не запущен).
  • Изменение не пересоздаёт ресурс (нет RequiresReplace) — in-place через PATCH.

Реализация:

  • api/v1alpha1/trigger_types.goEnabled bool в TriggerSpec, //+kubebuilder:default=true
  • controllers/trigger_controller.go — патчит Deployment replicas=0/1 в зависимости от Enabled
  • internal/api/handler/triggers.goUpdateTrigger handler (PATCH), поле enabled в request/response
  • internal/api/router.goPATCH /v1/namespaces/{namespace}/triggers/{name}
  • internal/client/client.goTriggerUpdateRequest, UpdateTrigger() метод
  • terraform/provider/internal/resources/trigger_resource.go — атрибут enabled (Optional+Computed, default=true), реализован Update метод

job.run_id

Проблема: нет способа создать FunctionJob "отложенным" — с явным контролем когда запускать. Также нет механизма повторного запуска с сохранением структуры ресурса.

Решение: run_id int64 (по умолчанию 0) в FunctionJobSpec.

  • run_id=0 → FunctionJob создаётся в k8s, но k8s Job не запускается (phase=Skipped).
  • run_id>0 → запускает Job. Увеличение значения (1→2→3) = повторный запуск через пересоздание.

Реализация:

  • api/v1alpha1/job_types.goRunID int64 в FunctionJobSpec, //+kubebuilder:default=0
  • controllers/functionjob_controller.go — если RunID==0 → устанавливает phase=Skipped, return
  • internal/api/handler/jobs.go — поле run_id в jobRequest/jobResponse
  • internal/client/client.goRunID int64 в JobRequest/JobResponse
  • terraform/provider/internal/resources/job_resource.go — атрибут run_id (RequiresReplace, default=0). Если run_id=0 → не ждёт завершения, phase=Skipped сразу в state.

Версии:

  • operator: naeel/sless-operator:v0.1.6
  • provider: terra.k8c.ru/naeel/sless v0.1.4

2026-03-08 — Переключение registry с Harbor на DockerHub

Проблема: Harbor (pearlharbor.registryk8s.services.ngcloud.ru) — внешний сервис облачного провайдера. Нестабилен: /v2/ периодически зависает на 10+ секунд или возвращает 504. Kaniko не мог завершить push образа.

Решение: REGISTRY_HOST=naeel (DockerHub namespace). Образы функций пушатся как naeel/sless-default-{namespace}-{name}:latest.

Реализация:

  • deployments/k8s/operator.yaml — configmap REGISTRY_HOST: "naeel"
  • Secret sless-registry-auth уже содержал DockerHub credentials → дополнительных изменений не потребовалось

Компромисс: DockerHub — публичный registry. Образы функций пользователей публично видимы. Для production нужен приватный registry (Harbor, ECR, GCR и т.д.).

Версии:

  • operator: оператор не пересобирался, только configmap
  • commit: b69f795

2026-03-08 — FunctionJob polling вместо Owns watch

Проблема: Owns(&batchv1.Job{}) в SetupWithManager не работает cross-namespace. Job создаётся в sless-fn-{ns}, FunctionJob — в user namespace. Watch никогда не срабатывал.

Решение: Убрать Owns. В syncJobStatus при Running статусе возвращать ctrl.Result{RequeueAfter: 5 * time.Second} — контроллер сам поллит k8s Job каждые 5 сек.

Версии:

  • operator: naeel/sless-operator:v0.1.10
  • commit: 461ac09

2026-03-08 — code_hash: filesha256 вместо output_md5

Проблема: hashicorp/archive v2.7.x имеет баг: output_md5 возвращает MD5 предыдущей версии zip. output_sha и output_sha256 обновляются корректно.

Решение: code_hash = filesha256("${path.module}/code/handler.js") — хэшируется исходный файл напрямую.

Правило проекта: В sless_function.code_hash всегда использовать filesha256(source_file), не archive_file.output_md5.