# Прогресс разработки Последнее обновление: 2026-03-18 22:00 (web-консоль v0.2.0 задеплоена, ветка feat/web-console) ## 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 ``` https://sless.kube5s.ru/funcs/sless-ffd1f598c169b0ae # браузер → HTML консоль # curl → plain text (backward compat) https://sless.kube5s.ru/funcs/sless-ffd1f598c169b0ae/source/pg-info # JSON файлы функции https://sless.kube5s.ru/fn/sless-ffd1f598c169b0ae/pg-info # вызов функции ``` ### Образы | Образ | Версия | Что изменилось | |-------|--------|----------------| | `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="` вместо `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/` — открывается в браузере без токена. `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 функций ``` https://sless.kube5s.ru/funcs/sless-ffd1f598c169b0ae # список функций (без токена) https://sless.kube5s.ru/funcs?token= # то же, через токен https://sless.kube5s.ru/fn/sless-ffd1f598c169b0ae/pg-info https://sless.kube5s.ru/fn/sless-ffd1f598c169b0ae/pg-table-reader ``` ### Образы | Образ | Версия | Что изменилось | |-------|--------|----------------| | `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 | ✅ | `{"message": "Hello, Naeel! (nodejs20)"}` via https://sless-api.kube5s.ru/fn/default/hello-node | ### 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 ```