沙箱网络架构设计概要设计

0 阅读47分钟

1. 设计概述

1.1 背景

Sandbox 由 Portal 集群统一管理,Sandbox Pod 分布在多个算力集群。Portal 可以通过 Kubernetes API 创建和管理 Sandbox,设计启动时运行时网络存在以下问题(历史背景,现已由下述 fixed-entry 主链路解决):

  1. Sandbox PodIP 只在所属算力集群 Overlay 内可达,Portal 无法直接访问 Pod 内 execd:44772。
  2. noVNC、CDP 曾依赖独立入口资源暴露;当前 E2B/OpenSandbox 通用 URI 可复用 fixed-entry,Native Browser 专用 Ingress/Service 路径仍保留。不能把 Ingress/Service 对象数量直接等同于对外端口耗尽。

因此,本设计需要同时解决 Sandbox 的入站访问、出站限制,以及从模板默认值映射到沙箱实例的网络策略控制,并保证 PodIP 不作为跨集群地址对外发布。

1.2 用户故事

  1. 运行时访问:我使用官方 Python e2b==2.24.0 调用运行命令接口时,只配置固定 Portal 地址,不需要感知 Sandbox 所在集群、固定入口或 PodIP。
  2. 业务端口访问:浏览器客户端,我能够取得已授权 Sandbox 的业务入口,并访问 noVNC、CDP 和自定义 HTTP/WS 服务。
  3. Sandbox 网络策略控制:作为 Sandbox 用户,我能够在基于模板创建沙箱时继承模板默认的入口白名单与出口白名单,也可以在创建请求的 network 对象中按维度覆盖(官方 SDK allowOut/allowPublicTraffic 出站字段 + ingress[] 入站扩展),并在实例创建后按沙箱维度独立调整精确的入口/出口规则。
  4. 模板默认白名单配置:作为平台管理员,我能够在新增或维护沙箱模板时配置默认的沙箱入口来源 CIDR 与出口目标 CIDR;模板默认值映射到实例后,实例入口白名单按共享固定入口的 path 级差异化规则生效,并在实例配置下发失败时保留上一份有效配置。

1.3 设计目标

  1. 官方 SDK 的固定 sandbox_url 为 http://<portal-authority>:30083,在 Sandbox 创建前完成配置,不随 create/connect 结果变化。
  2. Portal sandbox-server:30083 作为 E2B 数据面唯一入口,完成 token 目标校验、resource credential 恢复、按 ClusterID 解析固定入口和 envd 到 native execd 的转换。
  3. 每个算力集群只部署固定入口和上游原生 OpenSandbox Ingress;PodIP 始终留在算力集群 Overlay 内。
  4. 沙箱模板可配置默认入口白名单与默认出口白名单,并在基于模板创建实例时映射为沙箱实例初始网络策略;创建请求可选 network 对象按维度覆盖模板默认值(不传或某维度留空即继承该维度)。
  5. 沙箱实例创建后允许按实例维度独立修改入口白名单与出口白名单;模板默认值不反向覆盖已存在实例,创建时用户显式覆盖值以用户为准。
  6. 入口运行时统一采用 Portal → beyond-elb:80 → OpenSandbox Ingress Service:80 → OpenSandbox Ingress Pod:28888 → 当前 Sandbox PodIP:44772;当前代码不为每个 Sandbox 创建 sandbox-ingress-{sandboxID}、后端 alias Service 或 alias Endpoints。入口白名单不通过每个 Sandbox 的 Ingress 对象承载。
  7. 当前 E2B 能力严格对齐现有代码:Process Start、文件读写删除、目录列表和 Remove。
  8. 出口策略采用默认拒绝、平台 DNS 放行和用户目标 IPv4/CIDR-only 规则,并以 Kubernetes 对象事实完成验收。
  9. noVNC、CDP、自定义 HTTP/WS 与 E2B 数据面保持独立发布和回滚。

1.4 非目标

  1. 不修改或 fork 上游 OpenSandbox Ingress。
  2. 不把 Portal 到 PodIP 直连作为正式路径或回退路径。
  3. 不提供任意原始 TCP/UDP 代理;业务端口范围为 HTTP/HTTPS 与 WS/WSS。
  4. 不在本文设计模板默认策略变更后对存量 Sandbox 的批量追平或强制覆盖。
  5. 不在本文设计存量 Sandbox 的 CNI 准入失效处置。
  6. 不在本文引入共享大 Ingress 的手工 snippet 聚合、自定义 nginx 模板渲染或 fork OpenSandbox Ingress 作为第一版实现前提。
  7. 不扩展当前代码未注册的 E2B 运行时接口。
  8. 不考虑 generic credential、客户端选址、跨集群 Sandbox 扫描或 GetProviderForInstance(sandboxID);本设计只覆盖绑定资源规格、资源组、Namespace 和算力集群的 resource credential。

1.5 术语说明

术语定义
Portal Control APIsandbox-server 提供的生命周期、查询、connect、网络管理和模板默认网络策略配置接口;创建时由 API Key 对应的 enabled resource credential 提供资源事实和 ClusterID。
resource credential已绑定 ResourceSpecID、ResourceGroupID、ProjectNamespace 和 ClusterID 的凭据;本设计中是创建与运行时集群选择的唯一事实源。
模板默认网络策略维护在沙箱模板上的默认入口白名单与默认出口白名单配置;基于模板创建沙箱时映射为该沙箱的初始网络策略。
沙箱实例网络策略沙箱实例当前生效或待校验的入口白名单与出口白名单;创建时以模板默认值或创建请求 network 覆盖值初始化,创建后允许按实例独立修改。
Portal E2B 数据面sandbox-server:30083,接收官方 SDK operation-only 请求,校验 E2B 路由 Header 和 token 目标,从 CredentialID 恢复 enabled resource credential,并按 SandboxContext.ClusterID 调用 execd Adapter。
execd AdapterPortal 内的协议适配层,把已支持的 envd 操作转换为 native execd HTTP API,并使用凭据绑定算力集群的固定入口。
固定入口每个算力集群稳定的 beyond-elb 地址,接收 Portal 下游请求 业务端口请求,并统一转发至 OpenSandbox Ingress Service:80;不创建 Sandbox 专属 Ingress。
OpenSandbox Ingress上游 github.com/alibaba/OpenSandbox server/v0.2.2 的原生 URI 路由组件,按 sandboxID + port 动态解析当前 Pod。
逻辑服务 49983官方 E2B envd 服务标识,由 E2b-Sandbox-Port Header 携带;不是 SDK 实际 TCP 目标或 execd 监听端口。
native execd 44772Sandbox Pod 内 native execd 的实际监听端口。
allocation identity每次 Sandbox 分配的唯一标识,通过 BatchSandbox CR Pod 模板标签和 NetworkPolicy selector 关联。
LKGLast Known Good,最后一次通过对象级校验的网络策略规范。
控制面探测源Portal 就绪探测经固定入口 NAT 后,入口侧实际观察到的源 CIDR(166/cluster-4 实测 172.28.100.31/32);按 5.2.2 设计由平台自动发现并仅合并进入口白名单生效值,用户与运维无需配置(已实现并部署 166)。
configuredIngress当前实例入口规则的控制面状态,用于诊断 fixed-entry/OpenSandbox Ingress 路由事实;不对应 Sandbox 专属 Ingress 对象。
configuredEgress当前 NetworkPolicy spec 反编译得到的出站规则。
appliedIngress当前实例入口规则的已应用状态;固定入口统一进入 OpenSandbox Ingress Service,不创建 Sandbox 专属 Ingress。
lastVerifiedIngress最后一次通过控制面校验的 LKG 入站规则;仅表示策略状态已确认,不代表每个数据包实时行为。
lastVerifiedEgressNetworkPolicy 注解中保存的 LKG 出站规则。
CNI 准入记录Portal 既有集群配置中的集群级能力事实,绑定 clusterId、CNI 实现、版本和配置指纹。

