feat(service): timeout_sec без дефолта; 0=нет таймаута; operator v0.1.48
- 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
This commit is contained in:
+55
-1
@@ -1,6 +1,6 @@
|
||||
# API Design
|
||||
|
||||
Последнее обновление: 2026-03-18
|
||||
Последнее обновление: 2026-03-21
|
||||
|
||||
## Базовый URL
|
||||
|
||||
@@ -23,6 +23,12 @@ 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}
|
||||
@@ -33,6 +39,12 @@ GET /v1/namespaces/{ns}/jobs/{name}
|
||||
DELETE /v1/namespaces/{ns}/jobs/{name}
|
||||
```
|
||||
|
||||
**Вызов функций (публичный, без auth):**
|
||||
|
||||
```
|
||||
POST https://sless.kube5s.ru/fn/{namespace}/{service-name} ← прокси к Deployment
|
||||
```
|
||||
|
||||
**Глобальный сервис funcs (не оператор):**
|
||||
|
||||
```
|
||||
@@ -158,3 +170,45 @@ field: code = <zip-file>
|
||||
"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 → отсутствие дедлайна).
|
||||
|
||||
|
||||
@@ -2,6 +2,41 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-21 — timeout_sec для sless_service: 0 = нет лимита (не 30s по умолчанию)
|
||||
|
||||
### Контекст
|
||||
|
||||
В `api/v1alpha1/service_types.go` поле `TimeoutSec` имело `+kubebuilder:default=30`.
|
||||
В `invoke.go` при `TimeoutSec == 0` был хардкод `35 * time.Second` как дефолтный клиент.
|
||||
|
||||
Пользователь хотел ввести таймаут как **опциональный** параметр: если не указан — длинные/бесконечные функции работают без ограничений. Дефолт 30s ломал это намерение.
|
||||
|
||||
### Решение
|
||||
|
||||
`TimeoutSec = 0` → «нет ограничения». Убраны все механизмы дефолтного таймаута:
|
||||
1. `+kubebuilder:default=30` удалён из CRD — поле 0 по умолчанию в Go (zero value)
|
||||
2. `invoke.go`: `if timeoutSec <= 0 { return &http.Client{} }` — Go Timeout=0 = нет дедлайна
|
||||
3. `services.go`: валидация `< 0 || > 900` → 400 Bad Request (0 разрешён)
|
||||
4. Terraform schema: убрано `Computed: true`; `svcToModel`: `0 → Int64Null()` (null в state)
|
||||
|
||||
### Почему именно так
|
||||
|
||||
- **0 = нет лимита** — стандарт в Go для http.Client.Timeout (явно задокументировано в stdlib)
|
||||
- **null в Terraform** вместо 0 — чтобы пользователь видел "не задано", а не "0 секунд"
|
||||
- **Computed убрано** — поле не знает своего значения пока пользователь не задал явно; Computed означало бы "оператор сам решит" что неверно
|
||||
- **Диапазон 1–900** — верхний предел защищает от бесконечных зависших запросов в Production (15 минут достаточно для любой serverless задачи)
|
||||
|
||||
### Затронутые файлы
|
||||
|
||||
| Файл | Что изменено |
|
||||
|------|-------------|
|
||||
| `api/v1alpha1/service_types.go` | Убран `+kubebuilder:default=30`, добавлен комментарий |
|
||||
| `internal/api/handler/invoke.go` | `invokeHTTPClient(0)` → `&http.Client{}` |
|
||||
| `internal/api/handler/services.go` | Валидация в CreateService и UpdateService |
|
||||
| `terraform/provider/internal/resources/service_resource.go` | Schema + svcToModel |
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-21 — Объединить sless_function и sless_service в единый пользовательский листинг
|
||||
|
||||
### Контекст
|
||||
|
||||
+76
-33
@@ -1,6 +1,81 @@
|
||||
# Прогресс разработки
|
||||
|
||||
Последнее обновление: 2026-03-21 (operator v0.1.45, funcs-service v0.2.2 — source для sless_service)
|
||||
Последнее обновление: 2026-03-21 (operator v0.1.48 — timeout_sec: 0 = нет лимита; валидация 1–900; Terraform state = null при отсутствии лимита)
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-21 — Сессия 4: фича timeout_sec без дефолта + деплой v0.1.48
|
||||
|
||||
### Что сделано
|
||||
|
||||
| # | Компонент | Версия | Результат |
|
||||
|---|-----------|--------|-----------|
|
||||
| 1 | `api/v1alpha1/service_types.go` — убрать `+kubebuilder:default=30` | — | ✅ `TimeoutSec=0` → нет ограничения |
|
||||
| 2 | `internal/api/handler/invoke.go` — убрать хардкод 35s дефолт | — | ✅ `TimeoutSec=0` → `&http.Client{}` (нет таймаута) |
|
||||
| 3 | `internal/api/handler/services.go` — валидация `timeout_sec` | — | ✅ `< 0` или `> 900` → HTTP 400 |
|
||||
| 4 | `terraform/provider/…/service_resource.go` — schema + svcToModel | — | ✅ `0` → `null` в Terraform state |
|
||||
| 5 | Оператор Docker build v0.1.48 + push | operator | в процессе |
|
||||
| 6 | kubectl rollout + rollout status | k8s | в процессе |
|
||||
|
||||
### Изменения логики timeout_sec
|
||||
|
||||
| Место | Было | Стало |
|
||||
|-------|------|-------|
|
||||
| CRD default | `+kubebuilder:default=30` (всегда 30) | нет default (0 = нет лимита) |
|
||||
| invoke.go TimeoutSec=0 | 35s hardcoded fallback | `&http.Client{}` (Go: Timeout=0 → нет таймаута) |
|
||||
| services.go validation | нет проверки | `< 0 \|\| > 900` → 400 Bad Request |
|
||||
| TF schema timeout_sec | `Optional + Computed` | `Optional` (нет Computed) |
|
||||
| svcToModel | `Int64Value(0)` в state | `Int64Null()` в state (null = не задан) |
|
||||
|
||||
### Семантика для пользователя
|
||||
|
||||
- Не указывать `timeout_sec` в terraform → функция работает без ограничений времени
|
||||
- `timeout_sec = 60` → функция убивается через 60 секунд (+ 5s grace)
|
||||
- `timeout_sec = 0` → явный «нет лимита» (эквивалентно отсутствию поля)
|
||||
- Диапазон: 1–900 секунд. Именно: `1 ≤ timeout_sec ≤ 900` или не задан (null/0)
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-21 — Сессия 3: стресс-тестирование, баги рантаймов, паника Go → 500, Python threading
|
||||
|
||||
### Что сделано
|
||||
|
||||
| # | Компонент | Версия | Результат |
|
||||
|---|-----------|--------|-----------|
|
||||
| 1 | Деплой 10 стресс-сервисов (Go/Node/Python) | stress.tf | ✅ все 13 сервисов в Ready |
|
||||
| 2 | Написан и прогнан full_test.sh | — | 43/48 PASS → 48/48 PASS после фиксов |
|
||||
| 3 | Фикс: Go panic → 502 | go1.23 v0.1.2 | ✅ defer recover() → HTTP 500 |
|
||||
| 4 | Фикс: Python exception → 502 | python3.11 v0.1.5 | ✅ try/except в do_GET/_handle_with_body |
|
||||
| 5 | Фикс: Python single-thread → connection reset | python3.11 v0.1.5 | ✅ ThreadingHTTPServer |
|
||||
| 6 | Фикс: Python TCP backlog=5 → reset при 40+ параллельных | python3.11 v0.1.6 | ✅ _HighBacklogHTTPServer(request_queue_size=128) |
|
||||
| 7 | operator v0.1.46 → v0.1.47 | оператор | ✅ задеплоен, rollout OK |
|
||||
|
||||
### Найденные и исправленные баги
|
||||
|
||||
| Баг | Причина | Фикс |
|
||||
|-----|---------|------|
|
||||
| Go panic → HTTP 502 | Go `http.Server` закрывает соединение при panic — proxy получает EOF | `defer func() { recover() → w.WriteHeader(500) }()` в server.go |
|
||||
| Python exception → HTTP 502 | `BaseHTTPRequestHandler.handle_error()` не отправляет response, закрывает соединение | `try/except Exception` в do_GET/_handle_with_body/do_HEAD → `_respond(500, {"error":...})` |
|
||||
| Python 40× parallel → 13/40 connection reset | `HTTPServer` однопоточный, очередь accept = 5 | `ThreadingHTTPServer` — каждый запрос в отдельном потоке |
|
||||
| Python 40× parallel → 7/40 connection reset даже с threading | TCP listen backlog = 5 (дефолт `socketserver.TCPServer`) | `_HighBacklogHTTPServer(request_queue_size=128)` → `listen(128)` |
|
||||
|
||||
### Финальные результаты full_test.sh
|
||||
|
||||
- **48/48 PASS** ✅
|
||||
- Фаза 1: CRUD — 16/16 сервисов + job ✅
|
||||
- Фаза 2: Функциональные тесты — Go (7), Node (4), Python (13) ✅
|
||||
- Фаза 3: PG-стресс — 40× parallel writer 40/40 OK, 30× js-async 30/30 OK, pgstorm 14k ops 0 err ✅
|
||||
- Фаза 4: Краш-шторм — 75× panic/exception → 75× HTTP 500, платформа жива ✅
|
||||
|
||||
### Итоговое состояние кластера
|
||||
|
||||
| Ресурс | Версия |
|
||||
|--------|--------|
|
||||
| operator | `pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.47` |
|
||||
| go1.23 runtime | `naeel/sless-runtime-go1.23:v0.1.2` |
|
||||
| python3.11 runtime | `naeel/sless-runtime-python3.11:v0.1.6` |
|
||||
| nodejs20 runtime | без изменений (ошибки → 500 уже работали) |
|
||||
| Строк в terraform_demo_table | ~21 000 (накоплено за сессию тестирования) |
|
||||
|
||||
---
|
||||
|
||||
@@ -926,35 +1001,3 @@ babd8e6 feat: harbor integration
|
||||
6de90ac chore: python runtime v0.1.2 + operator v0.1.28
|
||||
3372cb1 fix: build logs in status + JSON for all HTTP methods in python runtime
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-21 — Очистка examples/
|
||||
|
||||
### Что сделано
|
||||
|
||||
Удалены все устаревшие примеры из `examples/` (отдельный git-репозиторий `sless-primer`).
|
||||
Оставлен единственный актуальный пример — `POSTGRES`.
|
||||
|
||||
**Удалены директории:**
|
||||
- `TNAR` — дубль POSTGRES с другим PG instance, tfstate отсутствовал
|
||||
- `demo-event-log` — только code/, без .tf файлов
|
||||
- `demo-managed-functions` — event-writer/monitor/cleaner; ресурсы уже удалены из кластера
|
||||
- `hello-go` — использовал старый API `sless_function` + `sless_trigger`; при попытке destroy упал из-за несовместимости схемы (атрибут `function =` удалён)
|
||||
- `hello-node`, `simple-node`, `simple-python`, `notes-python`, `pg-list-python`, `k8s` — старые примеры без активных ресурсов
|
||||
|
||||
**Ресурсы hello-go/pg-list-python в кластере:** все вернули 404 при DELETE-запросах — уже не существовали.
|
||||
|
||||
**Очищен POSTGRES:**
|
||||
- Удалён `luceUNDnode.tf` (полностью закомментированный legacy Lucee+Node)
|
||||
- Удалены `stress_log_1.txt`, `funcs_list.py` (артефакты отладки)
|
||||
- `stress_destroy_apply.sh` → `stress_destroy_apply.sh.disabled` (PG lifecycle stress: баг с удалением postgres_user)
|
||||
|
||||
**Добавлена документация:**
|
||||
- `examples/POSTGRES/README.md` — новый файл, описывает пример, ресурсы, как запустить
|
||||
- `examples/README.md` — обновлён: убраны старые примеры, оставлен только POSTGRES, актуализированы ресурсы (sless_service + sless_job)
|
||||
|
||||
### Git
|
||||
```
|
||||
examples/: 4fa1e71 → 4b04cde (chore(examples): remove stale examples, keep only POSTGRES)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user