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:
Naeel
2026-03-21 16:58:43 +03:00
parent 7f7aa44e59
commit b86ff3a62e
37 changed files with 2493 additions and 50 deletions
+55 -1
View File
@@ -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` / не задано | Без ограничений — функция выполняется любое время |
| `1900` | Таймаут в секундах (+ 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 → отсутствие дедлайна).
+35
View File
@@ -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
View File
@@ -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 = нет лимита; валидация 1900; 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)
```