2. 整体系统架构设计

2.1 (历史架构,废弃)跨集群整体架构(Portal 集群 + 算力集群)

历史架构:控制与运行时均经过 Portal,execd-only 曾尝试跨集群直连 PodIP(非 2026-09-08 现状)

算力集群
Portal 集群
创建 查询 connect
commands files PTY
生命周期管理 可达
跨集群直连 PodIP 不可达
Kubernetes API Server
OpenSandbox Controller
Sandbox Pod
PodIP execd 44772
E2B API
生命周期与运行时接口
E2B Facade
Cluster Manager
放置与 clusterId
commands files PTY
Portal 代理
Kubernetes Client
历史问题
Portal 无法路由算力集群 PodIP
no route timeout 1004

历史问题结论:Portal 能管理 Kubernetes 资源,但曾因直连算力集群 PodIP 失败导致运行时请求失败。当前正式主路径使用 fixed-entry,不再以跨集群 PodIP 可达为前提。

当前正式主链路与目标架构:Portal 协议转换,经 fixed-entry 访问 execd(兼容差异见 3.3)

算力集群
Kubernetes 控制组件
固定入口
原生 OpenSandbox Ingress
Sandbox Overlay
Portal 集群
sandbox-server
Watch
创建管理
sandboxID and port URI
Overlay 内当前 PodIP and selected port
允许 DNS
生命周期与管理
Process Filesystem
六条已支持 operation
http 固定IP 入口端口
sandboxID 44772 native execd path
noVNC CDP 自定义 HTTP WS
直达所属算力集群
跨集群管理资源
管理实例出口策略
不创建 Sandbox 专属 Ingress;入口统一由 OpenSandbox Ingress 路由
策略允许
Sandbox Pod
execd 44772 与业务端口
CoreDNS
Ingress Service 80
Ingress Pod 28888
sandboxID 与 port 动态路由
Ingress 与 beyond-elb
固定 IP 与入口端口
Kubernetes API Server
BatchSandbox CR
OpenSandbox Controller
OpenSandbox Ingress Service
共享固定入口 port 80 to Pod port 28888
NetworkPolicy
CNI
E2B Control API
生命周期 查询 connect 网络管理
E2B 认证鉴权
E2B Facade
Cluster Manager
放置与 clusterId
模板默认网络策略 API
模板到实例策略映射
30083
E2B envd 数据面入口
execd Adapter
envd 转 native execd
逻辑 49983 选择下游 44772
沙箱入口白名单控制器
NetworkPolicy Adapter
OpenSandbox Adapter
Kubernetes Client
官方 Python e2b 2.24.0 与 HTTP WS Client
用户允许的出站目标

结论:当前支持的 E2B Process、Filesystem 访问 Portal sandbox-server:30083;Portal 内的 execd Adapter 完成 envd→native execd 转换,再通过算力集群固定入口和原生 OpenSandbox Ingress 路由到当前 Pod 的 44772。图中不再设置算力集群独立的 E2B Data Plane Gateway。模板默认入口/出口策略在创建实例时映射为沙箱实例的初始网络策略;noVNC、CDP 和自定义 HTTP/WS 仍直达算力集群固定入口。

2.2 程序架构图后的模块职责表

当前版本能力以 3.3 的路由矩阵为准,PTY 不属于已支持六条 operation。当前图表示 E2B 正式主路径与逻辑职责,Native Browser 独立链路见 4.4,token 路由收敛及发布边界见 3.3;创建网络编排的 InitialNetwork 范围及 CNI/DNS 配置边界见 5.3。

模块负责是否自研
E2B Control APISandbox 生命周期、查询、connect、网络管理,以及模板默认网络策略配置入口;create 请求解析可选 network 覆盖对象是。 需要承载产品级 Sandbox 生命周期语义、鉴权、租户隔离和模板到实例的策略映射责任;create 的 network 适配见 3.1.1。
connection factory调用官方 Sandbox.connect(),并通过公开 headers 参数为返回的运行时对象安装 E2b-Sandbox-Id 和 E2b-Sandbox-Port部分自研。 复用官方 Python e2b==2.24.0 SDK,自研连接封装层以补齐运行时 Header 契约;SDK 可替换,封装边界保持稳定。
Credential Repository按数据面 token 中的 CredentialID 重新读取 enabled resource credential,并恢复最新 SandboxContext是。 需要掌握凭据启停、资源绑定和安全校验语义,不能泄漏底层存储细节到公共契约。
Cluster Manager按凭据绑定的 ClusterID 取得 Provider、集群客户端与固定入口元数据是。 需要统一管理集群放置、Provider 解析和固定入口元数据,是平台治理边界。
模板默认网络策略映射器在模板创建或修改时保存默认入口/出口策略,并在基于模板创建实例时映射为沙箱实例初始策略;创建请求携带 network 对象时按维度以用户值为准是。 需要掌握模板继承、逐维度覆盖/继承和“不反向覆盖存量实例”的产品语义。
Portal E2B 数据面接收官方 SDK 请求;校验 operation、E2B 路由 Header 与 token 目标;恢复 resource credential;按 SandboxContext.ClusterID 调用 execd Adapter是。 需要承担协议转换前的鉴权、目标校验和平台级审计责任。
execd Adapter把六条受支持的 envd 操作转换为 native execd API,并构造已选集群固定入口 URI是。 上游不提供 当前所需的协议治理与目标装配语义,需要自研适配层。
沙箱入口白名单控制器按沙箱维度把实例入口白名单同步到所属算力集群固定入口,并维护 desired/applied 与 LKG 状态;探测源自动发现与合并(5.2.2,已实现并部署 166)归属本模块是。 需要承载产品级沙箱归属、安全边界和失败保底语义;2026-09-01 现场穿刺已证明共享入口可按不同 path location 执行不同来源 CIDR 规则,当前控制器已实现规则编排与状态治理。第一版采用每 Sandbox 一个受管 Ingress 作为正式规则载体,便于回读、审计、删除和失败恢复。
固定入口将 /<sandboxID>/<port>/<upstreamPath> 转发给原生 OpenSandbox Ingress,并按各 Sandbox 专属 Ingress 已生效的 path location 执行对应来源 CIDR 限制否。 复用现有 Kubernetes Ingress / beyond-elb / ingress-nginx 能力。2026-09-01 现场穿刺已验证共享入口可对不同 path location 执行不同 whitelist-source-range。第一版不以修改 ingress-nginx / beyond-elb 的平台部署、全局控制器配置或自定义模板为前提 自研部分仅位于上层的实例入口规则编排、对象回读校验与状态治理。
OpenSandbox Ingress在已选集群内动态解析当前 Sandbox Pod,剥离路由前缀并透明代理 HTTP/WS否。 复用上游 github.com/alibaba/OpenSandbox server/v0.2.2 原生 URI 路由能力,减少自建动态路由组件成本。
OpenSandbox Controller根据 BatchSandbox CR 创建和回收 Pod否。 复用上游 OpenSandbox 控制器能力; 仅在外围做编排与治理,不修改其核心控制循环。
NetworkPolicy Adapter生成、回读、校验和回滚每 allocation 的 Egress NetworkPolicy是。 需要承载 精确出站规则、对象级校验、LKG 和冲突检测语义。
Sandbox execdnative execd 44772 命令和文件 API否。 复用 Sandbox 运行时内已有 execd 能力; 不实现官方 envd wire protocol 兼容端。

2.3 技术选型

