Files
sless/doc/architecture/agent-handoff-2026-03-18.md

18 KiB
Raw Permalink Blame History

Agent Handoff — 2026-03-18 (финальное состояние сессии)

Этот файл — полный срез для нового агента: что сделано, как устроено, как работать.


1. Идентификация проекта

  • Репозиторий: gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless
  • Локальная копия: /home/naeel/remote_dev/sless/
  • Remote server: naeel@5.172.178.213 (workspace: ~/terra/sless/)
  • SSH ключ: /home/naeel/.ssh/naeel_vm_id_ed25519
  • SSH команда: ssh -i <ключ> -o StrictHostKeyChecking=no naeel@5.172.178.213
  • Активная ветка: feat/web-console (последний коммит a04dfb2)
  • Git origin: https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless.git

Важно про git

Git не работает локально (зависает при записи объектов из-за NFS-подобного поведения volume). Все git add / commit / pushтолько через SSH на remote machine. scp для копирования файлов → ssh для git-операций.


2. Что такое проект

Managed Serverless Functions Service для облачного провайдера nubes.ru.

Пользователь пишет main.tf с ресурсами sless_function, sless_trigger, sless_job.
Terraform провайдер собирает zip → загружает в оператор → оператор запускает kaniko → Docker образ → Deployment в k8s.

Внешнее API оператора: https://sless.kube5s.ru
Ingress IP: 185.247.187.147
Namespace оператора в k8s: sless


3. Стек и компоненты

Компонент Технология Namespace / где
Operator (API + Controllers) Go (controller-runtime) k8s namespace sless
funcs-service (web-консоль) Go (net/http) k8s namespace sless
PostgreSQL PostgreSQL 16 k8s namespace sless
S3 Ceph (облачный) s3.msk-1.ngcloud.ru
Container Registry DockerHub (naeel/) внешний
Builder kaniko Job namespace пользователя
Function (HTTP trigger) k8s Deployment + Service namespace пользователя
Function (one-shot) k8s Job namespace пользователя
Function (cron) k8s CronJob namespace пользователя
Terraform Provider Go (plugin-framework v6) localhost/CI

4. Текущие версии образов

Образ Версия Что внутри
naeel/sless-operator v0.1.34 REST API + k8s controllers; GET /source; proxy-готовый PATCH /triggers
naeel/sless-funcs-service v0.2.0 HTML web-консоль + plain text (backward compat)
naeel/sless-runtime-python3.11 v0.1.3 str return → text/plain
naeel/sless-runtime-nodejs20 v0.1.2 без изменений
naeel/sless-runtime-go1.23 v0.1.0 без изменений

5. Структура директорий (актуальная)

