Deploy
Как worker поднимается и загружается. Worker — это руки агента: песочница по запросу (bash · fs · браузер), реализованная на Kubernetes как короткоживущий Job, а не постоянный Deployment. Ничего «всегда-онлайн» на каждого агента нет.
Это шаг 1 жизненного цикла → дальше Use → затем Destroy.
Кто его создаёт
Приложение worker не планирует себя само. Менеджер рантайма внутри api (слайс runtime/worker) владеет k8s-клиентом и жизненным циклом сессии:
orchestrator → tasks (BullMQ) → runtime manager → k8s Job (worker pod)
│ mints a short-lived session token (user/auth)
│ builds the manifest from a RuntimeProfile
▼
worker pod boots → WS to Core → ready → runs → uploads → exits
│ heartbeats reset the idle timer
▼
idle reaper deletes the Job (see Destroy)В очередь ставится только «агенту нужны руки»
Обычный чат-турн без тулов никогда не задействует очередь (слайс runtime/task). В очередь ставятся только worker/tool-задачи:
message → orchestrator (load agent + memory + chat, tool-analysis)
├─ no tools → llm.stream() answers directly → save → done ← NO queue
└─ tools → tasks (queue) → dispatcher → worker Job → stream ← queueОчередь оправдывает себя ради backpressure + таймеров + надёжности, а не ради сырой пропускной способности:
| Назначение | Что делает | Что ломается без неё |
|---|---|---|
| Backpressure | дозирует создание Job под ёмкость нод | всплеск из N задач → N Pending-подов → давление на scheduler/etcd |
| Конкурентность / справедливость | per-team-лимиты, глобальные потолки, премиум-приоритет | один тенант голодает всех остальных |
| Надёжность | retry-with-backoff + dead-letter | потерянная задача просто исчезает |
| Таймеры | delayed-джобы = idle-reaper; repeatable = agent/cron | нет чистого idle-teardown'а или запусков по расписанию |
| Decoupling | задача переживает то, какая stateless-реплика api её поставила в очередь | rescale HPA роняет работу в полёте |
Консьюмер — это dispatcher
Worker'ы эфемерны — один k8s Job на задачу — поэтому консьюмер очереди — это не пул worker'ов. Это dispatcher (менеджер runtime/worker в api): он берёт задачу, применяет лимиты конкурентности, выбирает RuntimeProfile и создаёт Job. Worker порождается на задачу и стримит результаты назад через events (Redis pub/sub → SSE/WS); сам он никогда не является консьюмером очереди.
| Тип Job | Механизм | Для чего |
|---|---|---|
| Task dispatch | standard job | выполнить обязательство-тул в worker Job |
| Idle timers | delayed job | reaper усыпляет/сносит idle-сессию (Destroy) |
| Scheduled / cron | repeatable job | запуски agent/cron → тот же путь orchestrator |
| Ingestion | standard job (budgeted) | индексация документов LightRAG |
Engine: BullMQ на Redis (даёт delayed- и repeatable-джобы и pub/sub для
events). Открытая альтернатива — только-Postgrespg-boss+LISTEN/NOTIFY— держится за слайсомruntime/task, так что engine заменяем. См. Решения по очереди задач.
Режимы рантайма → k8s-примитивы
Форма ресурсов берётся из RuntimeProfile (каталог пресетов):
| Режим | Триггер | k8s-примитив | Ресурсы | Idle |
|---|---|---|---|---|
| None | обычный чат, без тулов | без пода (только LLM + память) | — | n/a |
| Light | bash, скрипты, файловые операции | Job (restartPolicy: Never, ttlSecondsAfterFinished) | ~0.5 CPU / 512Mi | 60–120с |
| Browser | использован браузер-тул | Job с headless Chromium in-pod (Playwright, per-tenant --user-data-dir) | на задачу | до простоя браузера |
| Heavy | скрейпинг, тяжёлые вычисления | Job на нодпуле workers, высокие лимиты | 2+ CPU / 2Gi+ | 30–60с |
| Warm | премиум | Light/Heavy Job живёт дольше idle | по профилю | 10–30 мин |
Манифест Job (иллюстративно)
apiVersion: batch/v1
kind: Job
metadata:
name: agent-{sessionId}
namespace: tenant-{teamId} # namespace (or labels) per tenant
labels: { app: agentfy-worker, team: "{teamId}", session: "{sessionId}" }
spec:
backoffLimit: 0 # no retries — a failed obligation fails the task
ttlSecondsAfterFinished: 60 # k8s auto-deletes the finished Job
activeDeadlineSeconds: 1020 # dialSeconds + maxExecSeconds — НЕ один maxExecSeconds:
# k8s считает это от старта JOB, то есть включая холодный
# старт. См. /ru/worker/protocol#deadlines
template:
spec:
restartPolicy: Never
automountServiceAccountToken: false
nodeSelector: { node-role: workers }
tolerations: [{ key: node-role, value: workers, effect: NoSchedule }]
# `fsGroup` is what makes the 0440 pass file readable by the image's
# `USER 1000:1000`: a Secret volume's files are owned by root.
securityContext: { runAsNonRoot: true, fsGroup: 1000, seccompProfile: { type: RuntimeDefault } }
containers:
- name: worker
image: registry/agentfy-worker:{tag}
resources:
requests: { cpu: "500m", memory: "512Mi" }
limits: { cpu: "1", memory: "1Gi", ephemeral-storage: "5Gi" }
env:
- { name: SESSION_ID, value: "{sessionId}" }
- { name: CONTROL_URL, value: "wss://core/ws/runtime" }
- { name: TOOL_ALLOWLIST, value: "bash,fs,browser" }
# Where `http` / `web_fetch` may go. Hosts, or `*.host` — never a URL and
# never `*`. ABSENT MEANS EMPTY, and empty reaches nowhere.
- { name: EGRESS_ALLOWLIST, value: "example.com,*.example.com" }
# Где `browser_play` может ВПИСЫВАТЬ и НАЖИМАТЬ (AGNT2-221). ВТОРОЙ
# список, а не прочтение предыдущего: разрешение читать сайт не есть
# разрешение на нём нажимать. Только точные имена хостов — ни `*.`,
# ни `internet:unrestricted`. ОТСУТСТВУЕТ — ЗНАЧИТ ПУСТО, а пусто не
# трогает ничего.
- { name: BROWSER_PLAY_ALLOWLIST, value: "shop.example.com" }
# The pass arrives as a FILE, never as a value in the environment
# (006 T104). This names the PATH; the Secret behind it is owned by
# this Job, so the cluster reaps it when the Job goes.
- { name: WORKER_TOKEN_FILE, value: /var/run/agentfy/token }
volumeMounts:
- { name: workspace, mountPath: /workspace }
- { name: session-pass, mountPath: /var/run/agentfy, readOnly: true }
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities: { drop: ["ALL"] }
volumes:
- name: workspace
emptyDir: { sizeLimit: "5Gi" } # ephemeral; wiped when the pod dies
- name: session-pass
projected:
defaultMode: 0440 # root:1000 — readable by the pod's user
sources:
- secret:
name: worker-{jobName}-pass
items: [{ key: token, path: token }]Boot & регистрация
Под стартует с SESSION_ID, CONTROL_URL, короткоживущим session-токеном (JWT, scoped на эту одну AgentRuntimeSession, инжектится как projected/short-TTL Secret — никаких долгоживущих секретов в поде), allowlist'ом тулов и скоупом хранилища. Затем он дозванивается до Core по WebSocket и сигнализирует ready. Полный handshake и протокол сообщений живут в Use; сам контракт провода — каждый кадр, его ответ, его таймаут и правило версий — это Канал инструментов.
Масштабирование
- Scale-to-zero встроен: Job существует только пока идёт задача, потом удаляется. KEDA/Knative для MVP не нужны.
- Конкурентность ограничена нодпулом
workers+ per-tenant ResourceQuota/LimitRange; cluster-autoscaler растит нодпул под нагрузкой. - Worker несёт Chromium → холодный старт браузера = спин-ап Job'а + запуск Chrome. Пре-пулленные образы worker'а (и пара тёплых нод
workers/ Warm-профиль) прячут эту задержку.
Чеклист безопасности
- Неймспейс (или NetworkPolicy + labels) на тенанта/сессию.
- NetworkPolicy egress-allowlist (по умолчанию deny-all; разрешить Core WS + нужные домены).
- Лимиты CPU/RAM/storage из профиля; read-only root fs, drop all capabilities, non-root,
automountServiceAccountToken: false. - Только временный
emptyDir-workspace; никаких постоянных секретов в образе; креды короткоживущие, истекают вместе с сессией. activeDeadlineSeconds— жёсткий потолок на runaway-рантаймы, размеромdialSeconds + maxExecSeconds, потому что k8s начинает считать от Job, а не от процесса (канал инструментов); аудит-лог каждой сессии черезadmin/audit.
Где живёт код
- Менеджер (control):
api→ слайсruntime/worker—sessions·k8s(сборка манифеста + create/delete) ·browser(жизненный цикл Chromium in-pod) ·idle(reaper) ·profiles. - Очередь:
api→ слайсruntime/task(BullMQ). - Песочница (образ): верхнеуровневое приложение
worker— цикл агента + bash/fs/browser исполнители + WS-клиент. Тул-исполнители берём изcleanslice/runtime, паттерны k8s/манифеста — из Ranch. - Кластер: Hetzner k8s, нодпул
workers(tainted). См. Resources.
См. также
- Use — как мозг управляет живым worker'ом (сессии, протокол, тулы).
- Канал инструментов — контракт провода: кадры, ответы, молчание, версии.
- Destroy — idle-reaper, release, TTL и очистка.
- Implementation — build-промпт для этой подсистемы.
- Cluster & nodes — backpressure + autoscaler, который кормит очередь.