层次选型说明
官方客户端Python e2b==2.24.0 + connection factory固定 sandbox_url;create 对象由 SDK 自动携带路由 Header;reconnect-by-ID 由 factory 通过公开 headers 参数补齐运行时 Header
Portal 数据面sandbox-server:30083 + Credential Repository + execd Adapter统一协议入口、token 目标校验、resource credential 恢复、按 ClusterID 解析固定入口和协议转换;不按算力集群部署 Gateway
算力集群入口Kubernetes Ingress / beyond-elb每集群固定 IP/端口;承载 Portal native execd 下游和业务端口请求。第一版直接复用现有共享入口能力,不以修改平台部署、全局控制器配置或数据面代码为前提。
Sandbox 动态路由OpenSandbox server/v0.2.2 --provider-type batchsandbox --mode uri保持上游原生实现,动态解析当前 Pod
出站限制Kubernetes networking.k8s.io/v1 NetworkPolicy每 allocation 一条 Egress 策略;对象级校验与 LKG
CNI 能力门禁Portal 既有集群配置记录与 CNI 指纹绑定的集群级准入结果

3. E2B 网络接口设计

3.1 E2B Control API

Portal Control API 保持生命周期、查询、connect 和网络管理职责,并新增模板默认网络策略配置入口。创建请求的 API Key 必须对应可用用户访问凭证;Portal 从该凭据恢复包含 CredentialID、ResourceSpecID、ResourceGroupID、ProjectNamespace 和 ClusterID 的 SandboxContext,并直接按 ClusterID 选择 Provider。客户端不能通过 placement、URL、Header 或其他输入改变凭据绑定的集群;请求携带的集群或 Namespace 信息若与凭据事实不一致则拒绝。

sandbox_url 不是 Sandbox 返回字段 在创建 Sandbox 前固定配置 http://<portal-authority>:30083,create/connect 不返回、不替换也不切换该地址。

锁定 SDK 的 connect 控制请求会携带两个 E2B 路由 Header,但其返回的运行时对象默认只安装 X-Access-Token。因此 reconnect-by-ID 必须由 connection factory 调用官方 Sandbox.connect(),并通过公开 headers 参数把 E2b-Sandbox-Id 和 E2b-Sandbox-Port 安装到返回对象的运行时连接配置;直接使用未传这两个 Header 的 class-level connect 运行时对象不在本期支持范围。

方法路径说明
POST/sandboxes使用 API Key 对应 resource credential 的 ClusterID 和资源事实创建 Sandbox;创建时默认继承模板入口/出口策略,可选 network 对象按维度覆盖(见 3.1.1 二);enforced 模式同步完成实例网络对象校验
GET/sandboxes/{sandboxID}查询已授权 Sandbox 的生命周期和网络对象状态;返回业务端口入口投影(见 4.4),不作为 Portal E2B 运行时集群选择依据
POST/sandboxes/{sandboxID}/connect连接已存在 Sandbox;不发布新的数据面 URL
DELETE/sandboxes/{sandboxID}删除 Sandbox 并收敛关联入口白名单与 NetworkPolicy
PUT/sandboxes/{sandboxID}/network精确全量替换沙箱实例出口策略;PodUnavailable 时仍允许
GET/sandboxes/{sandboxID}/network查询沙箱实例当前出口配置与 LKG(Last Know Good),不触发探测或写入

3.1.1 更新 Sandbox 出站网络策略

一、 精确规则模型
PUT /sandboxes/{sandboxID}/network
Content-Type: application/json
{
  "defaultAction": "deny",
  "egress": [
    {
      "cidr": "203.0.113.10/32"
    }
  ]
}

规则约束:

  1. defaultAction 本期固定为 deny。
  2. cidr 仅接受规范化 IPv4 CIDR。
  3. 用户规则仅按目标 CIDR 放行,不限制协议或端口;用户请求不接受旧 protocol/port 字段,允许 CIDR 即在策略层允许到该目标的全部协议与端口(不保证目标监听或路由可达)。
  4. 请求代表完整 desired 规则集,不是增量 patch。
  5. 平台固定补充受控 CoreDNS UDP/TCP 53 规则;用户不能覆盖保留目标限制。
二、创建请求 network 对象与官方 E2B 网络模型兼容边界(2026-09-17 已实现)

2026-09-17 代码事实(internal/e2b/dto.go 的 CreateSandboxRequest.Network + resolveCreateNetwork,166 已部署验证,27 项检查 PASS):POST /sandboxes 接受可选 network 对象,对官方 SDK create 网络字段做子集适配,并扩展 原生维度:

{
  "templateID": "e2b-test",
  "timeout": 1800,
  "network": {
    "allowPublicTraffic": false,
    "allowOut": ["203.0.113.0/24", "198.51.100.7"],
    "ingress": [
      {"cidr": "203.0.113.0/24", "description": "办公网"},
      {"cidr": "172.28.100.31/32", "description": "控制面探测源(历史版本需人工包含;5.2.2 部署后无需传入,平台自动发现)"}
    ]
  }
}

字段处理如下(注意官方 SDK Python 侧为 snake_case,HTTP JSON 为 camelCase):

字段来源(已实现)
allowOut官方接受;每个地址按 normalizeIPv4CIDR 归一化(单 IP 补 /32、掩码对齐、去重、排序)后作为实例初始 egress 白名单,覆盖模板出站维度
allowPublicTraffic官方接受;true 时在出站白名单并入 0.0.0.0/0(与 allowOut 同时出现时取并集),false 或缺省不产生额外效果(出站是否放开完全由白名单决定)
ingress扩展官方 schema 无入站维度;接受 [{cidr, description?}],归一化后覆盖模板入口白名单
egress原生接受 [{cidr}];与 allowOut/allowPublicTraffic 同时出现时以本字段为准(原生优先)
denyOut / egressProxy / maskRequestHost / rules官方平台不实施,静默忽略——DTO 不定义这些字段,JSON 绑定直接丢弃,不报错、不产生效果

覆盖/继承语义按维度独立判断:

  1. 不传 network → 入口、出口整体继承模板默认网络策略(一次性复制,此后与模板脱钩)。
  2. 传了 network 但某维度留空(如只给 allowOut、ingress 缺省或空数组)→ 该维度仍继承模板;填了的维度以用户为准。
  3. 用户显式传入的值服务端先归一化再生效(归一化是必须的:下游 carrier/NP 回读比较为有序比较,未归一化输入会导致 Verified 误判)。
  4. 非法 CIDR → 400(扁平 E2B 错误信封):allowOut/egress 报 network_cidr_invalid,ingress 报 ingress_cidr_invalid。
  5. 显式覆盖 ingress 时,按 5.2.2 的探测源自动发现设计,无需包含控制面探测源——平台自动发现探测源并仅合并进入口白名单生效值(已实现并部署 166,2026-09-23 回归通过)。历史版本(digest 84017e17… 及之前)要求白名单包含探测源(166 为 172.28.100.31/32,即 Portal 经固定入口探测沙箱 44772 的 NAT 后源地址),否则 readiness 门禁被自己的白名单挡成 403、创建超时回滚;该人工约束已解除。

实例网络 PUT(PUT /sandboxes/{sandboxID}/network)的未知字段语义不变:返回 bad_request,不是 unsupported_network_model。该 PUT 契约与 create 的 network 对象是两套独立模型:前者是实例运行期全量替换(egress-only),后者是创建期初始值(ingress+egress)。官方 rules 是 HTTP Header transform 语义,与 CIDR 网络放行不同类,即使后续考虑支持也不得转换为 NetworkPolicy。

三、PUT 对象校验与 LKG(Last Know Good)

