Files
sless/doc/architecture/overview.md
T

7.7 KiB
Raw Blame History

Архитектура системы

Последнее обновление: 2026-03-11 (v0.1.22)

Общее описание

Managed Serverless Functions Service для облачного провайдера nubes.ru. Пользователь загружает код через Terraform, сервис его собирает (kaniko) и запускает по HTTP-триггеру, расписанию (cron) или вручную через one-shot Job.

Стек

Компонент Технология Где запущен
Operator (API + Controllers) Go (controller-runtime) Kubernetes, namespace sless
PostgreSQL PostgreSQL 16 Kubernetes, namespace sless
S3 Ceph (облачный) s3.msk-1.ngcloud.ru
Container Registry DockerHub (naeel/) внешний
Builder kaniko (k8s Job) namespace пользователя
Функции (HTTP) k8s Deployment + Service namespace пользователя
Функции (one-shot) k8s Job namespace пользователя
Функции (cron) k8s CronJob namespace пользователя
Terraform Provider Go (plugin framework v6) localhost/CI
nubes API REST (облако) deck-api.ngcloud.ru

Redis и RabbitMQ — отложены до v2.

Изоляция пользователей — Namespace per user

Каждый пользователь облака получает отдельный k8s namespace.

JWT токен (Bearer)
  └─► JWT.sub (строка "0199e325-1cdf-7cda-9319-e5302a85e291")
        └─► SHA256(sub) → первые 8 байт → hex → "sless-{16 hex символов}"
              └─► namespace = "sless-cdd874dfa31ba6ca"
  • Namespace детерминирован: один sub → всегда один namespace.
  • sub не раскрывается в имени namespace (SHA256 необратим).
  • Длина 22 символа — укладывается в лимит k8s (63).

Кто создаёт namespace: Terraform провайдер при Configure() вызывает POST /v1/namespaces/{ns}/ensure один раз, до любых ресурсных операций. Resource-хендлеры (Function, Trigger, Job) namespace не создают — это не их ответственность.

Аутентификация

Оператор (REST API)

  • Bearer JWT в заголовке Authorization
  • Проверяется структура JWT (3 части), наличие sub claim, срок действия exp
  • Подпись не проверяется — trusted perimeter (оператор за Ingress)

Terraform Provider (при Configure)

  1. Декодирует JWT → sub
  2. Вычисляет namespace через SHA256
  3. Если задан nubes_endpoint — пингует nubes API (GET <nubes_endpoint>) с тем же токеном
    • HTTP 401/403 → ошибка инициализации провайдера
    • Недоступен → ошибка инициализации
  4. Вызывает POST /v1/namespaces/{ns}/ensure (создаёт namespace если нет)

Схема вызова

Пользователь (curl / браузер)
    |
    v GET|POST|... /fn/{namespace}/{name}/*
sless-api Ingress -> Operator /fn/ прокси
    |
    v HTTP forward -> http://{name}.{namespace}.svc.cluster.local:8080
k8s Service -> Deployment/Pod функции
terraform apply
    |
    v provider Configure()
        1. JWT -> sub -> namespace
        2. PingNubesAPI (валидация токена)
        3. POST /v1/namespaces/{ns}/ensure  <- создаёт k8s namespace
    |
    +-> POST /v1/namespaces/{ns}/functions  <- создаёт Function CRD
    |       +-> POST /upload (zip)          <- загружает код -> S3 -> kaniko Job
    |                +-> polling phase=Ready
    |
    +-> POST /v1/namespaces/{ns}/triggers   <- создаёт Trigger CRD
    |       +-> controller: Deployment + Service + (CronJob для cron)
    |
    +-> POST /v1/namespaces/{ns}/jobs       <- создаёт FunctionJob CRD
            +-> controller: k8s Job -> result в status

Структура кода

sless/
|-- main.go                        точка входа: k8s manager + REST API сервер (goroutine)
|-- internal/
|   |-- api/
|   |   |-- router.go              gorilla/mux: /fn/ (публичный), /v1/ (auth + middleware)
|   |   |-- handler/
|   |   |   |-- handler.go         Handler struct + helpers (writeJSON, namespace(), pathVar())
|   |   |   |-- namespace.go       EnsureNamespace (POST /v1/namespaces/{ns}/ensure)
|   |   |   |-- functions.go       CRUD Function
|   |   |   |-- triggers.go        CRUD Trigger
|   |   |   |-- jobs.go            CRUD FunctionJob
|   |   |   |-- upload.go          zip -> Dockerfile -> tar.gz -> S3 -> CRD patch
|   |   |   |-- invoke.go          прокси /fn/{ns}/{name} -> in-cluster DNS
|   |   |   +-- invocations.go     501 stub (реализация отложена)
|   |   +-- middleware/
|   |       |-- auth.go            JWT validation (struct + sub + exp, подпись не проверяется)
|   |       +-- logging.go         slog request logger
|   |-- builder/
|   |   |-- builder.go         kaniko Job lifecycle (Build, JobStatus, Cleanup)
|   |   +-- context.go         PrepareContext: zip+runtime → tar.gz+Dockerfile для kaniko
|   |-- config/config.go           Load() из env vars
|   +-- storage/
|       |-- postgres/store.go      SaveInvocation, ListInvocations, RunMigrations
|       +-- s3/client.go           Upload, Download, UploadContext (tar.gz для kaniko)
|-- controllers/
|   |-- function_controller.go     Reconcile: Pending->Building->Ready/Failed + Deployment
|   |-- trigger_controller.go      Reconcile: Service+Ingress (http) / CronJob (cron)
|   +-- functionjob_controller.go  Reconcile: k8s Job -> Succeeded/Failed + output capture
|-- api/v1alpha1/
|   |-- function_types.go          Function CRD
|   |-- trigger_types.go           Trigger CRD
|   +-- job_types.go               FunctionJob CRD
|-- deployments/k8s/
|   |-- operator.yaml              Deployment + Service + Ingress
|   +-- rbac.yaml                  ClusterRole + ClusterRoleBinding + ServiceAccount
|-- terraform/provider/            независимый Go-модуль
|   +-- internal/
|       |-- client/client.go       SubFromJWT, NamespaceFromSub, PingNubesAPI + CRUD
|       |-- provider/provider.go   Configure(): JWT->NS->ping->EnsureNamespace
|       +-- resources/
|           |-- function_resource.go  sless_function
|           |-- trigger_resource.go   sless_trigger
|           +-- job_resource.go       sless_job
+-- runtimes/
    |-- python3.11/                server.py + Dockerfile -> naeel/sless-runtime-python3.11:v0.1.1
    +-- nodejs20/                  server.js + Dockerfile -> naeel/sless-runtime-nodejs20:v0.1.2

Kubernetes кластер

Сейчас используется существующий кластер (временный). Планируется переезд на новый кластер — манифесты переносятся без изменений.

Ноды:

  • wheel-control-plane-fm9sr — control-plane
  • wheel-workers-tv4qr-r45xs — worker
  • wheel-workers-tv4qr-x8xw7 — worker

Ingress: nginx, external IP 5.172.178.182 API endpoint: https://sless-api.kube5s.ru

Версии в production

Артефакт Тег/Версия
naeel/sless-operator v0.1.22
terra.k8c.ru/naeel/sless провайдер v0.1.13
naeel/sless-runtime-python3.11 v0.1.1
naeel/sless-runtime-nodejs20 v0.1.2