sless/
├── main.go                          # точка входа оператора (controller-runtime + HTTP сервер)
├── Dockerfile                       # сборка оператора
├── go.mod                           # module: gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless
├── api/v1alpha1/                    # CRD типы: Function, FunctionJob, Trigger
│   ├── function_types.go
│   ├── job_types.go
│   └── trigger_types.go
├── controllers/                     # k8s reconcilers
│   ├── function_controller.go       # Function CRD → kaniko → Deployment/Service
│   ├── functionjob_controller.go    # FunctionJob CRD → k8s Job → собирает stdout/stderr
│   └── trigger_controller.go        # Trigger CRD → Deployment scale / CronJob
├── internal/
│   ├── api/
│   │   ├── router.go                # gorilla/mux: все REST маршруты (актуальный)
│   │   ├── middleware/              # Auth (JWT→namespace), Logging
│   │   └── handler/
│   │       ├── handler.go           # Handler struct (K8s, S3, PG, Log)
│   │       ├── functions.go         # CRUD Functions
│   │       ├── triggers.go          # CRUD Triggers + UpdateTrigger (PATCH enabled)
│   │       ├── upload.go            # POST /upload — zip → tar.gz → S3 → Function CRD patch
│   │       ├── source.go            # GET /source — tar.gz из S3 → JSON файлы (НОВЫЙ)
│   │       ├── jobs.go              # CRUD FunctionJobs
│   │       ├── invocations.go       # logs из Postgres
│   │       ├── invoke.go            # прокси вызова HTTP функций
│   │       └── namespace.go         # EnsureNamespace
│   ├── builder/
│   │   └── context.go               # zip + runtime → tar.gz + Dockerfile для kaniko
│   ├── storage/
│   │   ├── s3/client.go             # minio-go: Upload, UploadContext, Download, Delete
│   │   └── postgres/                # хранение invocation logs
│   └── config/                      # env vars конфиг
├── services/
│   └── funcs/
│       ├── main.go                  # web-консоль сервис (v0.2.0)
│       ├── index.html               # HTML шаблон (embed)
│       ├── Dockerfile               # multi-stage Go → alpine
│       └── funcs-service.yaml       # (дубль, не деплоится отсюда)
├── deployments/k8s/
│   ├── operator.yaml                # ConfigMap + Secret + Deployment + Service + Ingress оператора
│   ├── funcs-service.yaml           # Deployment + Service + Ingress funcs-service
│   ├── postgres.yaml                # PostgreSQL
│   └── rbac.yaml                    # ClusterRole для оператора
├── terraform/provider/              # terraform-provider-sless
│   ├── main.go
│   └── internal/
│       ├── client/client.go         # HTTP клиент к оператору
│       └── resources/
│           ├── function_resource.go # sless_function: source_dir→zip, code_hash, ModifyPlan
│           ├── trigger_resource.go  # sless_trigger
│           └── job_resource.go      # sless_job + ErrJobAlreadyExists handling
├── runtimes/
│   ├── python3.11/server.py         # HTTP wrapper (str → text/plain)
│   ├── nodejs20/                    # HTTP wrapper
│   └── go1.23/                      # multi-stage builder образ
├── examples/
│   ├── POSTGRES/                    # pg функции: create-table, pg-info, pg-table-reader
│   ├── hello-go/
│   ├── hello-node/
│   └── ...
├── migrations/001_initial.sql       # PostgreSQL схема
└── doc/                             # ← ты здесь
    ├── architecture/
    │   └── agent-handoff-2026-03-18.md  ← ЭТОТ ФАЙЛ
    ├── api/design.md
    ├── decisions/log.md
    ├── errors/log.md
    └── progress.md

6. REST API оператора — полный список маршрутов

Все /v1/ защищены JWT (middleware.Auth проверяет Bearer токен + namespace). /fn/ — публичный прокси для HTTP-триггеров (без auth).

POST   /v1/namespaces/{ns}/ensure                         # создать namespace (идемпотентно)

GET    /v1/namespaces/{ns}/functions                      # список функций
POST   /v1/namespaces/{ns}/functions                      # создать функцию
GET    /v1/namespaces/{ns}/functions/{name}               # получить функцию
PUT    /v1/namespaces/{ns}/functions/{name}               # обновить функцию
DELETE /v1/namespaces/{ns}/functions/{name}               # удалить функцию
POST   /v1/namespaces/{ns}/functions/{name}/upload        # загрузить zip → S3 → kaniko
GET    /v1/namespaces/{ns}/functions/{name}/source        # НОВЫЙ: файлы кода из S3 (JSON)
GET    /v1/namespaces/{ns}/functions/{name}/invocations   # логи вызовов

GET    /v1/namespaces/{ns}/triggers                       # список триггеров
POST   /v1/namespaces/{ns}/triggers                       # создать триггер
GET    /v1/namespaces/{ns}/triggers/{name}                # получить триггер
PATCH  /v1/namespaces/{ns}/triggers/{name}                # enable/disable: {"enabled": bool}
DELETE /v1/namespaces/{ns}/triggers/{name}                # удалить триггер

POST   /v1/namespaces/{ns}/jobs                           # создать FunctionJob
GET    /v1/namespaces/{ns}/jobs/{name}                    # получить FunctionJob
DELETE /v1/namespaces/{ns}/jobs/{name}                    # удалить FunctionJob

GET/POST/... /fn/{namespace}/{name}[/...]                 # вызов HTTP функции (без auth)

