Host-level sandbox tasks: LocalExecutor + bwrap workspace-write
Docker outer layer and bwrap inner layer are mutually exclusive on this host (fresh proc mounts fail with kernel EPERM inside Docker's nested pidns, but work on the host under a userns profile). So tasks that need an inner sandbox run on the host: - Worker --claim-executor-only: strict executor claim (only executor == own type, never NULL). The host worker MUST use it, or it would steal NULL-executor tasks meant for Docker workers (those tasks would lose the Docker outer layer). Default behavior unchanged (NULL claimable by any worker). - host-config-host.json: sandbox workspace-write, state work/host-state-host.json (distinct from Docker tasks' host-state.json), same relay home and test repo as Docker. - deploy/apparmor/codex-host: docker-codex base + userns, mount, pivot_root for bwrap; still denies raw network, ptrace, dangerous capabilities, /proc/sys writes. - deploy/host-worker/codex-host-worker.service: aa-exec -p codex-host, --executor local --claim-executor-only, capacity 2, proxy env 127.0.0.1:8888 so host tasks' relay/git egress also goes through the Tinyproxy allowlist (host direct egress would bypass it). POST /tasks already accepts executor (local/docker/cloud), so no API change was needed for routing. Tests: 4 host-sandbox-routing tests (strict claim incl. CAS, default still takes NULL, config sandbox/state, profile allows bwrap without deny mount). Full suites pass.
This commit is contained in:
1 parent
67acddaf7c
commit
5d09af855f
6 files changed
+266
-2
No files matched your search
+29
-2
@@ -236,7 +236,7 @@ class Worker:
|
||||
worker_id=None, capacity=1, fallback_models=None,
|
||||
tenant_isolation=False, tenants_root=None,
|
||||
tenant_template_home=None, tenant_template_repo=None,
|
||||
egress_proxy_port=0):
|
||||
egress_proxy_port=0, claim_executor_only=False):
|
||||
self.db_path = str(db_path)
|
||||
self.host_py_path = host_py_path
|
||||
self.config_path = config_path
|
||||
@@ -322,6 +322,13 @@ class Worker:
|
||||
# 端口),代理只放行白名单域名;网关查不到时任务直接
|
||||
# failed,绝不无代理跑(无代理等于绕过白名单)。
|
||||
self.egress_proxy_port = egress_proxy_port or 0
|
||||
# 严格 executor 认领:只认领 executor 恰好等于本 worker
|
||||
# 类型的任务,绝不认领 executor 为 NULL 的任务。默认
|
||||
# False(老行为:NULL 任务任意 worker 可认领)。宿主级
|
||||
# 沙箱 worker 必须开这个,否则它会抢走本该进 Docker
|
||||
# 容器的 NULL 任务(那些任务在宿主上跑就丢了 Docker
|
||||
# 外层,违背“按任务二选一”的路由意图)。
|
||||
self.claim_executor_only = bool(claim_executor_only)
|
||||
# 运维心跳:路径优先显式参数,其次环境变量,默认当前目录下
|
||||
# 的 worker-heartbeat.json;主机路径不在代码里硬编码,主机
|
||||
# 部署时用 --heartbeat-file 或环境变量传入(见 deploy/README)。
|
||||
@@ -439,6 +446,9 @@ class Worker:
|
||||
执行器、或指定为本 worker 执行器类型的任务;CAS 语义不变,
|
||||
仍是“仅当 queued 时标 running”,两个同池 worker 抢同一
|
||||
任务时只有一个能拿到 rowcount == 1。
|
||||
claim_executor_only 开启时(宿主级沙箱 worker 用):WHERE
|
||||
只加 executor = ?,NULL 任务绝不认领(否则宿主 worker
|
||||
会抢走本该进 Docker 的任务)。
|
||||
"""
|
||||
now = time.time()
|
||||
conn = _db_connect(self.db_path)
|
||||
@@ -451,6 +461,14 @@ class Worker:
|
||||
" WHERE id = ? AND status = 'queued'",
|
||||
(now, now, task_id),
|
||||
)
|
||||
elif self.claim_executor_only:
|
||||
cur = conn.execute(
|
||||
"UPDATE tasks SET status = 'running',"
|
||||
" last_heartbeat = ?, updated_at = ?"
|
||||
" WHERE id = ? AND status = 'queued'"
|
||||
" AND executor = ?",
|
||||
(now, now, task_id, executor),
|
||||
)
|
||||
else:
|
||||
cur = conn.execute(
|
||||
"UPDATE tasks SET status = 'running',"
|
||||
@@ -646,6 +664,8 @@ class Worker:
|
||||
except ValueError:
|
||||
return []
|
||||
mine = self._executor_name()
|
||||
if self.claim_executor_only:
|
||||
return [t for t in ready if t.get("executor") == mine]
|
||||
return [t for t in ready
|
||||
if t.get("executor") in (None, "", mine)]
|
||||
|
||||
@@ -1863,6 +1883,12 @@ def main(argv=None):
|
||||
" 出口全部走宿主该端口的代理,代理只放行"
|
||||
"白名单域名;网关查不到时任务直接 failed,"
|
||||
"绝不无代理运行")
|
||||
parser.add_argument("--claim-executor-only", action="store_true",
|
||||
help="严格 executor 认领:只认领 executor 恰好"
|
||||
"等于本 worker 类型的任务,绝不认领"
|
||||
" executor 为 NULL 的任务。宿主级沙箱"
|
||||
" worker 必须开,否则它会抢走本该进"
|
||||
" Docker 容器的 NULL 任务")
|
||||
parser.add_argument("--task-timeout", type=float, default=1800,
|
||||
help="每任务截止时间(秒,默认 1800);超时后真杀"
|
||||
"容器/进程组、任务置 failed;与 --docker-timeout"
|
||||
@@ -1912,7 +1938,8 @@ def main(argv=None):
|
||||
tenants_root=args.tenants_root,
|
||||
tenant_template_home=args.tenant_template_home,
|
||||
tenant_template_repo=args.tenant_template_repo,
|
||||
egress_proxy_port=args.egress_proxy_port)
|
||||
egress_proxy_port=args.egress_proxy_port,
|
||||
claim_executor_only=args.claim_executor_only)
|
||||
# CLI 启动清理兜底(常驻与单次都做):清掉上一轮残留容器。
|
||||
worker.cleanup_stale_containers()
|
||||
# CLI 起 worker 前先建表/迁移:全新库直接起循环不应因缺表而炸。
|
||||
|
||||
Reference in new issue
Block a user