深入理解Moby Buildkit系列 #20 - 我从哪儿来?到哪儿去?

641 阅读2分钟

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

梳理完State - Definition - Edge,已经过去将近一个月了。 袁小白感觉实现纯手工打造,24K容器化运行生态,是越来越有信心了!

可话说回来。 我们看到哪儿了? 为什么在看这三个数据结构? 接下来要看啥? 袁小白突然感觉面临着人生三大终级问题。

好在按buildctl build全流程回顾后,发现,我们正在看Frontend,看到了dockerfile.Build,因为这个函数很长,长达近500行,发现关键地方出现了llb.State,为了搞清楚llb.State到底做了什么,所以转而去了解数据结构去了,现在好容易看完,再回过头来看看之前我们到达的地方:

func Build(ctx context.Context, c client.Client) (*client.Result, error) {
   ...
   res := client.NewResult()
   ...
    for i, tp := range targetPlatforms {
       func(i int, tp *ocispecs.Platform) {
          ...
          st, img, err := dockerfile2llb.Dockerfile2LLB(...)
          ...
       }
       ...
       def, err := st.Marshal(ctx)
       ...
       r, err := c.Solve(ctx, client.SolveRequest{
          Definition:   def.ToPB(),
          CacheImports: cacheImports,
       })
       ...
       ref, err := r.SingleRef()
       ...
          res.SetRef(ref)
       ...
    }
    ...
    return res, nil
}

再回过头来看这一段代码,袁小白感觉一目了然,神清气爽,这感觉就像是被打通了任督二脉。

  • 通过dockerfile2llb.Dockerfile2LLB方法,将dockerfile转换成State
  • 然后将返回的State - st.Marshal转换成def - Definition
  • 再重新解析一次c.Solve,这里的c - client也就是传入的llbBridge;并传入def.ToPB()
  • 最后得到解析结果,并返回

数据结构是没太大的问题了,帮助我们理解起这小500行的代码,变得轻松了起来。 可这晨为什么要又调用一次llbBridge解析一遍呢? 在前面的理解是,这里传入的参数和第一次解析传入的参数不一样,所以结果不一样,再看看解析源码:

func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid string) (res *frontend.Result, err error) {
   if req.Definition != nil && req.Definition.Def != nil && req.Frontend != "" {
      return nil, errors.New("cannot solve with both Definition and Frontend specified")
   }

   if req.Definition != nil && req.Definition.Def != nil {
      res = &frontend.Result{Ref: newResultProxy(b, req)}
      if req.Evaluate {
         _, err := res.Ref.Result(ctx)
         return res, err
      }
   } else if req.Frontend != "" {
      f, ok := b.frontends[req.Frontend]
      if !ok {
         return nil, errors.Errorf("invalid frontend: %s", req.Frontend)
      }
      res, err = f.Solve(ctx, b, req.FrontendOpt, req.FrontendInputs, sid, b.sm)
      if err != nil {
         return nil, err
      }
   } else {
      return &frontend.Result{}, nil
   }

   return
}

果然,同样的解析,这里有两个分支,一个是当req.Definitoin不为空时,一个是req.Frontend不为空时,前面一次是因为req.Frontend传入的是dockerfile.v0,是从buildctl build命令行参数里读取的。 而这一次是由Frontend dockerfile.Build方法,主动发起的调用,传入的req.Definition不为空。

那就对上了,那最终返回的结果res就是res = &frontend.Result{Ref: newResultProxy(b, req)}。 也就是说绕了一圈后,最终返回的结果是bridge.go里的Result。

真是把自己牛X坏了...

再看龙飞的时序图,真是不得不佩服,接下来就看真正的Build了。

下一篇:深入理解Moby Buildkit系列 #21 - 是时候开始Build了