Poco 使用手册
运维部署

多节点执行部署

使用 session 粘性和本地工作区扩展 Executor Worker 节点。

本文给出一套生产可用且不复杂的多节点执行方案:中心 Backend 统一持久化会话、任务队列和 worker 注册状态;每台执行机器运行一个 Executor Manager Worker,通过本机 Docker daemon 动态拉起 Executor 容器,并使用当前机器的本地 WORKSPACE_ROOT

这个方案不要求 NFS。会话执行期间的工作区只在绑定 worker 本地可用;任务完成后,worker 会把工作区 manifest、文件和 archive 导出到 S3 兼容对象存储,Backend 和前端再通过 S3 读取产物。

架构图

核心原则

  • Executor Worker 是执行节点,不是中心服务。它应该只暴露给 Backend 访问,不要直接暴露到公网。
  • 每个 worker 必须有唯一且稳定的 EXECUTOR_WORKER_ID。不要复制默认值到多台机器。
  • WORKSPACE_ROOT 是当前 worker 的本地目录,不需要和其他节点一致。
  • session 首次被某个 worker claim 后会绑定到该 worker,后续 run、停止、清理、会话历史补偿和浏览器流都会回到这个 worker。
  • 完成后的工作区文件通过 S3 访问,不依赖本地 worker 是否在线。

调度链路

准备阶段可能包含拉镜像、创建容器、同步 Skills/Plugins/Subagents、下载附件等操作,因此 worker 会在 claim -> start_run 期间续租。只要 worker 在准备阶段仍存活,就不会因为慢启动导致同一个 run 被其他 worker 重新领取。

节点部署

在每台执行机器上使用单独的 worker compose 文件:

docker compose -f docker-compose.worker.yml up -d

建议每台 worker 至少配置这些变量:

BACKEND_URL=https://<backend-public-host>
INTERNAL_API_TOKEN=< Backend 一致的强随机 token>
CALLBACK_TOKEN=< Backend/Executor Manager 一致的强随机 token>

EXECUTOR_WORKER_ID=worker-a
EXECUTOR_MANAGER_PUBLIC_URL=http://<worker-a-host>:8001

WORKSPACE_ROOT=${PWD}/tmp_workspace
S3_ENDPOINT=http://<s3-host>:9000
S3_ACCESS_KEY=<access-key>
S3_SECRET_KEY=<secret-key>
S3_BUCKET=poco

第二台 worker 只需要改唯一项:

EXECUTOR_WORKER_ID=worker-b
EXECUTOR_MANAGER_PUBLIC_URL=http://<worker-b-host>:8001
WORKSPACE_ROOT=${PWD}/tmp_workspace

WORKSPACE_ROOT 可以每台机器都写成 ${PWD}/tmp_workspace,因为它表示各自 compose 当前目录下的本地目录,不需要跨节点相同。

网络要求

访问方向说明
Worker -> Backend注册、心跳、claim、start、fail、回调转发
Backend -> Workerwake、停止、清理、会话历史补偿、浏览器流代理
Worker -> S3工作区文件、manifest、archive 导出
Backend -> S3前端文件列表和下载 URL 生成
Executor -> Worker回调、用户输入请求、浏览器截图上传

EXECUTOR_MANAGER_PUBLIC_URL 必须是 Backend 能访问到的 worker 地址。它不需要给最终用户直接访问。

前端用户访问浏览器实时预览时走 Backend WebSocket 代理:

因此对外提供系统访问时,不需要为每个 worker 绑定公网域名。只要 Backend 能访问 worker,前端统一连 Backend 即可。

关键环境变量

变量配置位置说明
EXECUTOR_WORKER_IDWorker每个 worker 唯一且稳定
EXECUTOR_MANAGER_PUBLIC_URLWorkerBackend 访问该 worker 的地址
BACKEND_URLBackend / Frontend / WorkerBackend 公开入口;worker 会访问它,浏览器实时预览 WebSocket 也会使用它,Frontend 仅将它作为兼容回退
INTERNAL_API_BASE_URLFrontendFrontend 服务端直连 Backend 的内部地址,优先于 BACKEND_URL
INTERNAL_API_TOKENBackend / Worker内部 API 鉴权 token,必须一致
CALLBACK_TOKENWorkerExecutor 回调到 worker 的鉴权 token
WORKSPACE_ROOTWorker当前节点本地工作区目录
S3_ENDPOINT / S3_BUCKETBackend / WorkerBackend 和 worker 都必须可访问同一对象存储
TASK_CLAIM_LEASE_SECONDSWorker准备阶段 claim 租约,默认 180
MAX_CONCURRENT_TASKSWorker单节点并发上限
MAX_EXECUTOR_CONTAINERSWorker单节点 executor 容器总量上限
LEGACY_UNBOUND_SESSION_WORKER_IDBackend从单机升级多节点时,历史未绑定 session 的原节点

Worker 状态管理

worker 启动后会自动注册并定期心跳。后台管理菜单「执行节点」会显示:

  • statusonlinedrainingofflinedisabled
  • capacity / active_runs:节点心跳上报的容量和当前活跃 run 数。capacity 来自该 worker 的 MAX_CONCURRENT_TASKSactive_runs 来自 Executor Manager 当前正在调度的 run 数。
  • running_runs:Backend 数据库中该 worker 已领取或运行中的 run 数。后台「运行负载」列展示的是 running_runs / capacity,因此分母是 MAX_CONCURRENT_TASKS,不是容器数量。
  • bound_sessions:绑定到该 worker 的未删除 session 数
  • 心跳时间和 stale 状态