写入流程只验证 Kubernetes 对象事实:

  1. 读取 BatchSandbox CR 和当前 NetworkPolicy,确定 allocation identity 与旧 LKG。
  2. 从 BatchSandbox CR spec.template.metadata.labels 取得预期标签集,必须包含预期 allocation label。
  3. 编译规范化 NetworkPolicy,先将状态置为 Verifying,再写入对象。
  4. 回读 apiserver 返回的规范化 spec,要求与 desired 等价。
  5. 静态校验 podSelector 与 CR 模板 allocation 标签一致;存在当前 Pod 时附加确认实际命中,Pod 不存在不阻止校验。
  6. 按 selector 语义枚举所有可能选择该模板标签集的 NetworkPolicy,发现非平台策略造成的并集扩权时返回 PolicyConflict。
  7. 对新的 enforced PUT,要求目标集群 CNI 准入记录有效;该门禁不参与存量对象的只读状态计算。
  8. 校验通过后,将规范化 spec 写入 last-verified-spec,并写入 Verified 和空错误码;再回读确认。
  9. 任一步失败时回滚旧 LKG 并回读;成功回滚返回“更新失败、已恢复 LKG”,回滚失败返回“更新失败、状态不确定”。PUT 前不存在旧 LKG(首次为存量 Sandbox 建立平台策略)时,回滚动作为删除本次新建的 NetworkPolicy 并回读确认,不保留未通过校验的策略。

当前 Pod 不存在时仍允许 PUT。selector 和策略并集均使用 CR 模板预期标签静态求值,不依赖具体 Pod 实例,也不执行 Sandbox 内行为验证。

networkReady=true 仅表示:

verificationState == Verified
AND current NetworkPolicy spec == LKG
AND selector 与 allocation 模板标签一致
AND 不存在选择同一标签集的冲突策略

该值不表示实时数据包已经按规则转发。

四、GET 语义
GET /sandboxes/{sandboxID}/network
{
  "sandboxID": "e2b-example",
  "networkReady": true,
  "verificationState": "Verified",
  "configuredEgress": [
    {"cidr": "203.0.113.10/32"}
  ],
  "lastVerifiedEgress": [
    {"cidr": "203.0.113.10/32"}
  ],
  "lastErrorCode": ""
}
  • configuredEgress 来自当前 NetworkPolicy spec。
  • lastVerifiedEgress 来自 LKG 注解。
  • 两者均是对象配置事实,不承诺数据面“当前实际生效”状态。
  • GET 只读,不推进状态、不回滚,也不触发额外校验动作。
  • NetworkPolicy 缺失、注解损坏或对象无法确定时返回稳定错误分类和 networkReady=false,不伪造空规则为有效配置。

3.1.2 固定 E2B 数据面地址

在创建 Sandbox 前配置:

sandbox_url = http://<portal-authority>:30083

必须使用不带路径前缀的 authority-only 地址。官方 SDK 的文件 URL 构造包含:

urllib.parse.urljoin(envd_api_url, "/files")

根相对 /files 会丢弃 base URL 的路径前缀,因此不得使用 http://<portal>:30083/e2b 等形式。

create/connect 只建立或查询 Sandbox,不返回、不替换也不切换 sandbox_url。create 返回对象由官方 SDK 自动安装两个路由 Header;reconnect-by-ID 使用以下 factory 调用,在对象构造时通过公开 headers 参数补齐同一运行时契约:

Sandbox.connect(
    sandbox_id,
    sandbox_url="http://<portal-authority>:30083",
    headers={
        "E2b-Sandbox-Id": sandbox_id,
        "E2b-Sandbox-Port": "49983",
    },
    ...,
)

ConnectionConfig 会把公开 headers 与 connect 返回的 X-Access-Token 合并到 sandbox_headers,Process/Filesystem 客户端构造时即可获得完整 Header。两个 E2B 路由 Header 标识请求的 Sandbox 和逻辑服务,X-Access-Token 用于恢复 resource credential;运行时集群由重新读取的 SandboxContext.ClusterID 决定;Header 不可选址,token 字段移除及旧 token 语义见 3.3。未通过 factory 补齐 Header 的 class-level connect 运行时对象不受支持。

3.2 模板默认入口白名单与实例继承设计

入口白名单的作用对象从“算力集群固定入口”调整为“沙箱模板默认值 + 沙箱实例配置”。平台管理员在新增或维护沙箱模板时配置默认入口白名单来源 CIDR;用户基于模板创建沙箱时,系统将模板默认值映射为该沙箱实例的初始入口白名单,创建请求 network.ingress 显式覆盖时以用户值为准(见 3.1.1 二)。实例创建后允许再按沙箱维度单独修改,且模板后续变更不反向覆盖已创建实例。

它覆盖两类正常来源:Portal execd Adapter 的出口 CIDR,以及允许直达 noVNC/CDP/自定义 HTTP/WS 的 浏览器来源 CIDR。实际下发时仍落到所属算力集群的固定入口,但配置拥有者和控制粒度以模板/沙箱实例为准,而不是集群整表。

方法路径说明
PUT/sandbox-templates/{templateId}/network新增或更新模板默认入口白名单与默认出口白名单
GET/sandbox-templates/{templateId}/network查询模板默认网络策略
POST/sandboxes基于模板创建沙箱时,自动继承模板默认入口/出口策略作为实例初始值;可选 network 对象按维度覆盖(见 3.1.1 二)
PUT/sandboxes/{sandboxID}/network/ingress全量替换沙箱实例入口白名单
GET/sandboxes/{sandboxID}/network/ingress查询沙箱实例当前入口白名单与最后一次校验通过配置

模板默认网络策略和实例入口白名单均复用 Portal 既有存储保存 desired/applied、版本与 lastError。第一版正式规则载体采用“每 Sandbox 一个受管 Ingress 对象”, 通过 Kubernetes API 在目标算力集群创建/更新标准 Ingress 资源,再由共享 beyond-elb/ingress-nginx 在同一 fixed-entry 下合并为 path location;不把手工共享 snippet 作为主实现。实例入口配置的 Applied 以 apiserver 回读到的受管 Ingress 对象为准,而 fixed-entry 负责消费这些已生效对象并表现为对应 path location。2026-09-01 现场专项穿刺已证明,当前 beyond-elb/ingress-nginx 共享入口可以针对不同 path location 执行不同 whitelist-source-range,因此把 /<sandboxID>/ 路径前缀映射为实例级入口白名单具备关键数据面前提(历史验证证据)。

本设计中:

  • Desired:Portal 持久化的实例目标入口规则;
  • Applied:从 apiserver 回读并规范化后的 Sandbox 专属 Ingress 规则;
  • Verified:Applied == Desired,且 Ingress 的 owner、ingress class、host/path、backend 和 sandbox 身份均与预期一致;
  • LKG:最后一次通过上述对象级校验的 Applied 规则;
  • configuredIngress:当前 Ingress 对象反编译得到的事实规则,仅用于诊断;
  • nginx 最终生成配置与现场 200/403 行为属于专项验收和故障排查证据,不作为日常控制面 read-back 来源。

模板映射、实例下发或回读失败时保留上一份实例级 Applied 配置,不自动全放行,也不回退为模板最新值。当前模板 network 与实例 ingress GET/PUT 均已实现。模板表中路径省略 /api/v1/sandbox 前缀,另有 /api/v1/sandbox/templates/{templateId}/network 别名。网络子资源没有独立 DELETE,随 Sandbox 删除清理对象。历史 /admin/clusters/{clusterId}/ingress-allowlist 是 cluster 级整表模型,当前路由未注册,不能与实例 ingress API 混同。

3.3 Portal E2B 数据面入口与 execd Adapter

Portal sandbox-server 在 30083 接收官方 Python e2b==2.24.0 请求。create 返回对象或经 connection factory 连接的对象只在固定 sandbox_url 后追加 operation path,目标由两个 E2B 路由 Header 携带:

POST http://<portal-authority>:30083/process.Process/Start
E2b-Sandbox-Id: <sandboxID>
E2b-Sandbox-Port: 49983
X-Access-Token: <runtime-token>
Content-Type: application/connect+json

E2b-Sandbox-Port: 49983 是 envd 逻辑服务标识。Portal 只接受当前支持的逻辑服务和 operation,不把 49983 用作下游 TCP 端口。

