- doc/progress.md: funcs global service v0.1.x, URLs, next steps - doc/decisions/log.md: storage architecture, funcs service decision, web-console plan - doc/api/design.md: current + planned endpoints (/source, PATCH trigger) - doc/architecture/overview.md: funcs-service component, S3 storage diagram
205 lines
9.4 KiB
Markdown
205 lines
9.4 KiB
Markdown
# Архитектура системы
|
||
|
||
Последнее обновление: 2026-03-18 (v0.1.33 + funcs-service v0.1.3)
|
||
|
||
## Общее описание
|
||
|
||
Managed Serverless Functions Service для облачного провайдера nubes.ru.
|
||
Пользователь загружает код через Terraform, сервис его собирает (kaniko) и запускает
|
||
по HTTP-триггеру, расписанию (cron) или вручную через one-shot Job.
|
||
|
||
## Стек
|
||
|
||
| Компонент | Технология | Где запущен |
|
||
|-----------|-----------|-------------|
|
||
| Operator (API + Controllers) | Go (controller-runtime) | Kubernetes, namespace `sless` |
|
||
| funcs-service (глобальная консоль) | Go (net/http) | 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.
|
||
|
||
## Компонент: funcs-service
|
||
|
||
Глобальный HTTP сервис — **одна копия** на весь кластер, для всех пользователей.
|
||
|
||
```
|
||
User Browser / curl
|
||
└─► https://sless.kube5s.ru/funcs/<namespace>
|
||
└─► nginx Ingress (sless-funcs-ingress)
|
||
└─► sless-funcs-service:8090 (namespace sless)
|
||
└─► http://sless-operator.sless.svc.cluster.local:9090/v1/...
|
||
```
|
||
|
||
**Файлы:**
|
||
- `services/funcs/main.go` — логика
|
||
- `services/funcs/Dockerfile` — multi-stage Go → alpine
|
||
- `deployments/k8s/funcs-service.yaml` — Deployment + Service + Ingress
|
||
|
||
**Env vars сервиса:**
|
||
| Переменная | Значение |
|
||
|-----------|---------|
|
||
| `SLESS_OPERATOR_URL` | `http://sless-operator.sless.svc.cluster.local:9090` |
|
||
| `SLESS_EXTERNAL_URL` | `https://sless.kube5s.ru` |
|
||
| `SLESS_EXCLUDE` | `event-writer,event-monitor,event-cleaner` |
|
||
| `SLESS_SERVICE_TOKEN` | JWT токен (задаётся через `kubectl set env`, не в git) |
|
||
|
||
## Хранение кода функций в S3
|
||
|
||
```
|
||
Terraform source_dir (локально)
|
||
└─► zip → POST /upload → builder.PrepareContext()
|
||
├─► functions/{ns}/{name}/{ts}.zip ← ИСХОДНЫЙ КОД пользователя
|
||
└─► contexts/{ns}/{name}/{ts}.tar.gz ← BUILD CONTEXT для kaniko
|
||
└─► Function CRD: spec.s3Key = "contexts/..."
|
||
└─► контроллер → kaniko Job → Docker image
|
||
```
|
||
|
||
Для web-консоли: `GET /source` читает `functions/{ns}/{name}/{ts}.zip` напрямую.
|
||
|
||
## Изоляция пользователей — 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 |
|