- api/v1alpha1/service_types.go: убрать +kubebuilder:default=30
- invoke.go: TimeoutSec=0 → &http.Client{} (без таймаута)
- services.go: валидация timeout_sec < 0 || > 900 → HTTP 400
- service_resource.go: TF schema Optional (без Computed); 0 → Int64Null()
- deployments/k8s/operator.yaml: v0.1.47 → v0.1.48
- doc/: progress.md + api/design.md (модель Service) + decisions/log.md
- examples/POSTGRES/: bug_hunter.sh, chaos_marathon.sh, chaos_marathon.tf
215 lines
7.5 KiB
Markdown
215 lines
7.5 KiB
Markdown
# API Design
|
||
|
||
Последнее обновление: 2026-03-21
|
||
|
||
## Базовый URL
|
||
|
||
```
|
||
http://<operator-host>:9090/v1
|
||
```
|
||
|
||
Локально: `http://localhost:9090/v1`
|
||
В кластере: `http://sless-operator.sless.svc.cluster.local:9090/v1`
|
||
Публично: `https://sless.kube5s.ru/v1/...` (через Ingress)
|
||
|
||
**Реализованные эндпоинты:**
|
||
|
||
```
|
||
GET /v1/namespaces/{ns}/functions
|
||
POST /v1/namespaces/{ns}/functions
|
||
GET /v1/namespaces/{ns}/functions/{name}
|
||
PUT /v1/namespaces/{ns}/functions/{name}
|
||
DELETE /v1/namespaces/{ns}/functions/{name}
|
||
POST /v1/namespaces/{ns}/functions/{name}/upload
|
||
GET /v1/namespaces/{ns}/functions/{name}/source ← файлы кода из S3 tar.gz (JSON)
|
||
GET /v1/namespaces/{ns}/functions/{name}/invocations
|
||
GET /v1/namespaces/{ns}/services
|
||
POST /v1/namespaces/{ns}/services
|
||
GET /v1/namespaces/{ns}/services/{name}
|
||
PUT /v1/namespaces/{ns}/services/{name}
|
||
DELETE /v1/namespaces/{ns}/services/{name}
|
||
GET /v1/namespaces/{ns}/services/{name}/source ← файлы кода из S3 tar.gz (JSON)
|
||
GET /v1/namespaces/{ns}/triggers
|
||
POST /v1/namespaces/{ns}/triggers
|
||
GET /v1/namespaces/{ns}/triggers/{name}
|
||
PATCH /v1/namespaces/{ns}/triggers/{name} ← {"enabled": bool}
|
||
DELETE /v1/namespaces/{ns}/triggers/{name}
|
||
POST /v1/namespaces/{ns}/jobs
|
||
GET /v1/namespaces/{ns}/jobs/{name}
|
||
DELETE /v1/namespaces/{ns}/jobs/{name}
|
||
```
|
||
|
||
**Вызов функций (публичный, без auth):**
|
||
|
||
```
|
||
POST https://sless.kube5s.ru/fn/{namespace}/{service-name} ← прокси к Deployment
|
||
```
|
||
|
||
**Глобальный сервис funcs (не оператор):**
|
||
|
||
```
|
||
GET https://sless.kube5s.ru/funcs/<namespace> ← plain text (курл) / HTML (браузер)
|
||
GET https://sless.kube5s.ru/funcs?token=<jwt> ← редирект по namespace
|
||
GET https://sless.kube5s.ru/funcs/<namespace>/source/<fn> ← прокси к GET /source
|
||
PATCH https://sless.kube5s.ru/funcs/<namespace>/triggers/<name> ← прокси к PATCH /triggers
|
||
GET https://sless.kube5s.ru/health ← liveness probe
|
||
```
|
||
|
||
## Аутентификация
|
||
|
||
```
|
||
Authorization: Bearer <cloud-token>
|
||
```
|
||
|
||
Токен — JWT от `auth-api`. Middleware в операторе:
|
||
1. Извлекает `sub` из payload (без проверки подписи — доверяет Ingress)
|
||
2. Вычисляет namespace: `SHA256(sub)[:8]` hex → `sless-{16 hex символов}`
|
||
3. Проверяет что запрошенный `{namespace}` совпадает с вычисленным
|
||
|
||
## Ресурсы
|
||
|
||
### Functions
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | /functions | Список функций |
|
||
| POST | /functions | Создать функцию |
|
||
| GET | /functions/{id} | Получить функцию |
|
||
| PUT | /functions/{id} | Обновить функцию |
|
||
| DELETE | /functions/{id} | Удалить функцию |
|
||
|
||
### Versions (код функции)
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | /functions/{id}/versions | Список версий |
|
||
| POST | /functions/{id}/versions | Загрузить новый код (multipart zip) |
|
||
| GET | /functions/{id}/versions/{ver} | Получить версию |
|
||
| POST | /functions/{id}/versions/{ver}/activate | Активировать версию |
|
||
|
||
### Triggers
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| GET | /functions/{id}/triggers | Список триггеров |
|
||
| POST | /functions/{id}/triggers | Создать триггер (HTTP/Cron) |
|
||
| DELETE | /functions/{id}/triggers/{tid} | Удалить триггер |
|
||
|
||
### Invocations (вызов и логи)
|
||
|
||
| Метод | Путь | Описание |
|
||
|-------|------|----------|
|
||
| POST | /functions/{id}/invoke | Синхронный вызов |
|
||
| GET | /functions/{id}/invocations | История вызовов |
|
||
| GET | /functions/{id}/invocations/{iid} | Детали вызова + логи |
|
||
|
||
## Upload endpoint
|
||
|
||
```
|
||
POST /v1/namespaces/{namespace}/functions/{name}/upload
|
||
Content-Type: multipart/form-data
|
||
Authorization: Bearer <token>
|
||
|
||
field: code = <zip-file>
|
||
```
|
||
|
||
Процесс:
|
||
1. Принимает zip (max 32MB)
|
||
2. Распаковывает zip
|
||
3. Генерирует `Dockerfile` (`FROM naeel/sless-runtime-{runtime}:latest\nCOPY . /app/function/`)
|
||
4. Перепаковывает в `tar.gz` (kaniko требует tar format)
|
||
5. Загружает в S3: `contexts/{ns}/{name}/{timestamp}.tar.gz`
|
||
6. Обновляет `fn.Spec.S3Key` → контроллер видит изменение и запускает kaniko Job
|
||
|
||
Ответ `200 OK`:
|
||
```json
|
||
{"message": "build queued", "phase": "Pending", "s3_key": "contexts/..."}
|
||
```
|
||
|
||
## Поддерживаемые runtime (v1)
|
||
|
||
- `python3.11` — реализован и протестирован
|
||
- `nodejs20` — реализован и протестирован
|
||
- `go1.23` — реализован
|
||
|
||
## Модель Function
|
||
|
||
```json
|
||
{
|
||
"id": "fn-uuid",
|
||
"name": "my-function",
|
||
"description": "...",
|
||
"runtime": "python3.11",
|
||
"entrypoint": "handler.handle",
|
||
"memory_mb": 128,
|
||
"timeout_sec": 30,
|
||
"env_vars": {"KEY": "value"},
|
||
"active_version": "1",
|
||
"status": "active",
|
||
"created_at": "...",
|
||
"updated_at": "..."
|
||
}
|
||
```
|
||
|
||
## Модель Trigger
|
||
|
||
```json
|
||
{
|
||
"id": "tr-uuid",
|
||
"type": "http",
|
||
"url": "https://sless.api.ngcloud.ru/invoke/fn-uuid",
|
||
"created_at": "..."
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"id": "tr-uuid",
|
||
"type": "cron",
|
||
"schedule": "0 * * * *",
|
||
"created_at": "..."
|
||
}
|
||
```
|
||
|
||
## Модель Service (sless_service — always-on Deployment)
|
||
|
||
`sless_service` — долгоживущая функция. Деплоится как Kubernetes Deployment + Service + Ingress.
|
||
Вызывается через `POST /fn/{namespace}/{name}` без авторизации (прокси напрямую к поду).
|
||
|
||
```json
|
||
{
|
||
"name": "pg-info",
|
||
"display_name": "PostgreSQL Info",
|
||
"description": "Возвращает список таблиц",
|
||
"runtime": "python3.11",
|
||
"entrypoint": "handler.handle",
|
||
"memory_mb": 128,
|
||
"timeout_sec": null,
|
||
"env_vars": {"DB_HOST": "..."},
|
||
"status": "Ready",
|
||
"image_ref": "pearlharbor.../naeel/pg-info:abc123",
|
||
"invoke_url": "https://sless.kube5s.ru/fn/sless-ffd1f598c169b0ae/pg-info"
|
||
}
|
||
```
|
||
|
||
### Поле `timeout_sec`
|
||
|
||
| Значение | Поведение |
|
||
|----------|-----------|
|
||
| `null` / не задано | Без ограничений — функция выполняется любое время |
|
||
| `1–900` | Таймаут в секундах (+ 5s grace на стороне прокси) |
|
||
| `< 0` или `> 900` | HTTP 400 Bad Request |
|
||
|
||
> **Примечание:** Поле опциональное (Optional в Terraform). Не указывать = без лимита.
|
||
> В Terraform state значение `null` означает "лимит не задан" (не путать с `0`).
|
||
> В Kubernetes CRD `TimeoutSec: 0` → лимита нет (поле omitempty).
|
||
|
||
### Invoke прокси (как работает timeout_sec)
|
||
|
||
Оператор принимает `POST /fn/{ns}/{name}`, находит Service CRD, проксирует запрос
|
||
к `http://{name}.sless-fn-{ns}.svc.cluster.local:8080`.
|
||
|
||
Если `TimeoutSec > 0` — создаёт `http.Client{Timeout: TimeoutSec*s + 5s}`.
|
||
Если `TimeoutSec == 0` — `http.Client{}` (Go: Timeout=0 → отсутствие дедлайна).
|
||
|