7. funcs-service — маршруты

Сервис sless-funcs-service, namespace sless, порт 8090.
Ingress: sless.kube5s.ru/funcssless-funcs-service:8090.

GET    /health                                 # liveness/readiness probe (без auth)
GET    /funcs                                  # usage hint (нет токена → 401 с подсказкой)
GET    /funcs?token=<jwt>                      # листинг через JWT
GET    /funcs/<namespace>                      # листинг по namespace (браузер→HTML, curl→plain text)
GET    /funcs/<namespace>/source/<fn>          # прокси → оператор GET /source (serviceToken)
PATCH  /funcs/<namespace>/triggers/<name>      # прокси → оператор PATCH /triggers (serviceToken)

Логика переключения HTML/plain text: strings.Contains(Accept header, "text/html").
Браузер всегда шлёт text/html в Accept → HTML консоль.
curl без -H "Accept: text/html" → plain text (совместимость с v0.1.x).

Env vars funcs-service:

SLESS_OPERATOR_URL  = http://sless-operator.sless.svc.cluster.local:9090
SLESS_EXTERNAL_URL  = https://sless.kube5s.ru
SLESS_EXCLUDE       = (список функций скрытых из листинга, через запятую)
SLESS_SERVICE_TOKEN = <JWT> (задаётся через kubectl set env, НЕ в git)
PORT                = 8090

8. Namespace пользователя

JWT.sub (UUID)  →  SHA256(sub)[:8]  →  hex  →  "sless-" + 16 hex символов

Пример: sub 019cc268-6c6a-781e-8613-4bed4ec7cd20 → namespace sless-ffd1f598c169b0ae

Эта логика одинакова в трёх местах:

  • internal/api/middleware/auth.go (оператор)
  • services/funcs/main.go (funcs-service)
  • terraform/provider/internal/client/client.go (провайдер)

9. S3 хранение кода — ключи

При POST /upload создаются два объекта:

functions/{ns}/{name}/{timestamp}.zip     ← исходный код (zip от пользователя)
contexts/{ns}/{name}/{timestamp}.tar.gz   ← build context для kaniko (zip + Dockerfile)

Function.Spec.S3Key хранит путь к contexts/....
GET /source читает Function.Spec.S3Key, скачивает tar.gz, извлекает файлы без Dockerfile.

Важно: zip исходника (functions/...) отдельно не хранится в CRD.
Код источника берётся из tar.gz контекста — там те же файлы пользователя.


10. Terraform провайдер — sless_function

resource "sless_function" "my_func" {
  name       = "my-func"
  runtime    = "python3.11"          # python3.11 | nodejs20 | go1.23
  entrypoint = "handler.handle"

  source_dir = "${path.module}/code/my-func"  # директория → провайдер делает zip сам
  # ИЛИ:
  # code_path = "./handler.zip"     # готовый zip
  # code_hash = filesha256("./handler.zip")  # для детекции изменений

  memory_mb   = 128
  timeout_sec = 30
  env_vars    = { KEY = "value" }
  build_timeout_sec = 300  # ожидание kaniko (дефолт 300 сек)
}

При изменении файлов в source_dir:

  • terraform plan → пересчитывает code_hash (ModifyPlan), показывает diff
  • terraform apply → загружает новый zip, ждёт сборки → phase = Ready
  • Новый код виден в браузере сразу после apply

11. Где что запущено (kubectl)

# Проверить поды оператора
kubectl get pods -n sless

# Проверить версию образа оператора
kubectl get deployment sless-operator -n sless -o jsonpath='{.spec.template.spec.containers[0].image}'

# Посмотреть логи оператора
kubectl logs -n sless -l app=sless-operator --tail=50

# Посмотреть логи funcs-service
kubectl logs -n sless -l app=sless-funcs-service --tail=50

# Обновить образ оператора
kubectl set image deployment/sless-operator operator=naeel/sless-operator:vX.X.X -n sless
kubectl rollout status deployment/sless-operator -n sless --timeout=90s

