opensandbox Docker 网络模式配置指南
Docker 网络模式配置指南
概述
OpenSandbox Server 在 Docker 运行时下支持两种网络模式:host 模式 和 bridge 模式。选择合适的网络模式,并正确配置端口范围,是保证多沙箱安全隔离和 SDK 正常通信的关键。
配置入口
在 ~/.sandbox.toml 配置文件的 [docker] 段中设置:
[docker]
# 可选值: "host" | "bridge" | "<自定义网络名>"
network_mode = "host"
# 可选:宿主机端口分配范围(默认 40000-60000)
# bridge 模式使用 Docker 端口映射
# host 模式使用进程级动态端口
port_range_min = 40000
port_range_max = 60000
# 当 server 运行在 Docker 容器内时,需要设置宿主机 IP
# bridge 模式下必须配置
host_ip = "host.docker.internal"
Host 模式
原理
┌──────────────────────────────────────────────────┐
│ 宿主机 │
│ │
│ 沙箱 C (无网络隔离) 沙箱 D (无网络隔离) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ execd: 40001 │ │ execd: 40003 │ │
│ │ HTTP: 40002 │ │ HTTP: 40004 │ │
│ │ 磁盘: /mnt/disk1 │ │ 磁盘: /mnt/disk2 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ OpenSandbox Server (8080) │
│ │
└──────────────────────────────────────────────────┘
沙箱容器直接使用宿主机的网络命名空间,没有网络隔离层。execd 进程直接绑定宿主机的 IP 和端口。
配置
[docker]
network_mode = "host"
# 端口范围配置(可选,默认 40000-60000)
port_range_min = 40000
port_range_max = 60000
# host 模式下不需要 host_ip
# host_ip = "host.docker.internal"
特点
| 特性 | 说明 |
|---|---|
| 网络性能 | ⭐ 最高——零 NAT 开销,直接使用宿主机网络栈 |
| 延迟 | 最低——无 bridge 转发开销 |
| 端口管理 | 每个沙箱动态分配唯一端口,通过 OPENSANDBOX_EXECD_PORT 注入 execd |
| SDK 通信 | 直接访问 host:{allocated_port}/proxy/{port} |
| egress 网络策略 | ❌ 不支持(networkPolicy 在 host 模式下被拒绝) |
| 端口冲突 | ✅ 动态分配避免(通过 socket.bind() 探测可用性) |
端口分配
Host 模式下,OpenSandbox 不再使用 Docker 的端口映射,而是在创建沙箱时:
- 调用
allocate_port_bindings(["44772", "8080"])在 40000-60000 范围内分配两个可用端口 - 将分配端口写入容器标签(
embedding-proxy-port、http-port) - 将 execd 端口通过环境变量
OPENSANDBOX_EXECD_PORT注入容器 - execd 进程启动时读取环境变量,绑定到分配端口而非默认 44772
端口冲突处理
# port_allocator.py 核心逻辑
def allocate_host_port(min_port, max_port):
for _ in range(50): # 最多尝试 50 次
port = random.randint(min_port, max_port)
with socket.socket() as sock:
try:
sock.bind(("0.0.0.0", port)) # 探测端口是否空闲
return port # 空闲 → 分配
except OSError:
continue # 被占用 → 重试
return None # 范围内无可用端口 → 报错
端口收回:沙箱删除后,execd 进程退出 → TCP socket 关闭 → 端口自动归还 OS 端口池。
向后兼容
旧沙箱(在本次改动前创建)没有 embedding-proxy-port label,endpoint 解析自动回退到原始格式:
if labels.get(SANDBOX_EMBEDDING_PROXY_PORT_LABEL):
return self._resolve_host_mapped_endpoint(public_host, labels, port)
# 无 label → 向后兼容
endpoint = Endpoint(endpoint=f"{public_host}:{port}")
Bridge 模式
原理
┌──────────────────────────────────────────────────────┐
│ 宿主机 │
│ │
│ OpenSandbox Server (8080) │
│ │
│ ┌────── Docker Bridge (docker0) ─────────────────┐ │
│ │ │ │
│ │ 沙箱 C 沙箱 D │ │
│ │ ┌─────────────────────┐ ┌─────────────────┐ │ │
│ │ │ 容器内: 44772, 8080 │ │ 44772, 8080 │ │ │
│ │ │ 宿主机映射: │ │ 宿主机映射: │ │ │
│ │ │ 40001→44772 │ │ 40003→44772 │ │ │
│ │ │ 40002→8080 │ │ 40004→8080 │ │ │
│ │ └─────────────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
每个沙箱容器运行在独立的 Docker bridge 网络上,有独立的网络命名空间和 IP 地址。通过 Docker 端口映射将容器端口暴露到宿主机。
配置
[docker]
network_mode = "bridge"
# 端口范围配置(可选,默认 40000-60000)
port_range_min = 40000
port_range_max = 60000
# 当 server 运行在 Docker 容器内时,必须设置 host_ip
# 让 SDK 能访问宿主机上的端口映射
host_ip = "host.docker.internal"
特点
| 特性 | 说明 |
|---|---|
| 网络性能 | ⭐⭐——少量 NAT 转发开销(通常可忽略) |
| 延迟 | 低——Docker bridge 内核级转发 |
| 端口管理 | 每个沙箱的容器端口映射到不同宿主机端口 |
| SDK 通信 | 直接访问 host:{mapped_port}/proxy/{port} |
| egress 网络策略 | ✅ 支持(需要额外配置 [egress] image) |
| 网络隔离 | ✅ 沙箱之间网络完全隔离 |
| host_ip 配置 | Server 在容器内时必须设置 |
端口分配
Bridge 模式下,Docker 负责端口映射:
- Server 调用
allocate_port_bindings(["44772", "8080"])分配宿主机端口 - 构造 Docker host_config 的
port_bindings:host_config_kwargs["port_bindings"] = { "44772": ("0.0.0.0", 40001), # 宿主机:40001 → 容器:44772 "8080": ("0.0.0.0", 40002), # 宿主机:40002 → 容器:8080 } - Docker 自动在
iptablesNAT 规则中创建端口转发 - execd 在容器内始终绑定 44772(不变),Docker 负责宿主机→容器的映射
关于 host_ip
当 OpenSandbox Server 自身运行在 Docker 容器内时,host_ip 必须配置:
[docker]
network_mode = "bridge"
host_ip = "host.docker.internal" # macOS / Docker Desktop
# 或
host_ip = "10.0.0.100" # 宿主机 LAN IP
SDK 获取 endpoint 后,会使用 host_ip 作为宿主机地址连接端口映射。如果不设置,SDK 得到的 endpoint 是容器内部 IP,无法从外部访问。
host_ip 在 host 模式下不需要,因为 execd 直接绑定宿主机网卡。
端口配置详解
端口范围配置
[docker]
port_range_min = 40000 # 端口分配下限(默认 40000)
port_range_max = 60000 # 端口分配上限(默认 60000)
每个沙箱占用的端口数
| 场景 | 容器端口 | 宿主机端口数 |
|---|---|---|
| 默认(无 egress) | 44772 (execd), 8080 (HTTP) | 2 |
| 有 egress sidecar | 44772, 8080, 18080 (egress API) | 3 |
| Windows 系统 | 44772, 8080, 3389 (RDP), 8006 (noVNC) | 4 |
端口占用计算
端口数 = 沙箱数量 × 每个沙箱所需端口
示例:
5 个 agent × 2 沙箱 = 10 沙箱(默认模式)
→ 所需端口 = 10 × 2 = 20
10 个 agent × 3 沙箱 = 30 沙箱(默认模式)
→ 所需端口 = 30 × 2 = 60
最大并行沙箱数(估算):
(port_range_max - port_range_min) ÷ 每个沙箱端口数
默认: (60000 - 40000) ÷ 2 = 10000
端口范围选择建议
| 场景 | 建议端口范围 | 理由 |
|---|---|---|
| 开发/测试(≤10 沙箱) | 40000-40100 | 100 个端口足够,防火墙规则更安全 |
| 小规模(≤50 沙箱) | 40000-40200 | 200 个端口充裕 |
| 中规模(≤500 沙箱) | 40000-41000 | 1000 个端口 |
| 大规模(≤5000 沙箱) | 40000-50000 | 保留范围 |
| 默认(不限制) | 保持默认 40000-60000 | 最大灵活性 |
防火墙配置
根据你启用的端口范围配置防火墙规则:
# 如果设置 port_range_min=40000, port_range_max=40100
firewall-cmd --permanent --add-port=8080/tcp # Server API
firewall-cmd --permanent --add-port=40000-40100/tcp # 沙箱端口
firewall-cmd --reload
# 如果保持默认 40000-60000
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=40000-60000/tcp
firewall-cmd --reload
端口分配失败处理
当 allocate_host_port 在指定范围内连续 50 次随机尝试都找不到空闲端口时:
Error: Failed to allocate host ports for sandbox container.
常见原因:
- 端口范围太小:同时运行的沙箱数 × 2 > 端口范围大小
- 解决:扩大
port_range_max - port_range_min
- 解决:扩大
- 宿主机其他进程占用了范围内端口:如 Redis、MySQL 等
- 解决:缩小范围避开已知端口,或
netstat -tln排查
- 解决:缩小范围避开已知端口,或
SDK 通信链路对比
Host 模式
SDK → Server API (8080):
POST /v1/sandboxes 创建沙箱
GET /sandboxes/{id}/endpoints/44772 获取 endpoint
Server 返回: host:40001/proxy/44772
← 注意:40001 是动态分配的,每个沙箱不同
SDK → 沙箱 execd (直连):
http://host:40001/proxy/44772/files/read/... 读取文件
http://host:40001/proxy/44772/commands/run... 执行命令
http://host:40001/proxy/44772/health 健康检查
Bridge 模式
SDK → Server API (8080):
POST /v1/sandboxes 创建沙箱
GET /sandboxes/{id}/endpoints/44772 获取 endpoint
Server 返回: host:40001/proxy/44772
← 40001 是 Docker 端口映射的宿主机端口
SDK → 宿主机 :40001:
http://host:40001/proxy/44772/files/read/...
Docker iptables NAT:
宿主机:40001 → 容器:44772 → execd
Server Proxy 模式
SDK 无法直连宿主机端口时,可启用 use_server_proxy=True:
config = ConnectionConfig(
domain="server-host:8080",
use_server_proxy=True,
)
sandbox = await Sandbox.create("python:3.11", connection_config=config)
SDK → Server (8080) → 沙箱 execd
Server 内部:
/sandboxes/{sandbox_id}/proxy/{port}/*
→ get_endpoint(sandbox_id, port, resolve_internal=True)
→ 127.0.0.1:{allocated_port}/proxy/{port}
→ 转发请求到沙箱 execd
模式选择决策树
需要 egress 网络策略 (networkPolicy)?
├─ 是 → bridge 模式(必须)
├─ 否 → 需要最大网络性能?
│ ├─ 是 → host 模式
│ ├─ 否 → Server 在 Docker 容器内?
│ │ ├─ 是 → bridge 模式 + host_ip
│ │ └─ 否 → 两者均可,建议 bridge 模式
┌─────────────────────────────────────────┐
│ 推荐:生产环境用 bridge 模式 │
│ │
│ bridge 提供网络隔离,防止沙箱互相影响 │
│ host 模式仅在对延迟极端敏感时使用 │
└─────────────────────────────────────────┘
完整配置示例
Bridge 模式完整配置
[server]
host = "0.0.0.0"
port = 8080
api_key = "your-secret-key"
[log]
level = "INFO"
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.0.19"
[docker]
network_mode = "bridge"
port_range_min = 40000
port_range_max = 50000
# 如果 server 在容器内,取消注释:
# host_ip = "host.docker.internal"
# 安全设置
drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"]
no_new_privileges = true
pids_limit = 4096
[egress]
image = "opensandbox/egress:v1.1.2"
mode = "dns"
[storage]
allowed_host_paths = ["/data/opensandbox"]
[store]
type = "sqlite"
path = "~/.opensandbox/opensandbox.db"
Host 模式完整配置
[server]
host = "0.0.0.0"
port = 8080
api_key = "your-secret-key"
[log]
level = "INFO"
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.0.19"
[docker]
network_mode = "host"
port_range_min = 40000
port_range_max = 50000
# host 模式不需要 host_ip
drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"]
no_new_privileges = true
pids_limit = 4096
# 注意:host 模式不支持 egress sidecar
# [egress] 段可以省略
[storage]
allowed_host_paths = ["/data/opensandbox"]
[store]
type = "sqlite"
path = "~/.opensandbox/opensandbox.db"
附录:端口 FAQ
Q: Host 模式下 execd 如何知道绑定哪个端口?
通过环境变量 OPENSANDBOX_EXECD_PORT。Server 在创建容器时注入环境变量,execd 的 parser.go 读取该变量:
const serverPortEnv = "OPENSANDBOX_EXECD_PORT"
if portFromEnv := os.Getenv(serverPortEnv); portFromEnv != "" {
port, err := strconv.Atoi(portFromEnv)
ServerPort = port // ← execd 绑定到这个端口
}
优先级:环境变量 > CLI --port > 默认 44772
Q: 端口用完会怎样?
allocate_host_port() 连续 50 次随机尝试都失败时,创建沙箱请求返回 500 错误。建议保持端口范围大于 预期最大沙箱数 × 2。
Q: 宿主机重启后端口会变化吗?
会。宿主机重启后所有沙箱消失,重新创建时端口会重新分配。没有持久性假设。
Q: Server 需要对外开放所有端口吗?
不。SDK 需要访问的是 execd 的端口,即 port_range_min ~ port_range_max 范围内的端口。Server API 端口(默认 8080)也需要开放。其他端口不需要。
Q: 可以用 use_server_proxy=True 避免开放端口范围吗?
可以。如果 SDK 无法直接访问宿主机端口范围(如跨网络、跨云),设置 use_server_proxy=True,SDK 所有请求都经过 Server 的 8080 端口代理转发,不再需要直接访问端口范围。
config = ConnectionConfig(use_server_proxy=True)
代价:多一层 HTTP 转发,延迟略增,Server 负载增加。
Q: 多大端口范围会被视为安全风险?
40000-60000 有 20000 个端口,确实较大。建议根据实际并发数缩小范围:
| 最大并发沙箱 | 建议范围 | 端口数 |
|---|---|---|
| 10 | 40000-40060 | 60 |
| 50 | 40000-40100 | 100 |
| 100 | 40000-40200 | 200 |
或者启用 use_server_proxy=True 完全不开放端口范围。
NOTE: docker 服务在 host 模式下 不支持多服务并发调用,详见:
https://blog.csdn.net/lichenyang810123/article/details/162733906?spm=1001.2014.3001.5501
更多推荐

所有评论(0)