进阶 omnigent.ai 2026-10-14 06:47:13 · 32 阅读

第31章 Omnigent 的 Kubernetes 沙箱配置

Kubernetes 沙箱配置

kubernetes 托管沙箱 provider 会为每个托管会话启动一个专用的 runner Pod。在服务器配置的 sandbox.kubernetes 下进行设置。agent_sandbox provider(自回收的 agent-sandbox Sandbox 资源)读取相同的 sandbox.kubernetes 配置,并添加一个可选的 sandbox.agent_sandbox 代码块用于原生 warm pools。

有关部署步骤,请参阅 Shared Server。

Pod 启动等待预算 配置项 | 值 | 键 | 环境变量 | 类型 | 默认值 pod_ready_timeout_s | OMNIGENT_K8S_POD_READY_TIMEOUT_S | 正整数(秒) | 90

在托管会话上线之前,Omnigent 会等待 runner Pod 完成调度、拉取镜像、克隆仓库(如果是多仓库会话,则并行克隆多个仓库)并达到 Running 状态。如果冷启动拉取镜像通常耗时较长,请增加等待预算:

sandbox: provider: kubernetes kubernetes: pod_ready_timeout_s: 300

该预算涵盖 Pod 发现、驱逐或节点排空后的替代 Pod 发现、Kubernetes API 读取错误以及处于 Pending 状态的时间。预算超时时,启动错误会报告配置的截止时间和最后观察到的 Pod 状态。

该值必须是正 YAML 整数。诸如 0、"90" 或 true 这样的值会导致服务器启动失败。

为了缩短克隆步骤本身,请设置与 provider 无关的 sandbox.git_clone 代码块(浅深度、单分支、blobless 过滤)。Kubernetes provider 对其克隆的每个仓库(包括 warm-pool Pods)都遵循此设置。

沙箱化容器运行时(runtime_class) 配置项 | 值 | 键 | 类型 | 默认值 runtime_class | 字符串(DNS-1123 子域名) | 未设置——使用集群默认运行时

设置 runtime_class 可将每个 runner Pod 调度到集群通过 RuntimeClass 对象暴露的沙箱化容器运行时上——例如 Kata Containers micro-VMs 或 gVisor。Omnigent 会将该值原样写入 Pod 的 spec.runtimeClassName:

sandbox: provider: kubernetes kubernetes: runtime_class: kata

如果未设置 runtime_class,Pod 将保留在集群的默认运行时上——这与当前行为在字节级别上完全一致。该名称必须是 DNS-1123 子域名,并且必须与你在集群中预先创建的 RuntimeClass 匹配;格式错误的值(例如 Not_A_DNS_Name)会导致服务器启动失败,而不是首次启动失败。Omnigent 仅引用 RuntimeClass——provisioning 及底层节点运行时是集群外的操作员步骤。

调度到带污点的 NodePool(tolerations) 配置项 | 值 | 键 | 类型 | 默认值 tolerations | 列表 of {key?, operator?, value?, effect?, tolerationSeconds?} | 未设置——不添加 tolerations

设置 tolerations 允许 runner Pods 调度到你专门用于沙箱的带污点 NodePool 上。每个条目是一个 Kubernetes Toleration,原样写入 Pod 的 spec.tolerations:

sandbox: provider: kubernetes kubernetes: tolerations: - key: sei.io/node-role operator: Equal value: omnigent-sandbox effect: NoSchedule node_selector: sei.io/node-role: omnigent-sandbox

Toleration 仅允许 Pod 调度到匹配污点的节点上——它不会吸引 Pod 前往该节点。将 tolerations 与 node_selector 配对使用,以将 runner Pods 也固定到该节点池;如果没有 selector,Pod 仍可能落在任何无污点节点上。如果未设置 tolerations,则不添加 spec.tolerations 键,保持与先前部署的清单在字节级别上的兼容性。

每个条目接受以下字段:

key — 要容忍的污点键。必须是有效的 Kubernetes label 键。仅在 operator: Exists(Kubernetes 的“容忍一切”形式)时省略。 operator — Equal(默认)或 Exists。Exists 匹配任何值,因此不能与 value 一起使用。 value — 污点值,在 operator: Equal 时进行匹配。必须是有效的 Kubernetes label 值。 effect — 可以是 NoSchedule、PreferNoSchedule 或 NoExecute 之一。省略则表示容忍该键在所有 effect 下的情况。 tolerationSeconds — 一个整数,仅在 effect: NoExecute 时有效;表示在添加匹配的污点后,Pod 保持绑定到节点的时间。