当前能力矩阵:

SDK/协议操作Portal 路由native execd 下游
Process StartPOST /process.Process/StartPOST /command
文件读取GET /files?path=...GET /files/download?path=...
文件写入POST /filesPOST /files/upload
文件删除DELETE /files?path=...DELETE /files?path=...
目录列表POST /filesystem.Filesystem/ListDirGET /directories/list?path=...&depth=...
Filesystem RemovePOST /filesystem.Filesystem/Remove根据目标类型调用 native 删除 API

除上表外的 envd operation 不属于本期范围,数据面返回明确 unsupported/unimplemented 错误。

请求目标解析如下(当前工作区代码契约,部署边界见本节末):

  1. 读取并校验 operation、E2b-Sandbox-Id 和 E2b-Sandbox-Port: 49983。

  2. 从 X-Access-Token 解码 CredentialID、SandboxID 和逻辑端口,校验 token 签名与有效期。

  3. 要求 claims 中的 SandboxID 和端口与请求 Header 目标一致;sandboxID 不用于选择集群。

  4. 按 CredentialID 重新读取 resource credential,要求凭据仍为 enabled,并转换为最新 SandboxContext。

  5. 以 SandboxContext.ClusterID 作为唯一集群事实,取得该集群当前 Provider 及其固定入口;Provider 配置刷新边界见本节末。

  6. execd Adapter 把 operation 转为 native execd path,并构造:

    http://<compute-cluster-fixed-entry>/<sandboxID>/44772/<native-execd-path>
    
  7. 将 OpenSandbox Ingress 和 execd 的当次响应沿原链路返回。

E2B runtime 路径不依赖 Portal watch/cache PodIP 来路由,当前 Pod 由原生 Gateway 解析。但生产 cmd/server/wiring.go 装配 ProviderAuthorizer(access/authorize.go),仍执行 ownership/running 校验并可缓存授权结果,不能宣称完全不按生命周期阻断运行时。Header/token 目标校验失败、凭据不存在或 disabled、凭据绑定集群或固定入口不可用、Ingress 无法解析 Sandbox、execd 返回错误时,分别返回稳定的请求校验、凭据、目标、网络、路由或执行错误;不回退裸 PodIP,也不按 sandboxID 跨集群发现 Provider。

2026-09-08 token 路由代码收敛:create/connect 均不再签发 EndpointBinding;token payload、claims、request context 及 execd Adapter 的专属 binding 签发、传递、消费和校验已移除。运行时按 CredentialID reload enabled credential,以最新 SandboxContext.ClusterID 选择 Provider,并从当前 Provider 取得 fixed-entry;不从 token 地址选址,也不回退 token 专属 native resolver。 Browser/Provider 原生绑定、身份签名和归属校验保留。签名版本与 signing key 未改变;旧 token 中的额外 endpointBinding 字段被忽略,不作为兼容选址输入。字段删除不是自动吊销:旧 token 仍须通过签名、有效期、claims/request 目标一致性、credential 启用及归属等既有校验;不承诺所有旧 token 均可用。

配置与发布边界:入口变化以当前 Provider 已更新为前提;数据库配置变更但 Provider 缓存尚未刷新时,不保证请求立即使用数据库新地址。credential reload 不等于每次请求直接重读集群数据库配置。2026-09-17 已将含 create network 适配的工作区部署至 166 并通过 27 项在线验证(见文档头部现状基线);该部署未 commit 代码,也未轮换密钥或修改签名版本。此前镜像及在线 PASS 仅证明其原日期和路径。详见同目录架构核对报告 §16。

3.4 原生 OpenSandbox Ingress 契约

算力集群使用未经修改的上游 OpenSandbox Ingress:

server/v0.2.2
--provider-type batchsandbox
--mode uri

统一 URI 契约:

/<sandboxID>/<port>/<upstreamPath>

两类调用均使用同一原生契约:

Portal execd Adapter
→ /<sandboxID>/44772/<native-execd-path>
 business-port client
→ /<sandboxID>/<6080|9222|custom>/<upstreamPath>

Portal 在请求到达固定入口前,已经由 resource credential 的 SandboxContext.ClusterID 选定算力集群。OpenSandbox Ingress 只在该集群内按 sandboxID 动态解析当前 PodIP,剥离 /<sandboxID>/<port> 前缀并保留剩余 path、method、query、HTTP 流和 WebSocket Upgrade。它不解析 E2B Header 或凭据,不执行 envd 到 execd 转换,也不参与跨集群选址。

4. 核心流程设计

4.1 Sandbox 创建流程(模板默认网络策略映射到实例)

 E2B HandlerCredential Repository创建编排Cluster Manager模板策略映射器实例入口白名单控制器NetworkPolicy Adapter算力集群 Kubernetes APIOpenSandbox ControllerAPI Key 加 POST /sandboxes(templateId 可选 network)读取 enabled resource credentialSandboxContext 含资源事实与 ClusterID创建请求、templateId 与 SandboxContext读取模板默认入口/出口策略模板默认网络策略解析可选 network 覆盖 逐维度合并并归一化按 SandboxContext.ClusterID 取得 Provider凭据绑定集群 Provider生成实例入口白名单 desired创建或更新 Sandbox 专属 Ingress 对象apiserver 回读成功或失败用模板默认出口规则创建实例基线 NetworkPolicy写入并规范化回读NetworkPolicy 对象对象校验结果alt[enforced 模式]创建 BatchSandbox CR 含 allocation 模板标签Reconcile CR创建 Sandbox PodPod  E2B HandlerCredential Repository创建编排Cluster Manager模板策略映射器实例入口白名单控制器NetworkPolicy Adapter算力集群 Kubernetes APIOpenSandbox Controller

Sandbox 创建时先读取模板默认网络策略;请求携带 network 对象时按 3.1.1 二的逐维度覆盖/继承语义解析出实例初始入口/出口策略(不携带则整体继承模板)。推荐执行顺序为:先持久化实例入口 Desired,再创建并回读 Sandbox 专属 Ingress 规则,待其达到对象级 Applied/Verified 后,再创建并回读出口 NetworkPolicy,然后创建 BatchSandbox CR、回填并校验两类对象 ownerReference,最后执行 fixed-entry execd 就绪检查。网络预创建前检查当前 CNI 指纹准入。实例入口白名单下发失败时保留该实例上一份有效配置;首次创建且不存在上一份有效配置时,本次创建失败并回收已创建资源。入口白名单的数据面落点是共享固定入口上按 /<sandboxID>/ 对应的 path location 生成的来源限制规则;该 path 级差异化执行能力已在 166/算力集群现场穿刺验证通过。

如果入口规则已创建成功、但后续 BatchSandbox CR 创建失败,则创建编排必须先尝试删除该 Sandbox 专属 Ingress,再回读确认对象不存在;删除成功后清理本次未提交的实例入口状态并返回失败。若删除结果无法确认,则返回“创建失败、入口状态待清理”的不确定状态,交由孤儿扫描重试;在任何失败路径下都不得签发可用运行时 token,也不得保留放开的入口规则。Sandbox Pod 创建后,创建编排进入 4.2 的 execd 就绪检查;检查通过后签发绑定 CredentialID、SandboxID 和逻辑端口 49983 的运行时 token,再返回创建成功。客户端提供的集群或 Namespace 信息不得改变 resource credential 的绑定事实。

4.2 execd 就绪检查流程(已实现 fixed-entry 跨集群检查)

创建编排Cluster ManagerPortal execd Adapter凭据绑定算力集群固定入口原生 OpenSandbox IngressSandbox Pod execd 44772Portal E2B  ClusterID 读取固定入口凭据绑定集群固定入口用该固定入口检查 sandboxID 的 execd/sandboxID/44772/ping原生 URI 转发Overlay 内 GET 当前 PodIP 44772 /ping200 OKexecd responsefixed-entry responsePortal Adapter response创建成功官方创建响应 不含 sandbox_url 切换创建编排Cluster ManagerPortal execd Adapter凭据绑定算力集群固定入口原生 OpenSandbox IngressSandbox Pod execd 44772Portal E2B 