# Обновить образ funcs-service
kubectl set image deployment/sless-funcs-service funcs=naeel/sless-funcs-service:vX.X.X -n sless

# Посмотреть функции пользователя
kubectl get functions -n sless-ffd1f598c169b0ae

# Посмотреть триггеры
kubectl get triggers -n sless-ffd1f598c169b0ae

# Обновить SLESS_SERVICE_TOKEN (не хранится в git!)
kubectl set env deployment/sless-funcs-service -n sless SLESS_SERVICE_TOKEN=<jwt>

12. Тестовые данные

  • Тестовый токен: /home/naeel/remote_dev/sless/secrets/test.token
  • Namespace тест-пользователя: sless-ffd1f598c169b0ae
  • Рабочие функции:
    • pg-info (nodejs20) — читает версию PostgreSQL и счётчик строк
    • pg-table-reader (python3.11) — читает строки из таблицы
    • pg-create-table-runner (python3.11) — job: создаёт таблицу
  • Быстрый тест:
    # plain text список (curl)
    curl https://sless.kube5s.ru/funcs/sless-ffd1f598c169b0ae
    
    # HTML консоль (браузер или curl с Accept)
    curl -H "Accept: text/html" https://sless.kube5s.ru/funcs/sless-ffd1f598c169b0ae
    
    # исходный код функции
    curl https://sless.kube5s.ru/funcs/sless-ffd1f598c169b0ae/source/pg-info
    
    # вызов функции
    curl https://sless.kube5s.ru/fn/sless-ffd1f598c169b0ae/pg-info
    

13. Деплой нового образа — стандартный workflow

SSH="ssh -i /home/naeel/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no naeel@5.172.178.213"
SCP="scp -i /home/naeel/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no"

# 1. Скопировать изменённые файлы оператора на remote
$SCP /home/naeel/remote_dev/sless/path/to/file.go naeel@5.172.178.213:~/terra/sless/path/to/file.go

# 2. Собрать образ на remote
$SSH "cd ~/terra/sless && docker build -t naeel/sless-operator:vX.X.X . 2>&1 | tail -5"

# 3. Запушить
$SSH "docker push naeel/sless-operator:vX.X.X 2>&1 | tail -3"

# 4. Задеплоить
$SSH "kubectl set image deployment/sless-operator operator=naeel/sless-operator:vX.X.X -n sless && kubectl rollout status deployment/sless-operator -n sless --timeout=90s"

# 5. Коммит (через SSH! не локально)
$SSH "cd ~/terra/sless && git add <файлы> && git commit -m 'msg' && git push origin feat/web-console"

Для funcs-service: путь ~/terra/sless/services/funcs/, контейнер называется funcs.


14. Известные проблемы / особенности

Проблема Решение
git зависает локально Только через SSH на remote machine
SLESS_SERVICE_TOKEN не в git Задан через kubectl set env, при пересоздании пода — пропадёт! Нужно переставить вручную
job-name= label удалён в k8s 1.27+ Используем свой label functionjob=<name> на PodTemplate (исправлено в v0.1.34)
S3 endpoint с SSL useSSL=false для внутреннего s3, useSSL=true для облачного s3.msk-1.ngcloud.ru
python3.11: str return → text/plain Начиная с naeel/sless-runtime-python3.11:v0.1.3

15. Следующие возможные задачи (не начаты)

Задача Сложность Заметки
Слияние feat/web-console в main (или базовую ветку) низкая Ветка стабильная, все тесты проходят
Кнопка "Обновить код" в HTML консоли (upload из браузера) средняя Drag&drop zip или указать source_dir
Обратная синхронизация (скачать код из S3 в source_dir) средняя terraform data source или отдельная команда
History/versioning (несколько версий кода) высокая S3 уже хранит по timestamp — нужен UI
SLESS_SERVICE_TOKEN из k8s Secret низкая Сейчас задаётся через kubectl set env — надо в YAML (sealed secret)
Логи функции в HTML консоли средняя GET /invocations уже есть в операторе
Публикация terraform провайдера средняя Terraform Registry или Gitea Releases