条目在服务器启动时进行验证,因此格式错误或自相矛盾的 toleration(带有 value 的 Exists operator、operator 不为 Exists 的空 key、或没有 effect: NoExecute 的 tolerationSeconds)会快速失败,而不是在首次托管启动时失败。

从环境设置预算 你也可以使用 OMNIGENT_K8S_POD_READY_TIMEOUT_S 环境变量设置等待预算,这在相同的 bundle 跨不同镜像拉取时间的部署运行时非常有用:

export OMNIGENT_K8S_POD_READY_TIMEOUT_S=300

优先级:当设置时,显式的 sandbox.kubernetes.pod_ready_timeout_s 配置键总是优先;环境变量仅在该键未设置时参考,并覆盖内置的 90 秒默认值。非数字的环境值(例如 not-a-number)会快速失败并给出清晰错误;像 120.0 这样看起来像浮点数的值会被接受并截断为整秒数。

runner Pods 的持久化存储 Runner Pods 是临时的:工作区位于 emptyDir 上,并随 Pod 一起销毁。为了暴露持久化数据(数据集、模型缓存、共享输出目录),使用 sandbox.kubernetes.pvc_mounts 将预先创建的 PersistentVolumeClaims 挂载到每个 runner Pod 中:

sandbox: provider: kubernetes kubernetes: pvc_mounts: - claim_name: omnigent-datasets mount_path: /mnt/datasets read_only: true # 默认 true;仅在共享 scratch 时设置为 false

每个条目是 {claim_name, mount_path, read_only?} 映射。read_only 默认为 true。Omnigent 仅引用 claim——你在集群外预先在 runner 命名空间(omnigent-sandboxes)中创建 PV/PVC,因此服务器 RBAC 保持不变。挂载仅落在 host 容器上;克隆仓库的 init 容器永远看不到它们。

请记住这些注意事项:

挂载路径在服务器启动时进行验证。mount_path 不得与 /home/omnigent、Secret 投影或镜像的 OS 目录(例如 /home、/var、/run、/tmp、/etc、/opt)重叠;格式错误或保留路径会导致启动失败,而不是首次启动失败。路径必须是绝对路径和规范化路径,并且不得相互重复或嵌套。 可写 claims 在并发 runners 之间共享。对于任何可写内容,使用支持 ReadWriteMany 的后端(NFS/SMB/CephFS),并在其他地方优先使用 read_only: true——可写共享挂载允许一个会话读取和修改另一个会话写入的内容,并且其内容比 Pod 生命周期更长。 ReadWriteOnce claims 将所有 runners 固定到一个节点上——有意地与 node_selector 结合使用,否则后续 Pods 会处于 Pending 状态。 在 Pod 中可见的挂载并不自动对 harness 自身的 OS 级沙箱可见(Omnibox 路径授权是独立的)。

有关完整指南,请参阅仓库中的 deploy/kubernetes/README.md 和 deploy/kubernetes/overlays/sandbox-runners/README.md。

支持轮换的凭据(secret_mounts) 在 sandbox.kubernetes 下,可选的 secret_mounts 列表将在 runner 命名空间中预先创建的 Kubernetes Secrets 作为只读文件投影到每个 runner Pod 上。每个条目是 { secret_name, mount_path } 映射:

sandbox: provider: kubernetes server_url: https://your-server.example.com kubernetes: secret_mounts: - secret_name: git-token # runner 命名空间中的 Secret mount_path: /mnt/secrets/git # 绝对的、非保留的 Pod 内路径 - secret_name: npm-token mount_path: /mnt/secrets/npm

与通过 envFrom 注入的凭据(在容器启动时读取一次)不同,作为卷投影的 Secret 由 kubelet 原地刷新,因此长期运行的 runner 无需重启即可获取轮换后的凭据。刷新最终一致(kubelet 同步,最长约 1 分钟),因此沙箱内的消费者必须在使用时重新读取文件——在启动时缓存的值会使轮换失效。

每个卷仅在 host 容器上以只读方式挂载(绝不会挂载到克隆时的 init 容器),因此 Secret 卷没有 read_only 开关。条目在服务器启动时进行验证:secret_name 必须是 DNS-1123 子域名,mount_path 必须是绝对的、规范化的路径,且未被保留、不重复、不嵌套于或与其他 secret_mounts 或 pvc_mounts 路径重叠——操作员笔误会导致启动失败,而不是首次启动失败。缺失的 Secret 会阻止 Pod 启动。