创建成功只表明本次对象校验和 execd 就绪检查通过。 继续使用创建前已配置的固定 Portal sandbox_url。

对象校验或就绪检查失败时,创建返回明确错误并收敛本次已创建的资源:创建编排先删除并回读确认受管 Ingress 不存在,再删除 BatchSandbox CR、收敛 NetworkPolicy 并确认清理;ownerReference GC 与孤儿扫描作为后续收敛保障;失败的 sandboxID 不签发可用运行时 token,也不再对外提供运行时访问。

4.3 execd 执行流程(已实现正式主路径)

create 或 factory-connect 运行时对象Portal 数据面 30083Credential RepositoryCluster Manager凭据绑定算力集群固定入口原生 OpenSandbox IngressSandbox execd 44772operation-only path 加路由 Header 和 X-Access-Token校验 operation sandboxID 逻辑 49983解码 token 取得 CredentialID SandboxID 49983校验 claims 与请求目标一致按 CredentialID 读取 enabled resource credentialSandboxContext 含 ClusterID按 SandboxContext.ClusterID 读取固定入口凭据绑定集群固定入口envd 操作转换为 native execd path/sandboxID/44772/native-path原生 URI 转发Overlay 内访问当前 PodIP 44772native execd responseOpenSandbox Ingress responsefixed-entry responseE2B responsecreate 或 factory-connect 运行时对象Portal 数据面 30083Credential RepositoryCluster Manager凭据绑定算力集群固定入口原生 OpenSandbox IngressSandbox execd 44772

运行时链路固定经过 Portal 30083。Portal 通过 token 的 CredentialID 重新读取 enabled resource credential,按 SandboxContext.ClusterID 选择当前 Provider 的算力集群固定入口;sandboxID 仅用于 claims/request 一致性校验和已选集群内的原生 URI。当前 Pod 的动态解析由算力集群原生 Ingress 完成。Provider 缓存刷新与本次未部署边界见 3.3。

4.4 noVNC、CDP 与自定义端口访问流程(通用入口与 Native Browser 并存)

 或浏览器所属算力集群固定入口原生 OpenSandbox IngressSandbox Pod 业务端口/sandboxID/6080|9222|custom/upstreamPath原生 URI 转发Overlay 内访问当前 PodIP 业务端口HTTP 或 WebSocket  或浏览器所属算力集群固定入口原生 OpenSandbox IngressSandbox Pod 业务端口

业务端口请求不经过 Portal 30083。客户端通过 GET /sandboxes/{sandboxID} 获得已授权 Sandbox 的业务入口投影;该控制面查询不作为 Portal E2B 运行时的集群选择依据。请求路径中显式包含 sandboxID 和目标业务端口。

端口连接段与 URI 保留(2026-09-08 报告链路)

SDK → Portal :30083(Header 中 49983 仅为 envd 逻辑服务)
Portal → fixed-entry :80/<sandboxID>/44772/<native-path>
→ 该 Sandbox 受管 Ingress path 与来源 CIDR
→ 项目 Namespace/opensandbox-ingress Service:80
→ selectorless Endpoints 指向 opensandbox-system/opensandbox-ingress ClusterIP:80
→ 真实 Service targetPort:28888 → OpenSandbox Ingress Pod:28888
→ 解析 sandboxID、port、upstreamPath → 当前 Sandbox PodIP:44772/<native-path>

fixed-entry 必须保留完整 URI,不得提前剥离 sandboxID;80 → 28888 是真实 Service 的静态 targetPort 映射,28888 → 44772 是 Gateway 解析 URI 后发起的动态 HTTP 代理,不是静态 NAT。业务请求用 6080、9222 或 customPort 替换 URI 目标端口,不能把 49983 当作其中任何监听端口。

业务授权边界与 Native Browser 独立路径

  • 通用 E2B endpoint 投影:baseURL=http://<fixed-entry>:80、uriTemplate=/{sandboxID}/{port}/{upstreamPath},noVNC 6080、CDP 9222;控制面按 credential 与 Sandbox 归属授权后返回。业务请求直接经受管 Ingress 来源 CIDR 到原生 URI Gateway,不经过 Portal :30083,不执行 Portal X-Access-Token 逐请求校验。
  • Native Browser 通过 GET /api/v1/sandbox/instances/<id>/endpoint?port=6080 获取入口;Controller 校验管理员或 tenant/project/namespace/creator 归属,Service/Provider 校验状态和 allocation 绑定。返回 http://<fixed-entry>:80/sandbox/<id>/vnc.html,数据路径为 sb-<id> Ingress → sb-<id> Service:6080 → selectorless Endpoints → 当前 Browser PodIP:6080,不经过通用 URI Gateway。
  • Native binder 校验 BatchSandbox UID、Pod UID、allocation generation 与 resourceVersion;当前 buildIngress 的 WebSocket/长超时/buffering 配置不能证明继承 sandbox-ingress-* 的 whitelist-source-range。控制面授权也不能代替后续 noVNC/WS 请求的身份认证。
  • noVNC 的 HTTP 页面与 WebSocket 经 websockify 转为 Pod 内 VNC 5900;CDP 的 /json/version 与 /devtools/browser/<session> 使用 9222。自定义 HTTP/WS 保留 method/path/query/header/body 与 Upgrade 语义,应用的 Cookie、Bearer token、VNC 密码或消息级权限须由业务进程或明确部署的鉴权代理实现。URI 中的 sandboxID/port 是路由标识,不是认证凭据。
路径endpoint 获取授权后续入口控制Portal runtime token 逐请求校验
通用 noVNC/CDP/custom HTTP/WS /<id>/<port>/...credential 与 Sandbox 归属通用受管 Ingress 来源 CIDR(非空策略);应用鉴权另行负责否
Native noVNC /sandbox/<id>/...admin 或 tenant/project/namespace/creator,加 allocation 绑定校验独立 sb-* carrier,未证明继承实例 CIDR;应用鉴权另行负责否

5. 网络边界设计

5.1 入站端口仅暴露到 Overlay

Sandbox Pod 的 44772、6080、9222 和自定义业务端口只监听在算力集群 Overlay 内。正式通用访问经过该集群 fixed-entry 与原生 OpenSandbox Ingress,Portal 不要求直连 PodIP。Native Browser 是 fixed-entry 后经独立 sb-* Service/Endpoints 的例外路径,不经过原生 URI Gateway(见 4.4);token 路由收敛及发布边界见 3.3。

5.2 沙箱入口白名单

沙箱入口白名单的配置来源分两层:模板默认值和沙箱实例值。管理员在模板上维护默认入口白名单;基于模板创建实例时,系统将默认值映射为该实例的初始入口白名单(创建请求 network.ingress 显式覆盖时以用户值为准),并由 Portal 集群中的 sandbox-server 通过 Kubernetes API 在所属算力集群创建或更新该 Sandbox 专属 Ingress 对象。fixed-entry 运行时只消费这些已生效的标准 Ingress 对象,并按对应 path location 执行该沙箱已 Applied 的来源 CIDR 限制。

第一版正式规则载体明确为:每个 Sandbox 生成一个 受管的 networking.k8s.io/v1 Ingress 对象。该对象与共享 fixed-entry 使用相同的 ingress class、host 和 OpenSandbox Ingress backend,仅独占自身 /<sandboxID>/ 路径前缀,并附带该实例专属的 nginx.ingress.kubernetes.io/whitelist-source-range。之所以优先采用每 Sandbox 一个 Ingress,而不是共享大 Ingress + 手工 snippet 聚合,是因为前者更容易做到按 Sandbox 回读、按 Sandbox 删除、对象 owner 校验、Applied/LKG 对齐和失败补偿。

