CubeProxy:OpenResty 里的路由魔法
本文基于 CubeSandbox 开源仓库中
CubeProxy/目录下的源码进行分析。
概述
CubeProxy 是 CubeSandbox 的请求路由层,基于 OpenResty(Nginx + Lua)构建。它作为沙箱流量的入口,负责将 SDK 请求路由到正确的沙箱实例。核心功能包括:
- Host 路由:从 Host header 解析出沙箱 ID 和端口
- Path 路由:支持
/sandbox/<id>/<port>/格式的路径路由 - Redis 查询:从 Redis 获取沙箱的路由元数据
- 智能转发:本地沙箱直连,远程沙箱通过节点代理
- 健康检查:故障后端检测与熔断
架构总览
请求处理流程
Host 路由模式
这是主要的路由方式,通过 Host header 中的格式 <port>-<sandbox_id>.<domain> 解析目标:
Path 路由模式
支持通过 URL 路径 /sandbox/<sandbox-id>/<container-port>/<rest> 访问沙箱:
核心模块解析
rewrite_phase.lua — Host 路由入口
lua
-- 解析 Host header: <container_port>-<sandbox_id>.<domain>
local function parse_port_and_instance_from_host(host)
local hostname = host:match("^([^%:]+)")
local container_port, ins_id = hostname:match("^(%d+)%-([^%.]+)")
return container_port, ins_id
end
-- 解析并验证
local container_port, ins_id = parse_port_and_instance_from_host(ngx.var.http_host)
if not container_port or not ins_id then
ngx.var.cube_retcode = "310400"
ngx.exit(400)
end
-- 解析后端地址
local host_ip, host_port = sb.resolve_backend(ins_id, container_port)
ngx.var.backend_ip = host_ip
ngx.var.backend_port = host_port
-- 健康检查
sb.assert_backend_healthy(host_ip, ins_id)sandbox_backend.lua — 后端解析核心
这是路由逻辑的核心模块,负责从 Redis 查询沙箱元数据并决定转发目标:
lua
function _M.resolve_backend(ins_id, container_port)
-- 1. 查询本地缓存
local cache = ngx.shared.local_cache
local host_ip = cache:get(cache_backend_ip_key)
local host_port = cache:get(cache_backend_port_key)
if host_ip and host_port then
-- 缓存命中,刷新过期时间
cache:set(cache_backend_ip_key, host_ip, timeout)
cache:set(cache_backend_port_key, host_port, timeout)
return host_ip, host_port
end
-- 2. 查询 Redis
local metadata = load_sandbox_proxy_metadata(ins_id)
-- metadata 格式: {HostIP: "10.0.0.1", SandboxIP: "192.168.1.100", "8080": "49983", ...}
-- 3. 判断路由方式
local target_host_ip = metadata["HostIP"]
local target_sandbox_ip = metadata["SandboxIP"]
if caller_host_ip == target_host_ip then
-- 本地沙箱:直连 SandboxIP
host_ip = target_sandbox_ip
host_port = container_port
else
-- 远程沙箱:通过节点代理
host_ip = target_host_ip
host_port = metadata[container_port] -- 映射后的 HostPort
end
-- 4. 写入缓存
cache:set(cache_backend_ip_key, host_ip, timeout)
cache:set(cache_backend_port_key, host_port, timeout)
return host_ip, host_port
end路由决策逻辑:
| 场景 | 条件 | 转发目标 |
|---|---|---|
| 本地沙箱 | CubeProxy IP == metadata.HostIP | SandboxIP:ContainerPort |
| 远程沙箱 | CubeProxy IP != metadata.HostIP | HostIP:HostPort |
balancer_phase.lua — Upstream 选择
lua
local balancer = require "ngx.balancer"
-- 设置后端地址(由 rewrite 阶段计算)
local ok, err = balancer.set_current_peer(ngx.var.backend_ip, ngx.var.backend_port)
if not ok then
ngx.var.cube_retcode = "310509"
ngx.exit(500)
endredis_iresty.lua — Redis 客户端
封装了 Redis 操作,支持连接池和重试:
lua
local redis = require "redis_iresty"
local red = redis:new({
redis_ip = ngx.var.redis_ip,
redis_port = ngx.var.redis_port,
redis_pd = ngx.var.redis_pd,
redis_index = ngx.var.redis_index
})
-- 查询沙箱元数据
local key = "bypass_host_proxy:" .. ins_id
local value, err = red:hgetall(key)Redis 数据结构
CubeProxy 依赖 Redis 存储沙箱的路由元数据,由 CubeMaster 写入:
bypass_host_proxy:<sandbox_id>
Hash 结构,存储沙箱的网络路由信息:
Key: bypass_host_proxy:abc123
Value:
HostIP: "10.0.0.1" # 沙箱所在节点 IP
SandboxIP: "192.168.1.100" # 沙箱内部 IP(TAP 设备 IP)
8080: "49983" # 容器端口 → HostPort 映射
3000: "49984" # 另一个端口映射
Status: "running" # 沙箱状态faulty_backend_set
Set 结构,存储故障后端 IP:
Key: faulty_backend_set
Members: ["10.0.0.2", "10.0.0.3"]Nginx 配置详解
监听端口
nginx
server {
listen 8081 reuseport; # HTTP 入口
server_name _;
}
server {
listen 8080 ssl reuseport; # HTTPS 入口
server_name _;
ssl_certificate /usr/local/openresty/nginx/certs/cube.app+3.pem;
ssl_certificate_key /usr/local/openresty/nginx/certs/cube.app+3-key.pem;
}Location 配置
nginx
# Host 路由模式(默认)
location / {
rewrite_by_lua_file lua/rewrite_phase.lua;
proxy_pass http://backend;
header_filter_by_lua_file lua/header_filter_phase.lua;
log_by_lua_file lua/log_phase.lua;
}
# Path 路由模式
location ^~ /sandbox/ {
rewrite_by_lua_file lua/path_rewrite_phase.lua;
proxy_pass http://backend;
# 响应头重写,保持路径前缀
proxy_redirect ~^/(.*)$ /sandbox/$ins_id/$container_port/$1;
proxy_cookie_path / /sandbox/$ins_id/$container_port/;
}Upstream 配置
nginx
upstream backend {
server 0.0.0.1:1234; # 占位地址
balancer_by_lua_file lua/balancer_phase.lua;
keepalive 1500;
keepalive_timeout 80;
}缓存机制
CubeProxy 使用多层缓存提高性能:
1. Lua 共享内存缓存
nginx
lua_shared_dict local_cache 500m;
lua_shared_dict faulty_backend 100m;缓存 Key 格式:
<sandbox_id>:<container_port>:backend_ip<sandbox_id>:<container_port>:backend_port<sandbox_id>:HostIP<sandbox_id>:SandboxIP
2. 缓存过期策略
lua
local function get_cache_timeout()
return math.random(ngx.var.timeout_min, ngx.var.timeout_max)
end使用随机过期时间避免缓存雪崩。
故障处理
故障后端检测
lua
function _M.is_faulty_backend(self, backend_ip, check_remote)
-- 1. 检查本地缓存
local cache = ngx.shared.faulty_backend
local value = cache:get(backend_ip)
if value == "true" then
return true, nil
end
-- 2. 检查 Redis
if check_remote then
local key = "faulty_backend_set"
local value = red:smembers(key)
-- 检查 backend_ip 是否在集合中
end
return false, nil
end熔断响应
lua
function _M.assert_backend_healthy(host_ip, ins_id)
local faulty_backend = utils:is_faulty_backend(host_ip, true)
if faulty_backend == true then
ngx.var.cube_retcode = "340500"
ngx.exit(500) -- 返回 500,触发重试
end
end错误码
CubeProxy 定义了详细的错误码用于监控和排查:
| 错误码 | 含义 |
|---|---|
| 310200 | 正常 |
| 310400 | 请求格式错误(Host/Path 解析失败) |
| 310500 | Redis 查询失败 |
| 310507 | 元数据缺失(HostIP/SandboxIP/端口映射) |
| 310508 | 后端地址无效 |
| 310509 | 连接后端失败 |
| 340500 | 后端故障熔断 |
性能优化
1. 连接池
nginx
upstream backend {
keepalive 1500; # 最大空闲连接数
keepalive_timeout 80; # 空闲连接超时
}2. 缓存命中
- 首次请求查询 Redis(~1ms)
- 后续请求命中本地缓存(~0.01ms)
3. 本地直连
当 CubeProxy 与沙箱在同一节点时,直接连接 SandboxIP,跳过节点代理,减少一跳延迟。
启动脚本
bash
#!/bin/bash
# start.sh
/usr/sbin/crond # 启动定时任务(日志轮转)
/usr/local/openresty/nginx/sbin/nginx # 启动 OpenResty延伸阅读
- CubeVS:用 eBPF 做 TAP 与 DNAT — 了解网络数据面如何处理沙箱流量
- network-agent:节点网络配置谁在管 — 了解 TAP 设备和端口映射的管理