推荐操作方式:

  • 扩容:新机器配置唯一 EXECUTOR_WORKER_ID 后启动 compose,Backend 会自动发现。
  • 临时下线:先切到 draining,等待 active_runs=0 后再停止容器。
  • 禁用节点:切到 disabled。该节点不会接新任务,也不会作为已绑定会话的可路由节点。
  • 恢复节点:启动原 worker,并保持原 EXECUTOR_WORKER_ID。如果 worker 已 stale,新的 boot id 可以重新注册。

draining 的含义是“不接新 session,但仍可服务已绑定 session”。这对本地工作区架构很重要,因为历史 session 的文件和运行态仍在原 worker 上。

失败与恢复行为

场景行为
worker 在 claim 后、start_run 前崩溃claim lease 过期后 run 回到 queued;如果这是新 session 的首次 claim,会清理 session 绑定,其他 worker 可重新领取
worker 准备阶段很慢worker 持续 renew claim,避免其他 worker 重复领取
worker 运行中失联session 已绑定到该 worker;需要恢复原 worker 才能继续本地工作区会话
用户删除 session 但 worker 不可用Backend 仍会软删 session,清理任务跳过并返回 cleanup error
任务已完成且 workspace export ready文件列表和下载从 S3 读取,不依赖 worker 在线
worker_id 被两台机器同时使用fresh boot id 冲突会被拒绝,避免同一逻辑节点被两个物理节点抢占

从单机升级到多节点

  1. 选定原单机节点 ID,例如 local-worker
  2. Backend 配置:
LEGACY_UNBOUND_SESSION_WORKER_ID=local-worker
  1. 原单机 worker 配置:
EXECUTOR_WORKER_ID=local-worker
EXECUTOR_MANAGER_PUBLIC_URL=http://<old-worker-host>:8001
  1. 新增 worker 使用新的 ID,例如 worker-bworker-c
  2. 观察后台「执行节点」,确认各节点 online,且 EXECUTOR_MANAGER_PUBLIC_URL 可被 Backend 访问。

历史未绑定 session 会优先回到 LEGACY_UNBOUND_SESSION_WORKER_ID,避免旧本地工作区被路由到新机器。新 session 则按节点可用性和负载选择 worker 并写入绑定。

安全边界

生产部署建议:

  • worker 8001 端口只允许 Backend 所在网段访问。
  • 不要把 INTERNAL_API_TOKENCALLBACK_TOKEN 使用默认值。
  • EXECUTOR_MANAGER_PUBLIC_URL 使用内网 IP、VPN 地址或私有负载网络地址。
  • 对外只暴露 Frontend 和 Backend。
  • S3/RustFS 控制台不要直接公网暴露;浏览器下载使用 Backend 生成的预签名 URL。

Worker 业务接口按用途分两类鉴权:

  • Backend -> Worker:X-Internal-Token
  • Executor -> Worker:X-Callback-Token

健康检查接口可以保持裸露给容器自检使用,但业务写入口、工作区管理、浏览器代理、任务控制和回调入口都必须带 token。

容量规划

先按简单规则估算:

总并发容量 = worker 数量 × MAX_CONCURRENT_TASKS

再根据单任务资源消耗调低:

  • EXECUTOR_CPU_LIMIT:限制单个 executor 容器 CPU。
  • EXECUTOR_MEMORY_LIMIT:限制单个 executor 容器内存。
  • MAX_EXECUTOR_CONTAINERS:限制单节点动态容器总量。一个 session 的多轮 run 通常会复用同一个 executor 容器,用于保留工作区、浏览器状态和运行上下文;同时 worker 也可能暂时保留 warm/persistent 容器以降低下一轮启动成本。因此容器总量不等于当前并发 run 数,通常应满足 MAX_EXECUTOR_CONTAINERS >= MAX_CONCURRENT_TASKS,并给多轮会话复用留出余量。
  • TASK_CLAIM_LEASE_SECONDS:节点拉镜像或 staging 慢时调大。

简单区分:

  • MAX_CONCURRENT_TASKS 是「同时干活的槽位数」,决定 worker 一次能并发执行多少个 run,也决定后台负载分母。
  • MAX_EXECUTOR_CONTAINERS 是「最多保留的容器池大小」,防止 Docker 中 executor 容器无限增长,不参与节点调度容量计算。

如果任务大量依赖浏览器、编译或大仓库,优先增加 worker 数量,而不是单机无限提高并发。

运维检查清单

  • 每个 worker 的 EXECUTOR_WORKER_ID 唯一。
  • Backend 能访问每个 EXECUTOR_MANAGER_PUBLIC_URL
  • Frontend 能访问 INTERNAL_API_BASE_URL,Worker 能访问 BACKEND_URL;用户浏览器能访问 Frontend;Worker 还必须能访问 S3。
  • Backend 和所有 worker 使用相同 INTERNAL_API_TOKEN
  • Worker 与动态 executor 使用相同 CALLBACK_TOKEN
  • WORKSPACE_ROOT 对 worker 容器用户可读写。
  • worker 端口没有直接暴露到公网。
  • 下线前先 draining,不要直接停掉承载活跃 session 的 worker。

On this page