规则载体虽然是“每 Sandbox 一个受管 Ingress”,但数据面仍表现为共享 fixed-entry 上按 /<sandboxID>/ 路径前缀生成的独立 path location。2026-09-01 在 166 Portal 与 clusterId=4 算力集群完成现场专项穿刺后,已确认当前 beyond-elb/ingress-nginx 共享入口能够对不同 path location 执行不同来源 CIDR 白名单:同一入口下,来源 A 访问路径 A 返回 200、访问路径 B 返回 403;来源 B 访问路径 B 返回 200、访问路径 A 返回 403。因此,按沙箱维度把实例入口白名单投影到共享入口 path 级规则,在数据面上可行。

实例入口状态采用与 egress 类似的对象级语义,但主键改为 sandboxID + clusterID,不再复用现有 cluster 级 IngressAllowlistState 作为最终模型。已实现的实例级字段包括:

  • desiredIngress:Portal 持久化的目标来源 CIDR 集;
  • desiredVersion:实例入口期望版本;
  • appliedIngress:从 apiserver 回读并归一化后的 Ingress 事实规则;
  • appliedVersion:最后一次成功回读的对象版本;
  • configuredIngress:当前 Ingress 对象反编译得到的事实规则;
  • lastVerifiedIngress:最后一次对象级校验通过的 LKG;
  • verificationState:沿用 Pending / Verifying / Verified / UnverifiedSpec / Indeterminate,删除侧使用 Deleting / CleanupFailed;
  • lastErrorCode:最后一次编排或回读失败分类;
  • carrierRef:受管 Ingress 的 namespace、name、host、path、ingressClass 和 backend 标识。

Applied 的权威来源是 apiserver 回读到的受管 Ingress 对象,而不是 ingress-nginx 容器内的最终 nginx.conf。只有当以下条件同时满足时,状态才进入 Verified 并更新 LKG:

  1. 受管 Ingress 存在,且 owner、management label、cluster/sandbox 身份匹配;
  2. ingress class、host、/<sandboxID>/ path、OpenSandbox Ingress backend 与预期一致;
  3. whitelist-source-range 反编译后的规范化 CIDR 集与 Desired 等价;
  4. 不存在另一个 Ingress 同时声称同一 sandbox 路径的归属。

Portal 出口 CIDR 用于 execd Adapter 的正常运行时转发和创建就绪检查 浏览器来源 CIDR 用于 noVNC、CDP 与自定义业务端口直达。来源 CIDR 必须以 fixed-entry 实际观察到的 northbound NAT 后地址为准,不能直接假设为 Portal 节点地址。其中控制面探测源(166 为 172.28.100.31/32)自 2026-09-23 部署(digest sha256:d7fd300e…)起由平台按 5.2.2 自动发现并合并进入口白名单生效值,用户与运维无需感知或配置;白名单不含该来源时,发现机制会在应用入口规则时自动并入,就绪门禁不再被自身白名单挡为 403。实例入口白名单更新失败时,如果存在旧 LKG,则保留旧 Applied/LKG 并仅推进 DesiredVersion 与错误状态;不自动放开来源,也不回退为模板最新值。模板默认值更新只影响后续新建实例;第一版不支持自动追平已创建实例。

删除路径采用 fail-closed 顺序:先撤销并确认删除 Sandbox 专属 Ingress,再删除/收敛 BatchSandbox CR 与关联 NetworkPolicy。若入口规则删除无法确认,则实例保持 Deleting 或 CleanupFailed,拒绝正常访问并交由重试与孤儿扫描继续收敛;即使后续 CR 删除失败,也不得把入口规则恢复为可访问状态。

5.2.1 当前实现与保留差异(2026-09-17)

  1. internal/model/template.go 已有 Network 字段,模板服务保存到 ConfigMap template.json;internal/router/router.go 注册模板 network GET/PUT。创建时 internal/e2b/facade.go 经 InitialNetwork 传给 Provider,模板后续变更不覆盖已有实例;internal/e2b/dto.go 的 SandboxNetworkSpec/resolveCreateNetwork 解析可选 network 覆盖对象(见 3.1.1 二),平台侧实例 network/ingress 四路由(internal/router/router.go 的 instances.GET/PUT /:id/network 与 /:id/network/ingress)同批可用。
  2. internal/e2b/router.go 已注册 egress /network 与 ingress /network/ingress GET/PUT;internal/provider/network.go 实现 per-Sandbox 受管 Ingress、per-allocation CIDR-only NetworkPolicy、对象回读、LKG/回滚与冲突检测。不存在独立 DELETE network API。
  3. internal/provider/network_lifecycle.go 已实现网络预创建、CNI 准入、CR 后 ownerReference 绑定、readiness 对象校验、失败同步补偿及删除收敛;不能继续列作“当前未实现”。
  4. 旧 cluster 级 allowlist(历史 D-35 整表语义)与现在实例级 CIDR 不是同一契约,旧管理路由当前未注册。实例状态不复用旧 cluster 整表模型。
  5. Native Browser 的 /sandbox/<id>/... → sb-* 是独立 carrier,当前未证明继承实例 ingress CIDR;该差异继续保留。3.3 所述 token EndpointBinding 路由已在工作区代码移除,不影响 Browser/Provider 原生后端绑定,也不代表新代码已部署。
  6. 当前空模板 ingress 允许创建不带 whitelist 注解的入口(来源不受限);实例 ingress PUT 拒绝空数组,避免清空现有白名单。历史“空入口默认 fail-closed”不是当前事实;失败不得主动扩权,与首次空模板创建的语义需分开理解。
  7. 5.2.2 的探测源自动发现于 2026-09-23 设计定稿并获批准,代码已实现、已过全量单测,并已部署 166 完成真实回归(digest sha256:d7fd300e…,14 项 PASS,证据 dist/probe-src-20260923/regress-report.json) ;线上不再要求用户/模板白名单人工包含探测源。存量模板(如 4ef1c3dd)network ingress 中人工写入的 172.28.100.31/32 可在方便时移除——用户显式包含的探测源 CIDR 会继续显示在 GET 中(用户语义保持),不移除也不影响功能。

5.2.2 控制面探测源自动发现与白名单合并(2026-09-23 已实现并部署 166,回归通过)

一、问题

就绪门禁(CheckNetworkReadiness)由 Portal 经共享固定入口探测 /<sandboxID>/44772/ping。nginx/beyond-elb 按其自身观察到的源地址执行 whitelist-source-range,而 Portal 出口经 NAT 后呈现为入口侧地址(166/cluster-4 实测 172.28.100.31)。因此用户白名单若只写自己的客户端 CIDR,探测请求会被挡为 403,readiness 门禁失败并触发创建回滚。现行方案要求用户/模板把探测源手工写进白名单——每个环境探测源不同,用户与运维均需感知平台内部地址,体验差且易错。

二、设计目标

  1. 零配置:用户与运维不需要感知、发现或配置探测源;不新增部署项、环境变量或集群配置。
  2. 口径一致:发现结果即 nginx 实际判定的源地址——探测行为与白名单执行走同一入口、同一 NAT 路径。
  3. 对外不可见:探测源只合并进入口白名单生效值,不出现在用户面向的 desired/configured/LKG 规则中;GET 不泄漏平台内部地址。
  4. 语义保持:无用户规则(不受限沙箱)仍不携带 whitelist 注解;探测源合并不得把不受限沙箱变为受限。
  5. 重启安全:合并事实自描述在受管 Ingress 注解中,回读比较不依赖进程内缓存;服务重启后不产生假漂移。

三、发现机制(临时 echo Ingress 自拨)