agent_sandbox 的原生 warm pools(warm_pool) 配置项 | 值 | 键 | 类型 | 默认值 | 要求 warm_pool | 字符串(DNS-1123 子域名)——SandboxWarmPool 的名称 | 未设置——每个会话直接创建其 Sandbox | sandbox.provider: agent_sandbox

使用 agent_sandbox provider 时,warm pool 在会话请求之前准备好备用 Pods。镜像拉取、调度、容器启动和初始 PVC provisioning 在请求之前完成;然后 Omnigent 申领一个兼容的 Sandbox,将托管主机身份交付给等待的 bootstrap,并运行正常的仓库准备和主机启动。GitHub 和 Databricks 凭据仍然来自现有的 owner-bound broker 和 credential store,并且带有持久化 HOME PVC 的 suspend/resume 生命周期不变。

通过命名操作员拥有的 pool 来启用:

sandbox: provider: agent_sandbox server_url: http://omnigent.omnigent.svc.cluster.local:8000 agent_sandbox: warm_pool: omnigent-default-v1 kubernetes: namespace: omnigent-sandboxes service_account: omnigent-runner image: your-registry/omnigent-host:your-version

验证在服务器启动时进行:warm_pool 是 sandbox.agent_sandbox 下允许的唯一键,值必须是非空的 DNS-1123 子域名(周围的空白被修剪),并且如果设置 sandbox.agent_sandbox 时使用其他 provider 则失败,要求 provider: agent_sandbox。省略该代码块或设置 warm_pool: null 则保留直接 Sandbox 创建。该键在多 provider 部署中 providers: 列表条目内也有效。

先决条件

上游 agent-sandbox v1.0.0 controller 带扩展——SandboxTemplate、SandboxWarmPool 和 SandboxClaim CRDs(extensions.agents.x-k8s.io/v1beta1): kubectl --context YOUR_CONTEXT apply --server-side -f \ https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.0/sandbox-with-extensions.yaml

从与服务器相同的 Omnigent revision 构建的 host 镜像。Warm Pods 运行 omnigent.host.warm_bootstrap 模块,该模块必须存在于镜像中。

加性 warm-pool RBAC overlay(见下文)。

生成模板和 pool Omnigent 读取 pool 但从不创建、调整大小或更新它——SandboxTemplate 和 SandboxWarmPool 由操作员拥有。从你部署的确切服务器配置中渲染它们,使用服务器相同的 workspace-volume 环境设置,以便模板的 profile 与服务器预期相符:

export OMNIGENT_AGENT_SANDBOX_WORKSPACE_SIZE=5Gi python -m omnigent.onboarding.sandboxes.agent_sandbox_warm_pool \ --config server-config.yaml --name omnigent-default-v1 --replicas 2 --shared \ > warm-pool.yaml kubectl --context YOUR_CONTEXT apply -f warm-pool.yaml kubectl --context YOUR_CONTEXT apply -k deploy/kubernetes/overlays/sandbox-runners/warm-pool

生成器选项:

--config PATH(必需)—— 服务器配置;必须启用 sandbox.provider: agent_sandbox。 --name NAME(必需)—— 用于 SandboxTemplate 和 SandboxWarmPool 两者;将 sandbox.agent_sandbox.warm_pool 设置为相同的值。 --replicas N —— 保持就绪的备用 Pods 数量(默认 1,必须为非负数)。 --shared 或 --agent-name NAME —— 互斥;选择 pool 可服务的会话(见下表)。

输出是带有 networkPolicyManagement: Unmanaged 的 SandboxTemplate 和使用 Recreate 更新策略的 SandboxWarmPool,后者仅替换未使用的库存。每当更改模式或任何 profile 设置时,请使用新的版本化 pool 名称(例如 …-v2),以便旧的备用 Pods 永远不服务于先前的 profile。

生成器选项 | Pool 服务的会话 --shared | 任何会话 harness 或可信 agent 分类器,包括上传的 agent,具有相同的基础设施 profile。 --agent-name | 仅精确的内置 agent 分类器;其他分类器回退到直接 provisioning。 两者均未设置 | 旧版未分类行为:仅精确的空分类器匹配使用 pool;内置分类器直接处理。

--shared 推荐给使用 owner-bound GitHub 和 Databricks brokers 的部署,以便内置和上传的 agent 共享相同的备用库存。共享模板省略 omnigent.ai/agent 标签并携带注释 omnigent.ai/warm-pool-shared: "true";该标记是 profile 指纹的一部分,因此仅缺少 agent 标签的现有 pool 不是共享的。Per-agent 准入时凭据注入不适用于无标签的共享 pool——如果你依赖它,请使用 --agent-name。共享模式仅更改 pool 兼容性;凭据仍范围限制于 host 的 owner。

