18 KiB
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/funcs → sless-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), показывает diffterraform 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 |