Provider 适配层(OpensandboxAdapter,每集群一份缓存)在需要探测源时执行:

  1. 在沙箱所在 Namespace 创建临时 echo Ingress:随机长路径(如 /sandbox-probe-echo-<random>)、managed-by 与 probe-echo 标签、注解 nginx.ingress.kubernetes.io/configuration-snippet: return 200 "$remote_addr";、无 whitelist 注解、无 backend 依赖(路径不指向任何真实 Service 也可,因 snippet 在 backend 前返回)。
  2. 等待入口控制器收敛后,从 Portal 侧多次自拨(约 20 次、短间隔)http://<固定入口>/<随机路径>;响应体即入口侧观察到的源 IP。
  3. 聚合去重得到探测源 CIDR 集(单 IP 记 /32),写入适配层缓存并带 TTL;随后删除临时 echo Ingress 并确认删除。
  4. 启动时与孤儿扫描时清理遗留的 probe-echo 标签 Ingress,防止异常退出残留。

该机制已于 2026-09-23 在 166/cluster-4 现场验证:echo Ingress 自拨 8/8 返回 172.28.100.31,与 ingress 判定口径一致,且无需任何后端参与。

四、合并与比较语义(关键不变量)

设用户规则为 U(desired,来自模板/创建请求/实例 PUT),探测源集为 P(自动发现):

对象取值
whitelist 注解生效值(写入 Ingress)U ∪ P(仅当 len(U) > 0;U 为空则不写注解)
受管 Ingress 自描述注解 probe-source-cidrs本次合并的 P(JSON 数组)
desired / GET 返回的 configuredIngress / LKGU(剥离 P 后的用户规则;U 显式包含的 CIDR 即使同时是探测源也保留显示)
回读比较(carrierMatches、verification、LKG 判定)生效口径比较:回读注解值与 merge(U, P_self) 等价比较,P_self 取自载体自描述注解(缺失即空集,兼容旧载体);whitelist 注解存在性按 len(U) > 0 判断
U 变更(PUT/重建)按当前缓存或重新发现的 P 重新合并;载体自描述注解同步更新

不变量:

  1. 探测源只影响生效值,不进入用户语义:用户将某 CIDR 从白名单删除后,GET 必须不再显示它(即使它同时是探测源)。
  2. 比较以载体自描述为准,不依赖进程内缓存:重启后旧载体的生效值仍能按 merge(U, P_self) 口径正确比对(U 显式包含的 CIDR 不会被误判为漂移),不产生假漂移。
  3. 发现失败不阻塞不受限沙箱(无 U 即无需合并);对受限沙箱,发现失败按现行错误路径失败(fail-closed,不静默放开)。
  4. readiness 探测收到 403 类失败时:失效当前缓存 → 重新发现 → 在预算时间内继续重试,覆盖 NAT 池漂移场景。
  5. 自拨聚合覆盖 NAT 池(多次短间隔拨号),避免只采到单个出口地址。

五、实现范围(已批准的实施计划)

  1. 新增 internal/provider/probe_source_discovery.go(+测试):发现、缓存、TTL、剥离/合并工具函数、遗留清理。 (2026-09-23 已实现:随代码落地 WithProbeSourceDiscovery() 组合开关——生产 wiring(cmd/server/main.go 与 internal/cluster/manager.go)无条件启用,运维零配置;单测 fixture 不启用以保持旧口径回归。 )
  2. buildSandboxIngressCarrier 写入 U ∪ P 与自描述注解;sandboxIngressCarrierMatches、ingressVerificationMatches、ingressLastKnownGood、ingress_persistence.go 长校验链改为生效口径比较;sandboxIngressStateFromCarrier/GET 外显剥离 P。
  3. CheckNetworkReadiness 增加 403 触发的重发现重试钩子。
  4. 单测(发现/合并/剥离/重启不漂移/不受限语义保持)+ 回归(lifecycleFixture:仅用户 CIDR 的白名单经模拟 NAT 的假入口仍达 Ready)+ 166 真实创建回归(ingress 仅 172.28.8.68,不含 .31/.32 → Ready)。

5.3 NetworkPolicy 出口限制

沙箱出口白名单继续以实例级 NetworkPolicy 表达。模板上的默认出口白名单只作为创建实例时的初始值(创建请求 network 显式覆盖出站维度时以用户值为准);实例创建后,用户可通过实例网络接口独立修改,不受模板后续变更反向覆盖。

每个 allocation 使用一条平台 NetworkPolicy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: sandbox-egress-<allocationID>
  ownerReferences:
    - apiVersion: sandbox.opensandbox.io/v1alpha1
      kind: BatchSandbox
      name: <sandboxID>
      uid: <crUID>
      controller: true
      blockOwnerDeletion: true
  annotations:
    last-verified-spec: <canonical-json>
    verification-state: Verified
    verification-error-code: ""
spec:
  podSelector:
    matchLabels:
      allocation-id: <allocationID>
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector: <cluster-qualified-dns-namespace-selector>
          podSelector: <cluster-qualified-dns-pod-selector>
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    - to:
        - ipBlock:
            cidr: 203.0.113.10/32

关键约束:

  1. allocation ID 全局不复用;allocation 切换创建新策略,不原位复用旧 selector。
  2. 基线策略在 BatchSandbox CR 创建前先行写入,此时不带 ownerReference;CR 创建成功后回填 ownerReference,正常删除由 Kubernetes GC 收敛;CR 创建失败或回填失败的残留策略由孤儿扫描收敛。
  3. 非平台 NetworkPolicy 若能选择同一 allocation 模板标签集并造成并集扩权,状态为 PolicyConflict。
  4. 无当前 Pod 时,selector 和并集检查仍使用 CR 模板标签静态执行。
  5. LKG 只表示最后一次通过对象级校验的 spec,不是实时网络行为快照。

集群 CNI 准入矩阵要求至少覆盖(测试要求,不是本轮执行记录):Egress enforcement、UDP/TCP DNS、IPv4 CIDR(目标 CIDR 的全部协议与端口)、策略更新与删除、replacement Pod 首包窗口、conntrack、NodeIP/hostNetwork 等豁免边界及可观测性。准入结果保存在 Portal 既有集群配置中,并绑定:

clusterId + CNI implementation + CNI version + relevant configuration fingerprint

准入记录缺失或指纹失效时,该集群停止新的 enforced Sandbox 创建和新的 enforced PUT /network,同时告警。存量 Sandbox 的降级与恢复不在本文范围;现有对象也不因该集群事实被宣称为已实时包级验证。

现状限定(2026-09-08) :上述创建网络编排针对携带 InitialNetwork 的 E2B 创建路径,不能外推为所有 Native 创建均预创建同一组网络对象。生产默认要求 CNI 准入,但配置 SANDBOX_CNI_QUALIFICATION_REQUIRED_BY_CLUSTER 可以对显式 clusterID 设置 false;核对报告所述线上无 bypass 不等于代码没有开关。DNS 目标优先使用解析到的 kube-dns selector,解析失败时当前实现可能回退到 kube-system namespace 全部 Pod 的 UDP/TCP 53;“受控 CoreDNS”是目标和报告现场口径,不是所有配置/故障路径均精确 podSelector 的保证。

生命周期状态与网络接口关系:

状态connectGET /networkPUT /network
Creating409返回当前对象事实允许,按当前 CR 模板标签校验
Ready允许允许允许
PodUnavailable409允许允许,使用 CR 模板标签
UnverifiedSpec / PolicyConflict / Indeterminate409允许,用于诊断按错误类型拒绝或先恢复 LKG
Deleting / 已删除409 / 404409 / 404409 / 404

该表不是实时包级事实。当前生产 ProviderAuthorizer 仍检查 ownership/running 并可缓存结果;运行时结果还取决于 token 校验、credential reload、当前 Provider 的 fixed-entry 选址及下游当次响应,不能宣称完全不按生命周期阻断。