深入理解Moby Buildkit系列 #22 - jobs的世界

300 阅读2分钟

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

从龙飞给出的buildctl build全景时序图中可以了解到: Buildctl build.jpg Worker是用来真正运行容器的,那jobs的定位又是什么呢?

带着这个问题,袁小白打开了j.list.load(e.Vertex, nil, j),稍早之前有看到初始化Job的时候,j.list指的是jobs.go中的Solver,所以j.list.load就是:

func (jl *Solver) load(v, parent Vertex, j *Job) (Vertex, error) {
   jl.mu.Lock()
   defer jl.mu.Unlock()

   cache := map[Vertex]Vertex{}

   return jl.loadUnlocked(v, parent, j, cache)
}

这里传入的的参数有v, parent都是Vertex,其中v就是edge.Vertex,从函数的定义来看,传入了vertex,返回的也是vertex,看起来像是对vertext做了一些处理,代码中实际调用的是jl.loadUnlocked:

func (jl *Solver) loadUnlocked(v, parent Vertex, j *Job, cache map[Vertex]Vertex) (Vertex, error) {
   if v, ok := cache[v]; ok {
      return v, nil
   }
   
   ...
}

直接从传入的Cache map中取vertex,如果有直接返回,那我们可以看出这里的cache是指的正在处理的Edge中的Vertex。

func (jl *Solver) loadUnlocked(v, parent Vertex, j *Job, cache map[Vertex]Vertex) (Vertex, error) {
   ...

   inputs := make([]Edge, len(v.Inputs()))
   for i, e := range v.Inputs() {
      v, err := jl.loadUnlocked(e.Vertex, parent, j, cache)
      if err != nil {
         return nil, err
      }
      inputs[i] = Edge{Index: e.Index, Vertex: v}
   }
   ...
}

这里又出现了递归,而且是根据vertex的input,循环load处于unlock状态的vertex,值得注意的是这里用的parent是同一个,并不是上一级的Vertex。 可以看出每一级只关心自己的这一级的input。

func (jl *Solver) loadUnlocked(v, parent Vertex, j *Job, cache map[Vertex]Vertex) (Vertex, error) {
   ...
   // if same vertex is already loaded without cache just use that
   st, ok := jl.actives[dgstWithoutCache]

   if !ok {
     ...

      v = &vertexWithCacheOptions{
         Vertex: v,
         dgst:   dgst,
         inputs: inputs,
      }
      ...
   }
    if !ok {
       st = &state{
          opts:         jl.opts,
          jobs:         map[*Job]struct{}{},
          parents:      map[digest.Digest]struct{}{},
          childVtx:     map[digest.Digest]struct{}{},
          allPw:        map[progress.Writer]struct{}{},
          mpw:          progress.NewMultiWriter(progress.WithMetadata("vertex", dgst)),
          mspan:        tracing.NewMultiSpan(),
          vtx:          v,
          clientVertex: initClientVertex(v),
          edges:        map[Index]*edge{},
          index:        jl.index,
          mainCache:    jl.opts.DefaultCache,
          cache:        map[string]CacheManager{},
          solver:       jl,
          origDigest:   origVtx.Digest(),
       }
       jl.actives[dgst] = st
    }
   ...
}

这里的重点是将vertex转换成vertexWithCacheOptions,也就是适用于缓存的状态。 紧接着创建出了state,这里的state,从结构体数据项来看是综合了所有的job相关的信息。

总的来说,就是将所有的vertex转换成有cacheOptions的状态,并用dgst(vertext唯一标识)关联上对应的state。

再看Build:

func (j *Job) Build(ctx context.Context, e Edge) (CachedResult, BuildInfo, error) {
   ...
   v, err := j.list.load(e.Vertex, nil, j)
   ...
   e.Vertex = v

   res, err := j.list.s.build(ctx, e)
   ...
   return res, j.walkBuildInfo(ctx, e, make(BuildInfo)), nil
}

将vertex转换成有cacheOptions状态的vertext后,替换掉了原Vertex - e.Vertex = v。 最后用j.list.s.build进行构建。

j.list.s指的是scheduler调度器,看来每个job都有自己的一个高度器,而这个调度器需要有缓存信息的vertex进行构建。

袁小白感觉自己正准备进入buildctl build中的引擎部位,前面那么多的铺垫都是在为这一刻做准备,紧张又兴奋!

下一篇:深入理解Moby Buildkit系列 #23 - jobs scheduler