Прогресс разработки
Последнее обновление: 2026-03-21
TODO (backlog — не скоро)
| # |
Задача |
Заметка |
| T1 |
nubes_endpoint в sless провайдере — сделать обязательным или ввести флаг require_token_validation |
Сейчас если nubes_endpoint не задан — проверка токена через nubes API пропускается. Упущение безопасности. |
| T2 |
Terraform провайдер nubes — end-to-end тестирование |
Провайдер написан но ни разу не прогонялся с реальным сервером. Нужно: init → apply → apply (idempotency) → update → destroy. Тест-кейсы уже есть в examples/. Это отдельный большой блок работы для облачных DevOps-инженеров nubes. |
TODO (завтра — приоритет)
ПЛАН: multi-user тест с Postgres
Шаг 1 — Добавить Postgres в survival тест (один юзер):
- Функции в
operator_survival_test.sh сейчас echo-хендлеры без PG
- Добавить группу тестов где функция делает реальные INSERT/SELECT в Postgres
- Postgres креды берутся из параметров ресурса
sless_pg (через env_vars функции)
- Изоляция: таблица называется
test_<namespace> — каждый юзер пишет в свою
- Прогнать от одного юзера → убедиться что работает
Шаг 2 — Параметризовать survival тест:
TOKEN и NAMESPACE через env переменные (сейчас хардкодные)
PG_DSN через env переменную (берётся из параметров Postgres ресурса)
Шаг 3 — Обёртка на 10 юзеров:
operator_multiuser_test.sh
- Генерирует 10 фейковых JWT (
test-user-01 ... test-user-10)
- Для каждого вычисляет namespace (SHA256(sub)[:8 байт])
POST /v1/namespaces/{ns}/ensure — создаёт namespace
- Параллельно запускает полный survival тест в каждом namespace (
&)
- Ждёт всех (
wait), собирает итоговый отчёт: PASS/FAIL по каждому юзеру
Ожидаемый результат: ~340 тестов (34×10), ~15-20 минут параллельно
2026-03-21 — Сессия 7: survival-тест (группы 5-10)
Что сделано
| # |
Компонент |
Результат |
| 1 |
operator_survival_test.sh — написан (928 строк, 6 групп) |
✅ запущен |
| 2 |
Группа 5: FLOOD BUILD — 6 функций (3×Python + 3×Node.js), 9 тестов |
🔄 в процессе |
| 3 |
Группа 6: RAPID DISCARD STORM — 30 create→DELETE, 4 теста |
🔄 в процессе |
| 4 |
Группа 7: CHURN UNDER FIRE — 10 PUT под 200 invokes, 9 тестов |
🔄 в процессе |
| 5 |
Группа 8: PHOENIX — 3 цикла DELETE+recreate |
🔄 в процессе |
| 6 |
Группа 9: SELF-HEALING MARATHON — 5×kill Deployment |
🔄 в процессе |
| 7 |
Группа 10: INVOKE HURRICANE — 500 parallel invokes |
🔄 в процессе |
Лог: VM /tmp/survival.log, ожидаемое время: ~75-100 мин
2026-03-21 — Сессия 6: исправление багов оператора v0.1.49
Что сделано
| # |
Компонент |
Результат |
| 1 |
БАГ 1 исправлен: добавлен Resources в UPDATE блок ensureServiceDeployment |
✅ |
| 2 |
БАГ 2 исправлен: добавлен RequeueAfter: 60s в конец ensureServiceDeployment |
✅ |
| 3 |
go build, docker build+push v0.1.49, kubectl rollout |
✅ |
| 4 |
operator_lifecycle_test.sh после фикса |
✅ 47/47 PASS (ранее 44/44 с 2 known bugs) |
Изменения в коде
controllers/service_controller.go:
2026-03-21 — Сессия 5: lifecycle-тест оператора — 44/44 PASS; 2 бага
Что сделано
| # |
Компонент |
Результат |
| 1 |
operator_lifecycle_test.sh — 44 теста жизненного цикла |
✅ 44/44 PASS |
| 2 |
Найден БАГ 1: memory_mb через PUT не применяется в k8s |
⚠️ зафиксировано |
| 3 |
Найден БАГ 2: нет self-healing при ручном удалении Deployment |
⚠️ зафиксировано |
Структура теста (operator_lifecycle_test.sh)
| Группа |
Что тестируется |
Тестов |
| 1 (API validation) |
memory_mb=0/5000 → 400; timeout_sec=-1/901 → 400; без runtime → 400; GET/PUT/DELETE несущ. → 404 |
8 |
| 2 (Full lifecycle solo) |
create→upload→build→ready→invoke→env update→k8s verify→memory update→timeout→delete→k8s cleanup |
18 |
| 3 (Edge cases) |
DELETE while Building (kaniko убит, Deployment не создан); Reconcile (self-healing) |
10 |
| 4 (Multi parallel) |
3 функции параллельно; delete одной в Building; 2 дошли до Ready; 40 parallel invoke |
8 |
Найденные баги оператора
| # |
Баг |
Место |
Суть |
| 1 |
memory_mb через PUT не применяется |
controllers/service_controller.go:ensureServiceDeployment |
При UPDATE обновляются только Image и Env. Resources (memory limit) не трогается — k8s Deployment хранит старое значение |
| 2 |
Нет self-healing при удалении Deployment |
ServiceReconciler |
Если Deployment удалили вручную — контроллер не пересоздаёт. Нет cross-namespace watch (sless-fn-*) и нет RequeueAfter. Reconcile срабатывает только при изменении CRD |
Что НЕ упало (важно)
- DELETE во время Building → CRD удалён, kaniko Job убит, Deployment не создан ✅
- Duplicate create + Ready → 409 ✅
- env_vars через PUT → k8s Deployment обновился, rollout OK, invoke вернул новое значение ✅
- 3 параллельных build → все 3 независимы, delete одного не ломает остальные ✅
- 40 параллельных invoke → 40/40 OK ✅
- Финальный teardown → все lc-* сервисы удалены ✅
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 |
✅ задеплоен, rollout OK |
| 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 (накоплено за сессию тестирования) |
2026-03-21 — Сессия 2: тестирование API, баги, web-консоль, source для services
Что сделано
| # |
Компонент |
Версия |
Результат |
| 1 |
Полный цикл CRUD-тестирования |
— |
✅ functions / triggers / services / jobs — все операции проверены |
| 2 |
Фикс: DELETE несуществующего ресурса → 204 вместо 404 |
operator v0.1.44 |
✅ исправлено в 4 хендлерах |
| 3 |
funcs-console: объединить functions + services в единый листинг |
funcs-service v0.2.1 |
✅ сервисы отображаются с бейджем always-on |
| 4 |
Фикс: imagePullSecrets отсутствовал в funcs-service.yaml |
manif. fix |
✅ образ v0.2.1 успешно стягивается на всех нодах |
| 5 |
Фикс: /funcs/{ns}/source/{fn} → 404 для sless_service |
operator v0.1.45 + funcs-service v0.2.2 |
✅ новый endpoint /services/{name}/source |
Найденные и исправленные баги
| Баг |
Причина |
Фикс |
Коммит |
DELETE /functions/not-exists → HTTP 204 |
Все 4 Delete-хендлера возвращали 204 No Content при IsNotFound |
writeJSON(w, 404, errResp("... not found")) в functions.go / services.go / triggers.go / jobs.go |
e8d0d78 |
funcs-service pod в ImagePullBackOff после обновления образа |
deployments/k8s/funcs-service.yaml не содержал imagePullSecrets |
Добавить imagePullSecrets: [{name: sless-registry-auth}] + полный образ с pearlharbor prefix |
09b3588 |
/funcs/{ns}/source/{fn} → 404 для sless_service (pg-info, pg-table-reader, etc.) |
proxySourceGet всегда обращался к /functions/{fn}/source; у оператора не было эндпоинта /services/{name}/source |
Добавить GetServiceSource в source.go + роут в router.go; в funcs-service передавать ?kind=service |
50f2456 |
Новые ресурсы / изменения кода
| Файл |
Изменение |
internal/api/handler/functions.go + services.go + triggers.go + jobs.go |
Delete*: IsNotFound → HTTP 404 вместо 204 |
internal/api/handler/source.go |
Новый хендлер GetServiceSource — аналог GetSource для Service CRD |
internal/api/router.go |
Новый маршрут GET /v1/namespaces/{ns}/services/{name}/source |
services/funcs/main.go |
fnResponse + поля Kind/URL; новая структура svcResponse; fetchAndRender объединяет functions + services; proxySourceGet поддерживает ?kind=service |
services/funcs/index.html |
Бейджи badge-kind-service / badge-kind-function; loadSource(…, fnKind) передаёт ?kind=service; счётчик "Функций N | Сервисов N | Всего N" |
deployments/k8s/funcs-service.yaml |
Добавлен imagePullSecrets: sless-registry-auth; образ → pearlharbor…/sless-funcs-service:v0.2.2 |
deployments/k8s/operator.yaml |
Образ → pearlharbor…/sless-operator:v0.1.45 |
Результаты тестирования (ключевые)
- CREATE 3 функций (nodejs20 / python3.11 / go1.23) → 201 ✅
- UPDATE memory / timeout / env_vars → 200 ✅
- DELETE существующей → 204; DELETE несуществующей → 404 ✅ (после фикса)
- Дубликат → 409; bad input → 400; no auth → 401 ✅
- Триггеры: CREATE http+cron / PATCH enable-disable / DELETE ✅
- Services CRUD ✅; Jobs API ✅
/funcs/sless-ffd1f598c169b0ae показывает 5 ресурсов: 3 sless_service + 2 sless_function ✅
source для function → 200, для service → 200 ✅
Коммиты сессии (feat/function-service-split)
e8d0d78 fix(api): DELETE несуществующего ресурса — 404 вместо 204 (function/service/trigger/job)
683d728 feat(funcs-console): показывать sless_service вместе с функциями — единый листинг
09b3588 fix(deploy): imagePullSecrets + образ v0.2.1 в funcs-service.yaml
50f2456 fix(source): add /services/{name}/source endpoint; fix 404 for service code view in funcs-console
Задеплоено
- Оператор:
pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.45 — rollout ✅
- funcs-service:
pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-funcs-service:v0.2.2 — rollout ✅
2026-03-21 — Сессия 1: Deploy + E2E тест: sless_job self-contained (POSTGRES пример)
Что сделано
| # |
Компонент |
Версия |
Результат |
| 1 |
Operator Docker image |
v0.1.43 |
✅ собран, запушен в pearlharbor, задеплоен |
| 2 |
CRD functionjobs.sless.kube5s.ru |
перегенерирован |
✅ runtime/entrypoint/env поля добавлены |
| 3 |
Terraform provider |
v0.1.19 |
✅ собран, опубликован в S3 (terra.k8c.ru) |
| 4 |
terraform apply POSTGRES |
— |
✅ Apply complete! Resources: 4 added |
| 5 |
E2E smoke test |
— |
✅ table-writer / table-reader отвечают |
Найденные и исправленные баги при deploy
| Баг |
Файл |
Фикс |
FunctionRef в invoke.go не убрали при merge |
internal/api/handler/invoke.go |
Заменить FunctionRef на поля из fn.Spec (Runtime/Entrypoint/S3Bucket/S3Key/MemoryMB/TimeoutSec/Env) |
Нет imagePullSecrets для pearlharbor registry |
deployments/k8s/operator.yaml |
Добавить imagePullSecrets: [{name: sless-registry-auth}] |
Проект naeel не существовал в Harbor |
Harbor API |
POST /api/v2.0/projects → 201 Created |
CRD не обновлён (старые поля: только functionRef/eventJson/runId) |
config/crd/bases/ |
bin/controller-gen crd paths="./api/..." ... → kubectl apply -f config/crd/bases/ |
Контроллер запускал kaniko до загрузки кода (S3Key == "") |
controllers/functionjob_controller.go |
В startJobBuild: if fj.Spec.S3Key == "" { return RequeueAfter: 5s } |
| dev override содержал старый бинарник v0.1.18 |
/tmp/sless-provider-dev/ |
rm terraform-provider-sless_v0.1.18 |
Коммиты этой сессии (feat/function-service-split)
0d83c0e docs: убрать упоминания 192.168.1.220, исправить шаблоны SSH
6f76ecb fix(invoke): FunctionRef → поля из Function.Spec
23141e5 chore: operator image v0.1.42 — pearlharbor registry
3dfe98e fix(operator): добавить imagePullSecrets sless-registry-auth
b3559c9 chore(crd): регенерация FunctionJobSpec
3b3d510 chore(postgres): provider version 0.1.18 → 0.1.19
735958b fix(controller): ждать S3Key перед startJobBuild
c910bb8 chore: operator image v0.1.43
E2E результаты
sless_job.postgres_table_init_job → фаза Succeeded ✅
sless_service.pg_info → создан ✅
sless_service.postgres_table_reader → URL: https://sless.kube5s.ru/fn/.../pg-table-reader ✅
sless_service.postgres_table_writer → запись test-v0.1.43 → ответ HTML с таблицей ✅
Следующий шаг
Смёрджить feat/function-service-split в main.
2026-03-20 — Merge: sless_function + sless_job → единый self-contained sless_job
Цель
Убрать обязательную зависимость sless_job от sless_function. Раньше для запуска одноразового
джоба нужно было сначала создать sless_function (CRD + kaniko build), потом sless_job.
Теперь sless_job самодостаточен: содержит runtime/entrypoint/env/source_dir и сам запускает сборку.
Изменённые файлы
| Файл |
Что сделано |
api/v1alpha1/job_types.go |
Убран FunctionRef. Добавлены: Runtime/Entrypoint/S3Bucket/S3Key/MemoryMB/TimeoutSec/Env map[string]string. Фаза Building. ImageRef в status. |
api/v1alpha1/zz_generated.deepcopy.go |
DeepCopyInto для FunctionJobSpec: proper deep copy map[string]string Env |
controllers/functionjob_controller.go |
Полная переработка: убрана зависимость от Function CRD. Новые поля Builder+OperatorNamespace. Новая фаза Building (kaniko). Методы startJobBuild/checkJobBuild. |
main.go |
Передача Builder: bldr и OperatorNamespace: "sless" в FunctionJobReconciler |
internal/api/handler/jobs.go |
jobRequest/jobResponse без FunctionRef, с Runtime/Entrypoint/Env/S3Key. Новый handler UploadJobCode. |
internal/api/router.go |
Добавлен маршрут /namespaces/{ns}/jobs/{name}/upload |
terraform/provider/internal/client/client.go |
JobRequest/JobResponse без FunctionRef, с Runtime/Entrypoint/Env/ImageRef. UploadJobCode метод. Рефакторинг UploadCodeReader → uploadCodeToURL. |
terraform/provider/internal/resources/job_resource.go |
JobModel без Function, с Runtime/Entrypoint/EnvVars/SourceDir/CodeHash/ImageRef. ModifyPlan. Create с upload. Дефолт wait_timeout_sec=900. |
examples/POSTGRES/functions.tf |
Убран sless_function.postgres_sql_runner_create_table. sless_job.postgres_table_init_job теперь самодостаточен: inline source_dir/runtime/entrypoint/env_vars. |
Статус
- ✅ Go код скомпилировался (controllers, main, internal/api)
- ✅ Terraform provider: нет ошибок компилятора
- ✅ functions.tf: раскомментирован и обновлён
- ⏳ Требует: кросс-компиляция provider → deploy в кластер → тест apply
2026-03-20 — Восстановление PostgreSQL и отладка провайдера nubes
Что произошло
- Старый PG-инстанс (
teststand-pg-2) был удалён. Девопсы позднее вернули PG под именем pg-sless-demo.
- Обнаружен неверный
api_endpoint в провайдере nubes: deck-test вместо deck-api-test → 404 на всех ресурсах.
- Исправлен
nubes_endpoint в провайдере sless — та же проблема.
- Обнаружен баг платформы:
delete_user не удаляет CRD → база/пользователь зависают в кластере → повторный apply падает с "нарушена консистентность".
- Workaround: ручное удаление PG через UI +
terraform state rm + пересоздание.
Изменения в файлах
| Файл |
Что изменено |
examples/POSTGRES/main.tf |
Исправлен api_endpoint и nubes_endpoint → deck-api-test.ngcloud.ru в обоих провайдерах |
examples/POSTGRES/postgres.tf |
try()-обёртка для vault_secrets["users"]; resource_name = "pg-sless-demo" |
examples/POSTGRES/stress_destroy_apply.sh |
Новый скрипт: 5 итераций destroy+apply с логами и остановкой при ошибке |
doc/infrastructure/overview.md |
Добавлена таблица Nubes endpoints (API vs UI) |
doc/errors/log.md |
Задокументированы 3 новые ошибки |
Статус
- ✅ Провайдер nubes работает с правильным endpoint
- ✅ PG пересоздан и managed через terraform
- ⏳ Стресс-тест (stress_destroy_apply.sh) — в процессе, выявлен баг платформы
- 📋 TODO: убрать
sless_function, встроить в sless_job
2026-03-20 — Архитектурный рефакторинг: sless_function + sless_service (ветка feat/function-service-split)
Цель
Разделить единый тип sless_function на два независимых:
sless_function — oneshot (запускается один раз через Kubernetes Job, нет постоянного URL)
sless_service — long-running (постоянный Deployment, Ingress, встроенный URL без sless_trigger)
Мотивация: устранить архитектурное несоответствие — функции с постоянным HTTP URL не должны требовать отдельного sless_trigger.
Изменения в operator (Go)
| Файл |
Что сделано |
api/v1alpha1/service_types.go |
Новый CRD-тип Service с фазами Pending/Building/Ready/Failed, поле URL в статусе |
api/v1alpha1/zz_generated.deepcopy.go |
DeepCopy методы для Service/ServiceList/ServiceSpec/ServiceStatus |
controllers/service_controller.go |
Полный ServiceReconciler: kaniko build → Deployment → k8s Service → Ingress → Status.URL |
controllers/function_controller.go |
Удалены мёртвые методы: ensureDeployment, buildDeployment, ensureRegistrySecret. Function = только oneshot (Job). |
internal/api/handler/services.go |
CRUD handlers: ListServices, CreateService, GetService, UpdateService, DeleteService, UploadServiceCode |
internal/api/handler/invoke.go |
Dual-mode invoke: сначала проверяет Service CRD (proxy к Deployment), затем Function CRD (FunctionJob poll) |
internal/api/router.go |
6 новых маршрутов /namespaces/{ns}/services/... |
main.go |
Регистрация ServiceReconciler |
Изменения в Terraform-провайдере
| Файл |
Что сделано |
terraform/provider/internal/client/client.go |
ServiceRequest, ServiceResponse, CreateService/Get/Update/Delete, UploadServiceCode, WaitServiceReady |
terraform/provider/internal/resources/service_resource.go |
Новый ресурс sless_service с полями name/runtime/entrypoint/memory_mb/timeout_sec/env_vars/source_dir/url (computed) |
terraform/provider/internal/provider/provider.go |
NewServiceResource добавлен в список ресурсов |
Изменения в примерах (examples/POSTGRES/)
| Файл |
Что сделано |
resources.tf |
Разбит на два файла; содержит только комментарий-указатель |
postgres.tf |
Managed PostgreSQL ресурсы: locals, nubes_postgres, nubes_postgres_user, nubes_postgres_database |
functions.tf |
Sless ресурсы: sless_function (только postgres_sql_runner_create_table + stress-*), sless_service (pg-info, pg-table-reader, pg-table-writer), sless_job; все sless_trigger блоки удалены |
Изменения в deployments/k8s/
| Файл |
Что сделано |
operator.yaml |
Обновлён image v0.1.33 → v0.1.41 |
rbac.yaml |
Добавлен services в rules группы sless.kube5s.ru (resources + status + finalizers) |
Полная миграция terraform state
RBAC fix (2026-03-20)
ClusterRole sless-operator не содержала прав на новый CRD services.sless.kube5s.ru.
Причина: rbac.yaml создавался до введения Service CRD; controller-gen rbac не запускался перед деплоем v0.1.41.
Исправление:
После патча terraform apply завершился успешно. Файл deployments/k8s/rbac.yaml синхронизирован.
Статус задач
| # |
Задача |
Статус |
| 1 |
Создать service_types.go + DeepCopy |
✅ |
| 2 |
service_controller.go с полным lifecycle |
✅ |
| 3 |
Очистить function_controller.go от мёртвых методов |
✅ |
| 4 |
API handler services.go |
✅ |
| 5 |
Dual-mode invoke.go |
✅ |
| 6 |
router.go + 6 маршрутов |
✅ |
| 7 |
main.go + ServiceReconciler |
✅ |
| 8 |
Terraform client + service_resource.go + provider.go |
✅ |
| 9 |
Разбить resources.tf → postgres.tf + functions.tf |
✅ |
| 10 |
terraform state rm (15 ресурсов: 6 pg + 9 stress triggers) |
✅ |
| 11 |
Удалить все sless_trigger блоки из functions.tf |
✅ |
| 12 |
Новый провайдер скомпилирован на VM (v0.1.18 dev) |
✅ |
| 13 |
Build + push operator image v0.1.41 |
✅ |
| 14 |
Apply CRD Service + deploy оператора v0.1.41 |
✅ |
| 15 |
Исправить RBAC ClusterRole: добавить services.sless.kube5s.ru |
✅ |
| 16 |
terraform apply -target sless_service.* |
✅ |
| 17 |
Удалить старые Function объекты pg-info/reader/writer из k8s |
✅ |
| 18 |
Smoke test curl (pg-info, pg-table-reader, pg-table-writer) |
✅ |
| 19 |
Синхронизировать deployments/k8s/rbac.yaml с кластером |
✅ |
| 20 |
Commit + push |
✅ |
Итоговое состояние кластера (sless-ffd1f598c169b0ae)
Старые function.sless.kube5s.ru/pg-info, pg-table-reader, pg-table-writer — удалены.
2026-03-19 — Go runtime v0.1.1: pgx/v5 + stress-go-pgstorm ✅ ЗАВЕРШЕНО
Цель
Новая функция stress-go-pgstorm — Go код со 100 горутинами, которые 10 минут
долбят PostgreSQL напрямую через pgxpool. Проверяем: Go runtime под нагрузкой,
connection pool под конкурентными запросами, устойчивость кластера.
Попутно выявлены и исправлены два системных бага:
- Хардкодный таймаут 30s в invoke.go (прокси оператора)
- Отсутствие proxy-read-timeout на nginx ingress sless-operator
Задачи
| # |
Задача |
Статус |
Заметки |
| 1 |
runtimes/go1.23/go.mod — добавить pgx/v5 v5.7.2 |
✅ |
go mod tidy на remote, go.sum сгенерирован (28 строк) |
| 2 |
runtimes/go1.23/Dockerfile — go mod download кешируем deps |
✅ |
Один stage golang:1.23-alpine, COPY go.mod+go.sum перед server.go |
| 3 |
Собрать naeel/sless-runtime-go1.23:v0.1.1, запушить |
✅ |
docker build + push, sha256 verified |
| 4 |
internal/builder/context.go — тег go1.23 v0.1.0 → v0.1.1 |
✅ |
строка return "naeel/sless-runtime-go1.23:v0.1.1", nil |
| 5 |
internal/api/handler/invoke.go — динамический таймаут |
✅ |
Таймаут = Function.Spec.TimeoutSec + 5s (был хардкод 30s) |
| 6 |
Пересобрать и задеплоить оператор v0.1.40 |
✅ |
rodlout complete |
| 7 |
deployments/k8s/operator.yaml — nginx proxy-read-timeout=900s |
✅ |
kubectl annotate + манифест обновлён |
| 8 |
code/stress-go-pgstorm/handler.go — горутины + pgxpool |
✅ |
100 горутин, INSERT/COUNT/MAX, параметры workers/duration_sec/max_delay_ms |
| 9 |
resources.tf — новая функция + trigger |
✅ |
timeout_sec=700, memory_mb=256, все PG env vars |
| 10 |
terraform apply — 2 ресурса добавлено |
✅ |
apply complete: 2 added |
| 11 |
Запуск 10-минутного стресс-теста |
✅ |
workers=100, duration_sec=600, err_ops=0, ok_ops=45918, ops_per_sec=76.5 — PostgreSQL выдержал |
| 12 |
Коммит d7fda15 |
✅ |
feat: Go runtime v0.1.1 (pgx/v5)... |
Архитектура функции stress-go-pgstorm
Цепочка таймаутов (после фиксов)
| Слой |
Таймаут |
Где задаётся |
| curl --max-time |
720s |
клиент |
| nginx ingress proxy-read-timeout |
900s |
аннотация ingress sless-operator |
| operator http.Client (invoke.go) |
TimeoutSec+5s = 705s |
Function.Spec.TimeoutSec=700 |
| function runtime (Go) |
duration_sec = 600s |
параметр в JSON body |
Что было сломано до этой сессии
- invoke.go:
var httpClient = &http.Client{Timeout: 30 * time.Second} — хардкод в строке 24
- nginx ingress sless-operator: аннотации proxy-read-timeout не было → nginx дефолт 60s → 504
2026-03-19 — Tests 3-7: E2E прогон POSTGRES + 8 стресс-функций
Tests 3-7
| Тест |
Действие |
Результат |
| Test 3 |
table_rw.py: добавлен version: v2-with-hostname, host: socket.gethostname() |
✅ Terraform apply, функция пересобрана |
| Test 4 |
pg_info.js: добавлен code_version: v2-agent-test |
✅ code_hash изменился, вернул {"code_version":"v2-agent-test"} |
| Test 5 |
Удалить trigger → 404 → пересоздать → работает |
✅ |
| Test 6 |
Удалить function + trigger → 404 → пересоздать (rebuild 46s) → работает |
✅ |
| Test 7 |
Новая функция pg-stats (Python): version, total_rows → протестирована → удалена |
✅ {"version":"v1-test7","total_rows":3} |
8 стресс-функций (коммит 014b99e)
Задеплоены и прогнаны дважды через stress_test.sh (3 раунда).
После crash-тестов все 8 подов: Running, 0 restarts.
| # |
Функция |
Runtime |
Что проверяет |
Результат |
| 1 |
stress-slow |
Python |
sleep 3-N сек |
✅ {"slept_sec":3} |
| 2 |
stress-bigloop |
Python |
CPU n=2M |
✅ 0.31s |
| 3 |
stress-divzero |
Python |
ZeroDivisionError crash |
✅ pod restart → при d=7: {"result":6.0} |
| 4 |
stress-writer |
Python |
batch INSERT в PG |
✅ +3/+10 строк |
| 5 |
stress-go-fast |
Go |
factorial+fib без deps |
✅ factorial(20), fib(20) |
| 6 |
stress-go-nil |
Go |
nil pointer panic |
✅ pod restart → при crash=false: ok |
| 7 |
stress-js-async |
NodeJS |
3 PG запроса Promise.all |
✅ version/count/max_id |
| 8 |
stress-js-badenv |
NodeJS |
TypeError (pod жив, HTTP 500) |
✅ нет restart |
Итого в таблице после двух прогонов: 32 строки.
Поведение crash-функций
stress-divzero / stress-go-nil: pod restart при краше (EOF на первый запрос после падения) — нормальное k8s поведение, runtime процесс умирает целиком
stress-js-badenv: HTTP 500 без restart — NodeJS поймал TypeError внутри async handler, pod остался жив
- Это различие задокументировано: Python/Go crash = process exit, NodeJS crash = unhandled rejection в async = 500
git
- Бинарник
event-dispatcher (50MB) удалён из истории через git reset --soft HEAD~2
- Добавлен в
.gitignore
- Force push:
d879817 → 014b99e
2026-03-19 — event-trigger refactor (ветка feat/event-trigger-refactor)
| # |
Задача |
Статус |
Заметки |
| 1 |
Аудит кластера — удаление мусора |
✅ |
Удалены: event-monitor/writer/cleaner, pg-* тестовые функции, failed build jobs, orphan namespaces b1e4df2d/b794a3c4 |
| 2 |
Managed RabbitMQ через Terraform |
✅ |
terraform/RABBIT/, ns 1dbfe9da-..., host: rabbitmqk8s.1dbfe9da-...svc.cluster.local |
| 3 |
Архитектура event-trigger задокументирована |
✅ |
doc/decisions/log.md — выбран Вариант A (отдельный event-dispatcher) |
| 4 |
trigger_types.go: +TriggerTypeEvent, +Queue |
✅ |
|
| 5 |
internal/config: +RabbitMQURL |
✅ |
|
| 6 |
services/event-dispatcher/ |
✅ |
Go-сервис: dispatcher.go, watcher.go, main.go |
| 7 |
controllers/trigger_controller.go: +reconcileEvent |
✅ |
Создаёт Service, обновляет status |
| 8 |
deployments/k8s/event-dispatcher.yaml |
✅ |
ServiceAccount + ClusterRole + Deployment |
| 9 |
Образ naeel/sless-event-dispatcher:v0.1.0 |
✅ |
Собран, запушен в DockerHub |
| 10 |
RABBITMQ_URL в sless-operator-secret |
✅ |
managed rabbit (1dbfe9da namespace) |
| 11 |
event-dispatcher задеплоен в кластер |
✅ |
Running в sless namespace |
2026-03-19 — pg-table-writer HTML + bugfix invoke.go Content-Length (оператор v0.1.37)
| # |
Задача |
Статус |
Заметки |
| 1 |
Python runtime v0.1.4: text/html поддержка |
✅ |
server.py: str начинается с < → text/html; charset=utf-8; добавлен _accept в event |
| 2 |
internal/builder/context.go: python runtime → v0.1.4 |
✅ |
|
| 3 |
Образ naeel/sless-runtime-python3.11:v0.1.4 |
✅ |
Собран и запушен |
| 4 |
code/table-rw/table_rw.py |
✅ |
Комбинированный reader+writer. list_rows — JSON. add_row: GET → HTML форма, POST form → INSERT + HTML ответ. _render_page шаблон с тёмной темой |
| 5 |
examples/POSTGRES/resources.tf |
✅ |
reader: source_dir=code/table-rw, entrypoint=table_rw.list_rows. Новые: sless_function.postgres_table_writer, sless_trigger.postgres_table_writer_http, output.table_writer_url |
| 6 |
Удалена папка code/table-reader/ |
✅ |
|
| 7 |
internal/builder/builder.go: убран --cache=true |
✅ |
kaniko по умолчанию не кэширует слои |
| 8 |
Оператор v0.1.35 |
✅ (промежуточный) |
|
| 9 |
Оператор v0.1.36 |
✅ |
Убран ошибочный --no-cache (kaniko v1.24.0 не поддерживает этот флаг) |
| 10 |
Оператор v0.1.37 |
✅ |
Bugfix invoke.go: добавлен proxyReq.ContentLength = r.ContentLength. Без этого Python-сервер читал тело как 0 байт (бесконечно chunked) — форма не парсилась |
| 11 |
POST через прокси |
✅ |
curl POST title=test-proxy-fix → HTML с «Добавлено: «test-proxy-fix»», строка в таблице |
Root cause бага формы
internal/api/handler/invoke.go: при проксировании POST-запроса не устанавливался Content-Length.
Go http.Client отправлял запрос без Content-Length → Python BaseHTTPRequestHandler.headers.get("Content-Length", 0) = 0 → тело не читалось → title пустой → JSON ошибка.
Фикс: одна строка proxyReq.ContentLength = r.ContentLength.
Образы
| Образ |
Версия |
Что изменилось |
naeel/sless-operator |
v0.1.37 |
invoke.go: proxyReq.ContentLength = r.ContentLength |
naeel/sless-runtime-python3.11 |
v0.1.4 |
text/html support, _accept in event |
2026-03-18 — web-консоль (оператор v0.1.34 + funcs-service v0.2.0)
2026-03-18 — web-консоль (оператор v0.1.34 + funcs-service v0.2.0)
| # |
Задача |
Статус |
Заметки |
| 1 |
Оператор: GET /v1/.../functions/{name}/source |
✅ |
internal/api/handler/source.go — скачивает tar.gz контекст из S3; извлекает пользовательские файлы (без Dockerfile); возвращает [{name, content, binary}] |
| 2 |
Оператор: PATCH /v1/.../triggers/{name} |
✅ |
Уже существовал в v0.1.33 — Handler обрабатывает поле enabled |
| 3 |
internal/api/router.go — маршрут /source |
✅ |
GET /namespaces/{ns}/functions/{name}/source → h.GetSource |
| 4 |
services/funcs/index.html — HTML web-консоль |
✅ |
Тёмная тема, аккордеоны, highlight.js, кнопки ▶/■ для enable/disable триггеров |
| 5 |
services/funcs/main.go — HTML режим + proxy endpoints |
✅ |
Accept: text/html → HTML; GET /funcs/{ns}/source/{fn} и PATCH /funcs/{ns}/triggers/{name} — прокси к оператору через SLESS_SERVICE_TOKEN |
| 6 |
Backward compat: plain text |
✅ |
curl без Accept: text/html — старое поведение v0.1.x |
| 7 |
Оператор v0.1.34 |
✅ |
Build+push+deploy в кластер |
| 8 |
funcs-service v0.2.0 |
✅ |
Build+push+deploy в кластер |
| 9 |
Коммит |
✅ |
bf9f073 — feat: web-console — HTML UI + source viewer + trigger toggle |
Текущие URL
Образы
| Образ |
Версия |
Что изменилось |
naeel/sless-operator |
v0.1.34 |
GET /source endpoint, HTML web-консоль proxy routes |
naeel/sless-funcs-service |
v0.2.0 |
HTML консоль + source/trigger proxy endpoints |
2026-03-18 — FunctionJob bugfix (functionjob label + stderr capture)
| # |
Задача |
Статус |
Заметки |
| 1 |
functionjob_controller.go: label на PodTemplate |
✅ |
Добавлен PodTemplate.ObjectMeta.Labels: {managed-by, functionjob, function} — k8s 1.27+ удалил встроенный job-name= лейбл |
| 2 |
getJobPodOutput по labelSelector |
✅ |
Принимает "functionjob=<name>" вместо jobName; используется для успешного и упавшего job |
| 3 |
Захват stderr при ошибке Job |
✅ |
При job.Status.Failed > 0 — берём podOutput; если непустой — пишем в status.Message (truncate до 2000 символов) |
| 4 |
truncateForStatus() helper |
✅ |
Ограничивает длину status.Message для безопасной записи в k8s |
| 5 |
terraform client: ErrJobAlreadyExists |
✅ |
client.go: 409 Conflict → ErrJobAlreadyExists; job_resource.go: при конфликте создания — читаем существующий job вместо ошибки |
2026-03-18 — Python runtime str→text/plain
| # |
Задача |
Статус |
Заметки |
| 1 |
runtimes/python3.11/server.py |
✅ |
Если функция вернула str → Content-Type: text/plain, тело без json.dumps. Dict/list остаются application/json |
| 2 |
internal/builder/context.go |
✅ |
Python runtime базовый образ → v0.1.3 |
| 3 |
Образ naeel/sless-runtime-python3.11:v0.1.3 |
✅ |
Собран и задеплоен |
2026-03-18 — funcs: глобальный Go сервис + ветка web-console
| # |
Задача |
Статус |
Заметки |
| 1 |
Python runtime str→text/plain |
✅ |
runtimes/python3.11/server.py: если функция возвращает str → Content-Type: text/plain. Образ naeel/sless-runtime-python3.11:v0.1.3 |
| 2 |
funcs_list.py: plain text вывод |
✅ |
Переписан с JSON на plain text с ─ разделителями, фильтр SLESS_EXCLUDE |
| 3 |
Оператор v0.1.33 |
✅ |
context.go: python runtime → v0.1.3; internal/builder/context.go обновлён |
| 4 |
funcs как глобальный Go сервис |
✅ |
services/funcs/main.go — standalone Go HTTP сервер. Деплоится ОДИН РАЗ в namespace sless, работает для ВСЕХ пользователей. Образ naeel/sless-funcs-service:v0.1.3 |
| 5 |
URL без токена |
✅ |
https://sless.kube5s.ru/funcs/<namespace> — открывается в браузере без токена. SLESS_SERVICE_TOKEN задан через kubectl set env |
| 6 |
Удалить funcs из terraform |
✅ |
examples/POSTGRES/resources.tf: удалены sless_function.funcs_list, sless_trigger.funcs_list_http, output "funcs_url", local.user_namespace. terraform apply — 2 destroyed |
| 7 |
Ветка feat/web-console |
✅ |
Создана от feat/harbor-integration на remote, запушена |
| 8 |
Документация архитектуры хранения кода |
✅ |
См. doc/decisions/log.md раздел "Хранение кода функций и web-консоль" |
| 9 |
Коммиты |
✅ |
e8cd62e (funcs Go service), 38bb494 (v0.1.3 — /funcs/namespace без токена) |
Текущие URL функций
Образы
| Образ |
Версия |
Что изменилось |
naeel/sless-operator |
v0.1.33 |
created_at, last_built_at в API; python runtime v0.1.3 |
naeel/sless-runtime-python3.11 |
v0.1.3 |
str → text/plain в server.py |
naeel/sless-funcs-service |
v0.1.3 |
/funcs/ без токена, /health probe |
2026-03-18 — Следующие шаги: web-консоль (ветка feat/web-console)
| # |
Задача |
Статус |
Заметки |
| 1 |
Оператор: GET /v1/namespaces/{ns}/functions/{name}/source |
✅ |
Реализовано — bf9f073 |
| 2 |
Оператор: PATCH /v1/namespaces/{ns}/triggers/{name} |
✅ |
Существовал — проверено, поддерживает enabled |
| 3 |
sless-funcs-service: HTML страница |
✅ |
Accept: text/html → HTML консоль. Аккордеоны + highlight.js + кнопки ▶/■ |
| 4 |
Bump оператора до v0.1.34 |
✅ |
Задеплоен |
| 5 |
Bump funcs-service до v0.2.0 |
✅ |
Задеплоен |
2026-03-18 12:00 — DNS инцидент устранён, POSTGRES E2E PASS
2026-03-18 — DNS инцидент: sless-api.kube5s.ru → мёртвый кластер
| # |
Задача |
Статус |
Заметки |
| 1 |
Диагностика TLS handshake timeout при terraform apply POSTGRES |
✅ |
sless-api.kube5s.ru → 5.172.178.182 (старый кластер). Порт 443 принимал TCP, но TLS не завершался — Go HTTP клиент висел бесконечно |
| 2 |
Подтверждение нового кластера |
✅ |
kubectl get nodes в контексте tazetdinovn@gmail.com@naeel-test-3 → 3 ноды Ready. Ingress IP 185.247.187.147. DNS sless.kube5s.ru уже указывал на него |
| 3 |
Обновить Ingress в кластере |
✅ |
kubectl patch ingress sless-operator -n sless: host + TLS → sless.kube5s.ru, secretName → sless-operator-tls-sless |
| 4 |
Обновить ConfigMap оператора |
✅ |
EXTERNAL_URL: https://sless.kube5s.ru (было https://sless-api.kube5s.ru) |
| 5 |
Let's Encrypt сертификат |
✅ |
certificate/sless-operator-tls-sless READY=True, order valid — выдан сразу после смены ingress host (ACME HTTP-01 challenge на новом IP прошёл) |
| 6 |
Заменить sless-api.kube5s.ru → sless.kube5s.ru во всех examples + deployments |
✅ |
POSTGRES/, hello-go/, hello-node/, simple-node/, simple-python/, notes-python/, pg-list-python/, demo-managed-functions/, README.md, deployments/k8s/operator.yaml |
| 7 |
POSTGRES terraform apply с функцией и job |
✅ PASS |
sless_function pg-create-table-runner — создан за 55s. sless_job pg-create-table-job-main-v13 — создан за 20s. Apply complete! Resources: 2 added |
| 8 |
Коммит и push |
✅ |
cca3a8c — fix: migrate sless endpoint from sless-api.kube5s.ru to sless.kube5s.ru |
2026-03-17 — E2E тесты всех примеров на тест-стенде
| # |
Задача |
Статус |
Заметки |
| 1 |
Исправить nubes_endpoint во всех examples (prod→test) |
✅ |
deck-api.ngcloud.ru → deck-api-test.ngcloud.ru/api/v1 в hello-node, hello-go, simple-node, notes-python, pg-list-python, demo-managed-functions |
| 2 |
Исправить sless_endpoint в demo-managed-functions |
✅ |
185.247.187.147.nip.io → https://sless-api.kube5s.ru |
| 3 |
Postgres для тестов |
✅ |
postgres.sless.svc.cluster.local:5432, user=sless, pass=sless-pg-password, db=sless. Уже запущен в кластере |
| 4 |
hello-node E2E |
✅ PASS |
init → apply (4 ресурса) → modify (memory_mb 128→256, env_var) → apply → destroy |
| 5 |
simple-python E2E |
✅ PASS |
init → apply → modify (memory_mb 64→96) → apply → destroy. job_result = {"time": "..."} |
| 6 |
simple-node E2E |
✅ PASS |
Аналогично simple-python но nodejs20 |
| 7 |
notes-python E2E |
✅ PASS |
7 ресурсов, PostgreSQL init job: {"ok": true, "executed": 1}, destroy complete |
| 8 |
Коммит на remote |
✅ |
a768454 в /home/naeel/terra/sless/examples |
| 9 |
POSTGRES example |
🔄 |
Следующий шаг — вернуться к POSTGRES после прохождения всех тестов |
2026-03-17 — v0.1.31: compile fix + POSTGRES example end-to-end
| # |
Задача |
Статус |
Заметки |
| 1 |
Баг 3: PodLogOptions{Stdout/Stderr} compile error |
✅ |
Убраны несуществующие поля. getJobPodOutput в functionjob_controller.go |
| 2 |
Сборка naeel/sless-operator:v0.1.31 |
✅ |
Собрано на naeel@5.172.178.213, запушено в Docker Hub |
| 3 |
Деплой v0.1.31 в кластер |
✅ |
kubectl rollout status → success. Pod sless-operator-545556c59d-n8qv9 |
| 4 |
simple-python — сквозной тест на тест-стенде |
✅ |
terraform apply на remote: Apply complete! Resources: 3 added. job_result = {"time": "..."} |
| 5 |
Найдена главная причина падения POSTGRES |
✅ |
nubes_endpoint в provider "sless" указывал на прод deck-api.ngcloud.ru |
| 6 |
Исправлен nubes_endpoint в POSTGRES/main.tf |
✅ |
Теперь deck-api-test.ngcloud.ru/api/v1 |
| 7 |
Исправлен nubes_endpoint в simple-python/main.tf |
✅ |
Аналогично |
| 8 |
End-to-end terraform apply для POSTGRES |
🔄 |
Следующий шаг: синхронизировать на remote и запустить terraform apply |
2026-03-17 — POSTGRES example: внешний managed PG, FunctionJob и реальные блокеры
| # |
Задача |
Статус |
Заметки |
| 1 |
Перевод examples/POSTGRES на PGSSLMODE=require |
✅ |
Для managed PostgreSQL allow_no_s_s_l = false; disable был некорректен |
| 2 |
Подтверждение root-cause password authentication failed |
✅ |
Из runtime namespace поднят диагностический pod того же image: psycopg2.OperationalError: FATAL: password authentication failed for user "u-user0" |
| 3 |
Проверка источника пароля |
✅ |
nubes_postgres.npg.vault_secrets["users"] не совпадает с фактическим паролем пользователя в кластере managed PostgreSQL; authoritative source сейчас — k8s secret u-user0.postgresqlk8s.credentials.postgresql.acid.zalan.do |
| 4 |
Попытка читать пароль через hashicorp/external |
❌ |
В текущем окружении доступен только кастомный registry; registry.terraform.io/hashicorp/external не устанавливается |
| 5 |
One-command terraform apply для examples/POSTGRES |
🔄 |
Остаточный баг локализован в связке credential-source + FunctionJob observability. После переключения на актуальный пароль sless_job всё ещё падает, но актуальные pod logs реального job теряются из-за TTL/слабой наблюдаемости |
| 6 |
Необходимый следующий технический шаг |
🔄 |
Либо добавить в проект доступный data-source/провайдер для чтения k8s secret внутри apply, либо починить источник пароля в nubes-потоке, чтобы vault_secrets и реальный Postgres user password совпадали |
2026-03-17 — Fix: examples/POSTGRES / sless_job 409 + диагностика FunctionJob
| # |
Задача |
Статус |
Заметки |
| 1 |
Идемпотентность sless_job при 409 job already exists |
✅ |
terraform/provider/internal/client/client.go, terraform/provider/internal/resources/job_resource.go: добавлен typed-error ErrJobAlreadyExists, в Create при 409 читается существующий Job и продолжается ожидание/синхронизация вместо немедленного падения |
| 2 |
Диагностика падений FunctionJob |
✅ |
controllers/functionjob_controller.go: labels добавлены в PodTemplate, fail-message теперь включает output pod-а (обрезка до 2000), fallback-команда логов по job-name |
| 3 |
Root-cause для examples/POSTGRES |
✅ |
Рабочие examples/notes-python используют внутренний DSN (postgres.sless.svc), а examples/POSTGRES работает через managed Nubes Postgres + динамические креды; критичный UX-дефект был в повторном Create после failed apply |
Статусы: ✅ готово | 🔄 в процессе | ⏳ не начато
2026-03-10 — Архитектурный разбор и handoff для следующего агента
| # |
Задача |
Статус |
Заметки |
| 1 |
GPT-5.4: Подробный разбор кода и lifecycle issues |
✅ |
doc/architecture/agent-handoff-2026-03-10.md — детальный code review, event model problems, invocation history gap, приоритеты |
| 2 |
Claude Sonnet 4.6: Review + security roadmap |
✅ |
doc/architecture/sonnet-review-of-gpt-analysis.md — где GPT-5.4 прав, что пропустил (gVisor, LLM validation, NetworkPolicy), production security roadmap |
| 3 |
Claude Opus 4.6: Прагматичный обзор для небольшого провайдера |
✅ |
doc/architecture/opus-pragmatic-review-2026-03-10.md — анализ с учётом масштаба, конкретный план по этапам, разбор где коллеги перемудрили |
| 4 |
Claude Opus 4.6: Дизайн LLM-валидации кода |
✅ |
doc/decisions/log.md → "2026-03-10 — LLM-валидация кода при upload" — архитектура, интерфейс, prompt, план файлов |
v1 — Базовый сервис
Код
| # |
Компонент |
Статус |
Заметки |
| 1 |
Структура проекта, operator-sdk init |
✅ |
module: gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless |
| 2 |
CRD types: Function, Trigger |
✅ |
api/v1alpha1/, zz_generated.deepcopy.go |
| 3 |
internal/config |
✅ |
config.go, Load() из env vars, добавлены IngressHost + APIToken |
| 4 |
Миграции PostgreSQL |
✅ |
migrations/001_initial.sql — таблица invocations |
| 5 |
internal/storage/postgres |
✅ |
store.go: SaveInvocation, ListInvocations, RunMigrations |
| 6 |
internal/storage/s3 |
✅ |
client.go: Upload, Download, Delete, EnsureBucket (Ceph S3) |
| 7 |
internal/builder |
✅ |
builder.go: kaniko Jobs для сборки образов функций |
| 8 |
controllers/function_controller.go |
✅ |
Pending→Building(kaniko)→Ready/Failed, Deployment, finalizer; handleDeletion удаляет Service+Ingress |
| 9 |
controllers/trigger_controller.go |
✅ |
HTTP→Service+Ingress (fallback) или прокси через ExternalURL, Cron→CronJob, finalizer; handleTriggerDeletion удаляет Service+Ingress |
| 10 |
internal/api/router.go |
✅ |
gorilla/mux, /v1/namespaces/{ns}/... (auth), /fn/{ns}/{name} (публичный прокси) |
| 11 |
internal/api/handler/ |
✅ |
functions.go, triggers.go, invocations.go, invoke.go (proxy — no such host → 404), handler.go (slog) |
| 12 |
internal/api/middleware/ |
✅ |
auth.go (Bearer token), logging.go (slog) |
| 13 |
main.go |
✅ |
operator manager + HTTP сервер параллельно, wire всех зависимостей |
Кластер (namespace: sless)
| # |
Ресурс |
Статус |
Заметки |
| 1 |
namespace sless |
✅ |
создан |
| 2 |
CRD functions.sless.kube5s.ru |
✅ |
установлен из config/crd/bases/ |
| 3 |
CRD triggers.sless.kube5s.ru |
✅ |
установлен из config/crd/bases/ |
| 4 |
ServiceAccount sless-operator |
✅ |
deployments/k8s/rbac.yaml |
| 5 |
ClusterRole + ClusterRoleBinding |
✅ |
deployments/k8s/rbac.yaml |
| 6 |
PostgreSQL Deployment + Service |
✅ |
deployments/k8s/postgres.yaml, Running |
| 7 |
Secret postgres-secret |
✅ |
в namespace sless |
Runtime image + Upload endpoint (2026-03-07)
| # |
Компонент |
Статус |
Заметки |
| 1 |
runtimes/python3.11/server.py |
✅ |
HTTP wrapper :8080, loads /app/function/handler.py |
| 2 |
runtimes/python3.11/Dockerfile |
✅ |
FROM python:3.11-slim, naeel/sless-runtime-python3.11:v0.1.0 |
| 3 |
Secret sless-registry-auth |
✅ |
DockerHub creds для kaniko в namespace sless |
| 4 |
internal/api/handler/upload.go |
✅ |
POST /v1/namespaces/{ns}/functions/{name}/upload |
| 5 |
Fix: kaniko S3 endpoint |
✅ |
https:// + S3_FORCE_PATH_STYLE=true (Ceph) |
| 6 |
Fix: бесконечный цикл сборки |
✅ |
idempotency guard через аннотацию last-built-s3key |
E2E тест (2026-03-07)
| # |
Шаг |
Результат |
| 1 |
Запуск оператора локально |
✅ source hack/local.env && go run main.go |
| 2 |
Создание функции hello + upload handler.zip |
✅ kaniko собрал naeel/sless-default-hello:latest |
| 3 |
Function phase=Ready, Deployment Running |
✅ namespace sless-fn-default |
| 4 |
GET /health |
✅ {"status": "ok"} |
| 5 |
POST / {"name":"Naeel"} |
✅ {"message": "Hello, Naeel!"} |
| 6 |
terraform apply (pg-query python3.11) |
✅ {"invocations": [], "count": 0} через https://sless-api.kube5s.ru |
| 7 |
hello-node (nodejs20) E2E |
✅ |
Terraform провайдер (2026-03-07)
| # |
Компонент |
Статус |
Заметки |
| 1 |
terraform/provider/ — независимый Go-модуль |
✅ |
module: terraform-provider-sless |
| 2 |
internal/client/client.go |
✅ |
HTTP-клиент sless API, готов к переносу в nubes |
| 3 |
internal/provider/provider.go |
✅ |
паттерн идентичен nubes NubesProvider |
| 4 |
sless_function resource |
✅ |
CRUD + upload zip + WaitReady (kaniko) |
| 5 |
sless_trigger resource |
✅ |
CRUD (http/cron); Delete ждёт исчезновения CR (WaitGone, 90с) |
| 6 |
hack/build-and-publish.sh |
✅ |
GPG-подпись + mc upload в S3 |
| 7 |
Публикация в terra.k8c.ru |
✅ |
v0.1.3, key CB3A0DF161ECC416 |
| 8 |
terraform init |
✅ |
Installed terra.k8c.ru/naeel/sless v0.1.3 |
| 9 |
sless_job resource |
✅ |
одноразовые запуски функции через FunctionJob CRD |
| 10 |
build_timeout_sec / wait_timeout_sec |
✅ |
настраиваемые таймауты в sless_function и sless_job |
Примечание: Код провайдера писался с прицелом на перенос в nubes провайдер.
internal/client/ → internal/core/ nubes, ресурсы → internal/resources_gen/.
source_dir + fix destroy cleanup (2026-03-09)
| # |
Компонент |
Статус |
Заметки |
| 1 |
provider: source_dir атрибут |
✅ |
zip в памяти, SHA256 автоматически; hashicorp/archive не нужен |
| 2 |
fix: trigger_controller handleTriggerDeletion |
✅ |
удаляет Service+Ingress из sless-fn-{ns} |
| 3 |
fix: function_controller handleDeletion |
✅ |
удаляет Service+Ingress из sless-fn-{ns} |
| 4 |
fix: trigger_resource.go Delete WaitGone |
✅ |
polling GetTrigger до 404, таймаут 90с |
| 5 |
fix: invoke.go no such host → 404 |
✅ |
после destroy DNS NXDOMAIN → 404 вместо 502 |
| 6 |
E2E тест run_terraform_examples.sh |
✅ |
все 4 примера: hello-node, simple-node, simple-python, notes-python |
Что ещё не сделано
| # |
Компонент |
Статус |
Заметки |
| 1 |
terraform apply e2e — функция с postgres |
✅ |
{"invocations": [], "count": 0} — функция подключилась к postgres.sless.svc, таблица пустая |
| 2 |
HTTP trigger e2e тест |
✅ |
прокси /fn/ через sless-api, без wildcard DNS |
| 3 |
deployments/k8s/operator.yaml |
✅ |
Deployment+Service+Ingress, naeel/sless-operator:v0.1.5, Running |
| 4 |
Внешний доступ к API оператора |
✅ |
https://sless-api.kube5s.ru → HTTP/2 200, TLS cert OK |
| 5 |
DNS запись sless-api.kube5s.ru |
✅ |
A → 5.172.178.182, TTL 3600, zone kube5s.ru |
| 6 |
nodejs20 runtime |
✅ |
runtimes/nodejs20/server.js + Dockerfile, naeel/sless-runtime-nodejs20:v0.1.0 |
| 7 |
upload.go: nodejs20 + package.json |
✅ |
runtimeBaseImage switch, hasPackageJSON → npm install |
| 8 |
go.mod: direct/indirect fix |
✅ |
go mod tidy — gorilla/mux, lib/pq, minio-go, k8s.io/api помечены direct |
| 9 |
Runtime образы: версионированные теги |
✅ |
python3.11:v0.1.0, nodejs20:v0.1.0 (убираем :latest) |
v2 — Расширения
| # |
Компонент |
Статус |
Заметки |
| 1 |
RabbitMQ event triggers |
⏳ |
|
| 0 |
Изоляция пользователей по namespace |
⏳ |
Каждый токен → свой k8s namespace. API: GET /v1/whoami → {namespace}. Провайдер получает ns при Configure(), юзер не видит. Сейчас: хардкод "default" для демо. |
| 2 |
Облачный auth (nubes.ru token validation) |
⏳ |
v1 — статический токен |
| 3 |
Метрики → Victoria Metrics |
⏳ |
|
| 4 |
Managed PostgreSQL от провайдера |
⏳ |
сейчас postgres в k8s |
| 5 |
Pre-warm для cron триггеров |
⏳ |
TriggerSpec.PreWarmSeconds — поле есть, логика не реализована |
| 11 |
trigger.enabled |
✅ |
enabled=false → Deployment replicas=0, in-place update через PATCH |
| 12 |
job.run_id |
✅ |
run_id=0 → skip, run_id>0 → execute. Повторный запуск = увеличить run_id |
2026-03-11 — Namespace-per-user + SoC рефакторинг
| # |
Задача |
Статус |
Заметки |
| 1 |
JWT decode в провайдере (SubFromJWT, NamespaceFromSub) |
✅ |
terraform/provider/internal/client/client.go |
| 2 |
PingNubesAPI в provider.Configure() |
✅ |
атрибут nubes_endpoint, env NUBES_ENDPOINT |
| 3 |
JWT auth в операторе (validateJWT vs статический токен) |
✅ |
internal/api/middleware/auth.go — sub + exp, без подписи |
| 4 |
EnsureNamespace как отдельный endpoint |
✅ |
POST /v1/namespaces/{ns}/ensure, handler/namespace.go |
| 5 |
router.go: маршрут /ensure |
✅ |
добавлен перед Functions CRUD |
| 6 |
client.go провайдера: метод EnsureNamespace |
✅ |
terraform/provider/internal/client/client.go |
| 7 |
provider.Configure(): вызов EnsureNamespace |
✅ |
namespace создаётся один раз при init |
| 8 |
handler.go: убраны k8s-типы (SoC) |
✅ |
только Handler struct + helpers |
| 9 |
secrets/ исключены из git (.gitignore) |
✅ |
токены не попадают в репу |
| 10 |
E2E тест: apply + destroy |
✅ |
namespace sless-cdd874dfa31ba6ca, все 4 ресурса |
| 11 |
operator v0.1.21 задеплоен |
✅ |
naeel/sless-operator:v0.1.21 |
| 12 |
provider v0.1.13 опубликован |
✅ |
terra.k8c.ru/naeel/sless v0.1.13 |
| 13 |
commit + push feat/namespace-per-user |
✅ |
a1774e1 |
Текущая ветка: feat/namespace-per-user
Последний коммит: a1774e1
2026-03-11 — Opus review: fixes + Builder SoC + unit tests (v0.1.22)
| # |
Задача |
Статус |
Заметки |
| 1 |
CronJob перемещён в deployNS (sless-fn-{userNS}) |
✅ |
NetworkPolicy-ready |
| 2 |
curl:8.5.0 (pin вместо :latest) |
✅ |
стабильный образ |
| 3 |
sort env vars в buildDeployment |
✅ |
нет лишних rollout'ов при reconcile |
| 4 |
cleanup kaniko Job в handleDeletion |
✅ |
при удалении Function во время Building |
| 5 |
hop-by-hop headers отфильтрованы в /fn/ прокси |
✅ |
RFC 2616 §13.5.1, 8 заголовков |
| 6 |
SLESS_API_TOKEN — убран required |
✅ |
был dead code (auth через JWT) |
| 7 |
Builder SoC: internal/builder/context.go |
✅ |
PrepareContext публичный API, upload.go 200→60 LOC |
| 8 |
JWKS insertion point stub в auth.go |
✅ |
verifySignature() заготовка для v2 |
| 9 |
Unit tests: 9 тестов, все PASS |
✅ |
controllers×2, handler×3, builder×4 |
| 10 |
operator v0.1.22 задеплоен |
✅ |
kubectl rollout status — complete |
| 11 |
Коммиты e761439 + 18f25e7 |
✅ |
ветка feat/namespace-per-user |
Последний коммит: 18f25e7
2026-03-11 — operator v0.1.23: bug fixes + полный stress test PASS
Исправленные баги
| # |
Компонент |
Баг |
Исправление |
| 1 |
run_stress_test.sh |
mod2 для simple-node/python: patch_memory time-getter.tf 64→96 — time-getter.tf начинался с 96, не с 64 → "No changes" → assertion FAIL |
Изменено на 96→128 |
| 2 |
internal/builder/builder.go |
BackoffLimit=0 — при транзиентном сбое kaniko (OOM, network blip) сборка сразу считалась провалившейся без retry |
BackoffLimit=2 |
| 3 |
internal/api/handler/functions.go |
При BUILD FAIL: Function оставалась в API в статусе Failed, terraform её не добавлял в state → следующий apply → 409 "function already exists" → orphaned resource |
На 409+phase=Failed: удаляем + пересоздаём |
Результат стресс-теста (operator v0.1.23)
| Пример |
Результат |
Время |
| hello-node |
PASS |
165s |
| simple-node |
PASS |
250s |
| simple-python |
PASS |
222s |
| notes-python |
PASS |
112s |
| ИТОГО |
4/4 = 100% |
749s |
Коммит: 59563eb
Схема запуска: агент запускает run_stress_test.sh на удалённом сервере 5.172.178.213 через SSH, получает логи из /tmp/stress_test_run2.log
2026-03-11 — E2E тесты через скрипт run_e2e_tests.sh
| Пример |
init |
apply |
modify + apply |
destroy |
Итог |
| hello-node |
✅ |
✅ |
✅ memory 128→256 MB |
✅ 4 destroyed |
PASS |
| simple-node |
✅ |
✅ |
✅ memory 64→96 MB |
✅ 4 destroyed |
PASS |
Скрипт: run_e2e_tests.sh в корне репы
Возможности: retry 3× при TLS timeout, emergency destroy trap, логи в .e2e-logs/
Провайдер в примерах: токен через var.token + terraform.tfvars (gitignored), version ~> 0.1.13
Наблюдения:
- Периодические TLS handshake timeout на
sless-api.kube5s.ru — сеть нестабильна, retry помогает
terra.k8c.ru иногда даёт unexpected EOF при скачивании провайдера — то же, retry
- Namespace
sless-cdd874dfa31ba6ca жив после всех destroy — поведение корректное
Остаток технического долга (не блокирует)
| # |
Что |
Приоритет |
| 1 |
ensureRegistrySecret в FunctionReconciler — cross-namespace инфра операция |
Низкий |
| 2 |
invocations.go — 501 stub, PG подключён но endpoint не реализован |
Средний |
| 3 |
LLM-валидация кода при upload |
Средний (решение задизайнено в decisions/log.md) |
| 4 |
JWKS подпись (verifySignature) — stub есть, логика не реализована |
Средний (ждёт JWKS endpoint от nubes) |
| 5 |
Scale-to-zero через KEDA HTTP Add-on |
Низкий (v2) |
| 6 |
replicas field в FunctionSpec |
Низкий (v1.1) |
| 7 |
RabbitMQ event triggers |
Низкий (v2) |
| 8 |
Метрики → Victoria Metrics |
Низкий |
2026-03-11 — Harbor integration + Go 1.23 runtime (v0.1.24–v0.1.26)
Что сделано
| # |
Компонент |
Изменение |
| 1 |
internal/harbor/ |
Harbor-клиент: EnsureProject создаёт проект перед push образа |
| 2 |
internal/builder/builder.go |
Поддержка HarborClient, push в pearlharbor.registryk8s.services.ngcloud.ru |
| 3 |
runtimes/go1.23/ |
Новый Go 1.23 runtime: server.go + runner.go + Dockerfile |
| 4 |
internal/builder/context.go |
Go 1.23 в switch; COPY . /app/handler/ вместо COPY handler/ |
| 5 |
api/v1alpha1/function_types.go |
go1.23 в enum поддерживаемых рантаймов |
| 6 |
terraform/provider/ |
v0.1.14: go1.23 в stringvalidator.OneOf |
| 7 |
deployments/k8s/operator.yaml |
v0.1.26 |
| 8 |
examples/hello-go/ |
Новый пример: HTTP + job на Go 1.23 |
| 9 |
examples/hello-go/code/greeting.go |
Переименован из handler.go, функция buildGreeting(guestName) |
| 10 |
examples/pg-list-python/ |
Новый пример: Python + PostgreSQL, только HTTP, без job |
Docker Hub auth на удалённой машине
- Токен:
dckr_pat_aElN35tdXMBrtOAPraV5Fi0o58s (сохранён в secrets/dockerhub.env, chmod 600)
docker login -u naeel --password-stdin выполнен на remote machine
Исправленные баги
- kaniko
COPY handler/ /app/handler/ — файлы в tar-контексте без prefix → исправлено на COPY .
Decimal из psycopg2 не сериализуется → float(row[2]) в catalog.py
- Formatter добавил дублирующий
package code в greeting.go — удалён вручную
- CRD в кластере имел только
go1.21 в enum → kubectl apply обновлён CRD
- Provider v0.1.13 имел только
go1.21 → пересобран v0.1.14
Git HEAD
2026-03-11 — UX улучшения: build logs + JSON для всех HTTP методов (v0.1.27–v0.1.28)
Проблемы до исправления
| Проблема |
Симптом |
| Ошибка сборки без деталей |
terraform apply показывал только function build failed: build job failed без причины |
| HTML 501 при DELETE/HEAD/PUT |
Python runtime http.server не обрабатывал эти методы → HTML-ответ вместо JSON |
Что изменили
controllers/function_controller.go
FunctionReconciler получил поле KubeClient kubernetes.Interface
- Добавлен
getBuildPodLogs(ctx, kube, namespace, jobName) — читает логи kaniko-пода (последние 50 строк)
- При сбое сборки (
case "failed") вызывает getBuildPodLogs и включает вывод в Function.Status.Message
- Провайдер пробрасывает
Message в ошибку terraform apply — разработчик видит точную причину
runtimes/python3.11/server.py
- Выделен
_handle_with_body() — общий обработчик для методов с телом
do_PUT, do_DELETE, do_PATCH = _handle_with_body — вызывают функцию пользователя
do_HEAD — отдаёт заголовки без тела (HTTP-стандарт; тело вычисляется но не отправляется)
send_error() переопределён → JSON {"error": "...", "code": N} вместо HTML 501
- Runtime пересобран:
naeel/sless-runtime-python3.11:v0.1.2
main.go
KubeClient: kubernetes.NewForConfigOrDie(mgr.GetConfig()) передаётся в FunctionReconciler
internal/builder/context.go
- python3.11 runtime:
v0.1.1 → v0.1.2
Версии после исправления
- Оператор:
naeel/sless-operator:v0.1.28
- Python runtime:
naeel/sless-runtime-python3.11:v0.1.2
- Provider:
terra.k8c.ru/naeel/sless v0.1.14 (без изменений)
Результат тестирования
| Тест |
До |
После |
DELETE /fn/... |
HTML 501 |
✅ JSON 200, функция вызывается |
HEAD /fn/... |
HTML 501 |
✅ HTTP 200, Content-Type: application/json |
PUT /fn/... |
HTML 501 |
✅ JSON 200, функция вызывается |
Невалидный requirements.txt |
build job failed |
✅ Полный вывод pip включая строку с ошибкой |
Git коммиты
2026-03-22 — Тестовая сессия: G13 / G14 / G15 + fix v0.1.51
Цель
Написать и прогнать три группы тестов, закрывающих:
- G13: пользовательские ошибки и граничные случаи (40 тестов)
- G14: кластерный хаос — удаление ресурсов, kill pods, OOM, kaniko interrupt (20 тестов)
- G15: комбинированный — хаос + пользовательские ошибки одновременно (21 тест)
Исходное состояние
- Оператор: v0.1.50, 45/45 failure test (G12)
- Известные баги: исправлены
CreateService IsInvalid→400 и SLESS_ENTRYPOINT в Deployment
Проведённые работы
1. Написан operator_edge_cases_test.sh (G13)
6 секций:
- 13A Name validation (uppercase, пробелы, underscore, slash, длина)
- 13B Boundary values (timeout_sec 0/-1/900/901, memory_mb limits)
- 13C Lifecycle edge cases (GET/DELETE/PUT 404, 409 duplicate, upload 404)
- 13D State transitions (invoke during build, re-upload Ready, upload Failed → restart)
- 13E Update validation (PUT invalid runtime/entrypoint/memory, PUT ruby3.0)
- 13F Upload edge cases (wrong field, empty body, nested handler.py, 40MB)
Первый прогон: 36P / 4F
Найденные баги и исправления:
| # |
Проблема |
Тип |
Решение |
| G13E-5 |
PUT ruby3.0 → 500 |
БАГ кода |
UpdateService + IsInvalid→400 |
| G13F-2 |
upload empty body → 404 |
Баг теста |
добавить -X POST в curl |
| G13D-3 |
GET /fn/ → FAIL |
By design |
тест обновлён (all methods allowed) |
| G13F-4 |
40MB → 413 |
Баг теста |
принять 413 (nginx limit) |
2. Исправлен internal/api/handler/services.go (UpdateService)
Добавлена обработка errors.IsInvalid → 400 Bad Request в UpdateService.
3. Собран и задеплоен v0.1.51
4. G13 перезапущен → 40/40 PASS ✅
5. Написан operator_chaos_test.sh (G14)
5 секций:
- 14A Self-healing: удалить Deployment/Service → оператор пересоздаёт за 60s
- 14B Pod kill resilience:
kubectl delete pod → ReplicaSet поднимает новый
- 14C OOM Kill:
memory_mb=32 + функция выделяет 300MB → OOMKill (фаза остаётся Ready — Gap)
- 14D Kaniko interrupt: убить kaniko Job →
builder.IsNotFound → "failed" → phase=Failed
- 14E Operator restart: kill operator pod → reconcile восстанавливает все сервисы
Прогон: 20/20 PASS ✅
6. Написан operator_combined_test.sh (G15)
5 секций:
- 15A CRUD under chaos (DELETE Deployment → API отвечает, оператор восстанавливает)
- 15B Upload during self-heal (загрузка кода пока оператор восстанавливает ресурсы)
- 15C Concurrent creates (3 параллельных POST → 1×201, 2×409)
- 15D API errors after restart (ошибочные запросы сразу после kill operator pod)
- 15E Rapid lifecycle (create→upload→delete→create→upload за ~60s)
Первый прогон: 15P / 6F
Баги тестов (не кода):
| # |
Проблема |
Решение |
| G15C-2 |
GET /services возвращает [] не {"items":[]} |
тест исправлен |
| G15D |
kill operator → 503 (API = operator pod) |
тест принимает 503 как transient |
| G15E-3 |
DELETE → 204, тест ждал 200 |
тест принимает 204 |
G15 перезапущен → 21/21 PASS ✅
Итоговые результаты
| Группа |
Тесты |
Результат |
Файл |
| G12 Failure |
45 |
✅ 45/45 |
run_e2e_tests.sh |
| G13 Edge Cases |
40 |
✅ 40/40 |
operator_edge_cases_test.sh |
| G14 Cluster Chaos |
20 |
✅ 20/20 |
operator_chaos_test.sh |
| G15 Combined |
21 |
✅ 21/21 |
operator_combined_test.sh |
| ИТОГО |
126 |
✅ 126/126 |
|
Gap'ы (не баги, но отмечены в тестах)
| # |
Описание |
Где задокументировано |
| 1 |
OOM Kill не меняет phase → наблюдаемость ухудшена |
G14C NOTE |
| 2 |
handler.py в подпапке zip принимается при upload, ошибка только при запуске контейнера |
G13F-3 NOTE |
| 3 |
operator pod = API server → 503 при restart (нет HA) |
G15D NOTE |
| 4 |
nginx client_max_body_size ограничивает upload → 413 (не настроено явно) |
G13F-4 NOTE |
Версия оператора
v0.1.51 — задеплоен, работает