创建沙箱时 CubeMaster 做了哪些事
当 CubeAPI 调用 POST /cube/sandbox 时,CubeMaster 需要完成模板查询、调度选节点、调用 Cubelet、写入 Redis 等一系列操作。这篇文章拆解这个完整流程。
1. 全景流程
2. 入口:HttpHandler 路由
CubeMaster/pkg/service/httpservice/cube/sandbox_create.go 的 createSandbox() 是入口函数:
constructCreateReq
go
// sandbox_create.go
func constructCreateReq(r *http.Request) (*types.CreateCubeSandboxReq, error) {
req := &types.CreateCubeSandboxReq{}
common.GetBodyReq(r, req)
// 默认值填充
if req.InstanceType == "" {
req.InstanceType = cubebox.InstanceType_cubebox.String()
}
if req.NetworkType == "" {
req.NetworkType = cubebox.NetworkType_tap.String()
}
return req, nil
}dealCubeboxCreateReqWithTemplate
从模板中心查询模板信息,填充 CPU、内存、rootfs 大小等资源规格到请求中。
3. sandbox.CreateSandbox:核心流程
CubeMaster/pkg/service/sandbox/sandbox_run.go 的 CreateSandbox() 是创建沙箱的核心函数。
3.1 流程概览
3.2 createSandboxContext
每次创建沙箱都会创建一个 createSandboxContext,包含:
go
type createSandboxContext struct {
selctx *selctx.SelectorCtx // 调度上下文
directHost bool // 是否指定节点
selectHost *node.Node // 选中的节点
masterRsp *types.CreateCubeSandboxRes // 最终响应
cubeletReq *cubebox.RunCubeSandboxRequest // Cubelet 请求
cubeletRsp *cubebox.RunCubeSandboxResponse // Cubelet 响应
retryTimes int64 // 重试次数
reschedule bool // 是否需要重新调度
done chan struct{} // 完成信号
}3.3 AddBufferTask:进入调度队列
go
scheduler.AddBufferTask(createCtx, req.InstanceType)创建请求被放入调度器的缓冲队列,由调度器决定何时执行 Handle()。这样可以:
- 批量调度,提高效率
- 控制并发,避免过载
- 支持优先级排序
4. Handle():调度 + 调用 Cubelet
4.1 handleCubelet 循环
4.2 schedule()
调用调度器选择节点:
go
func (c *createSandboxContext) schedule() error {
if c.directHost {
// 指定节点模式,跳过调度
return nil
}
var err error
c.selectHost, err = scheduler.Select(c.selctx)
return err
}详见调度器如何选择节点。
4.3 callCubelet()
通过 gRPC 调用 Cubelet 创建沙箱:
4.4 熔断机制
如果 Cubelet 返回熔断码(如资源不足),CubeMaster 会:
- 将该节点加入
lastBadNode列表 - 重新调度,选择其他节点
- 重试直到成功或达到上限
go
if errorcode.IsCircutBreakCode(errorcode.MasterCode(c.cubeletRsp.GetRet().GetRetCode())) {
c.selctx.AddLastBadNode(c.selectHost)
}5. dealSuccResult:成功后的收尾
Cubelet 创建成功后,CubeMaster 需要完成以下收尾工作:
setProxyToRedis
这是最关键的一步,将沙箱信息写入 Redis,供 CubeProxy 路由使用:
go
func (c *createSandboxContext) setProxyToRedis() error {
// 写入 sandbox → node IP 映射
// 写入端口映射
// 写入 sandbox 元数据(模板、环境变量等)
}CubeProxy 通过 Redis 查询沙箱所在的节点 IP,将请求路由到正确的 Cubelet。
6. 完整时序图
7. 错误处理与重试
7.1 重试策略
| 错误类型 | 处理方式 |
|---|---|
| Cubelet 熔断码 | 加入 badNode,重新调度 |
| gRPC 连接错误 | errRetry,重新调度 |
| 业务错误码 | errorCodeRetry,根据错误码决定 |
| context 取消 | 直接退出 |
7.2 重试循环
go
for {
if er := c.schedule(); er != nil {
return // 调度失败,退出
}
if c.callCubelet() {
// 需要重试
continue
}
c.dealSuccResult()
return // 成功,退出
}延伸阅读
- 调度器如何选择节点 — 5 阶段选择器链
- 进程启动与服务注册 — CubeMaster 启动流程
- E2B 兼容接口如何映射到内部模型 — 上游请求格式