21 KiB
21 KiB
Прогресс разработки
Последнее обновление: 2026-03-10 (добавлен прагматичный обзор Claude Opus 4.6)
Статусы: ✅ готово | 🔄 в процессе | ⏳ не начато
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 на удалённом сервере 192.168.1.220 через 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 на 192.168.1.220
- Токен:
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
d6a2e22 fix: восстановлен requirements.txt после теста
5c6a373 docs: переработан examples/README.md + комментарии function.tf
2ca3137 feat: пример pg-list-python
c4559dd fix: go1.23 build context
78d11ae refactor: rename handler.go→greeting.go
a709b38 feat: go1.23 runtime support
babd8e6 feat: harbor integration
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 коммиты
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