Skip to content

创建沙箱时 CubeMaster 做了哪些事

当 CubeAPI 调用 POST /cube/sandbox 时,CubeMaster 需要完成模板查询、调度选节点、调用 Cubelet、写入 Redis 等一系列操作。这篇文章拆解这个完整流程。


1. 全景流程


2. 入口:HttpHandler 路由

CubeMaster/pkg/service/httpservice/cube/sandbox_create.gocreateSandbox() 是入口函数:

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.goCreateSandbox() 是创建沙箱的核心函数。

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 会:

  1. 将该节点加入 lastBadNode 列表
  2. 重新调度,选择其他节点
  3. 重试直到成功或达到上限
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  // 成功,退出
}

延伸阅读