RBAC overlay deploy/kubernetes/overlays/sandbox-runners/warm-pool 为 omnigent-sandboxes 中的 omnigent-server ServiceAccount 添加命名空间的 Role/RoleBinding:

sandboxclaims: create, get, patch, delete sandboxtemplates, sandboxwarmpools: get pods/exec: create, get —— 用于在分配后向 bootstrap 交付会话身份

Kubernetes RBAC 无法将 exec 限制到特定命令,因此在 runner 命名空间内启用 warm 模式是服务器的显式权限扩展。Runner ServiceAccounts 不接收这些授权,直接模式不需要 overlay。

行为和注意事项

已分配的 profile 保持固定。已分配的 warm Sandbox 保留其原始镜像、挂载、资源、调度和安全设置以及 agent 分类器。更改这些服务器设置可能会阻止该会话的后续唤醒;不匹配会以 profile 错误失败并保留 Sandbox 及其 PVC。共享分配在基础设施 profile 仍匹配时容忍跨挂起的 agent 或 harness 更改;专用和旧版未分类分配要求精确的分类器匹配。 更改 warm_pool 仅影响新分配。现有 claims 和工作区不会迁移,并且禁用池化不会将现有 warm 分配转换为直接分配。对于需要在唤醒之间进行基础设施更改的会话,使用直接 provisioning。 容量规划计算两个容器。Bootstrap 和 host 作为普通容器运行,每个都预留配置的资源,因此配置中的 250m CPU / 384Mi 内存为每个池化 Pod 预留 500m / 768Mi(在注入 sidecar 之前)。为备用和已分配的 Pods 调整节点大小。 网络由你负责。生成的模板设置 networkPolicyManagement: Unmanaged 并使用集群 DNS,与直接 provider 匹配。你自己提供生产 NetworkPolicy 对象,包括 DNS 和到 Omnigent 回调 URL 以及任何私有 Git/Databricks 端点的出站。 不要自定义单个 claims。向 SandboxClaim 添加环境变量或 PVC 覆盖会使上游创建冷 Sandbox 而不是 warm。会话特定数据在分配后到达;每个模板创建的 HOME PVC 属于其 Sandbox 并在挂起后生存。已分配的 Sandboxes 永远不会返回到备用 pool。 空 pool 仍可工作。上游可能从相同模板创建冷成员,因此成功的分配本身并不能证明 warm 命中——测量延迟时,比较请求前记录的 Pod UIDs。 通过 envFrom 注入的静态 harness Secrets 在备用 Pod 启动时读取,因此在依赖新值之前通过替换未使用的 Pods 来轮换它们。

验证部署 一个可选的实时端到端测试针对真实集群测试分配、host/runner 注册、工作区持久化、pool 补充和 suspend/wake。它需要一个接受 API 请求而不带身份验证标头的测试服务器(例如单用户开发服务器),一个在与其他启动器隔离的命名空间中至少有 1 个就绪备用的共享 pool,以及一个支持 Claude SDK harness 的 host 镜像。如果没有 OMNIGENT_WARM_POOL_E2E=1,测试跳过;不需要模型或已连接账户的凭据。

OMNIGENT_WARM_POOL_E2E=1 \ OMNIGENT_WARM_POOL_SERVER_URL=http://127.0.0.1:8000 \ OMNIGENT_WARM_POOL_KUBECONFIG=/absolute/path/to/test-kubeconfig \ OMNIGENT_WARM_POOL_CONTEXT=YOUR_TEST_CONTEXT \ OMNIGENT_WARM_POOL_NAMESPACE=omnigent-sandboxes \ OMNIGENT_WARM_POOL_NAME=omnigent-default-v1 \ .venv/bin/python -m pytest tests/e2e/test_agent_sandbox_warm_pool.py -v

在身份验证部署中单独验证凭据行为: 连接测试 GitHub 和 Databricks 账户,创建带有私有仓库的池化会话,并检查 git fetch 和 gh api user --jq .login;确认工作区在挂起和唤醒后生存,并且两个服务之后仍然进行身份验证。在此检查期间,将静态 provider 凭据 Secrets 排除在 pool 之外,以免它们掩盖失败的 broker 查找。

有关完整 runbook,请参阅仓库中的 deploy/kubernetes/overlays/sandbox-runners/warm-pool/README.md。

评论 (0)