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

SourceOp构建完后,就轮到了ExecOp。 现在一切的Op都执行完了,也便是咱们的Edge都得到了遍历。

还记得咱们从哪儿来的吗? 让咱们一起来回顾一下。 没错,jobs收到构建恳求后,由scheduler将edge进行了dispatch,这时分edge会进行unpark操作。

这儿的要害是jobs, scheduler和unpark的配合:

  • jobs存储了一切的inbounds和outgoing恳求,办理整个构建事情的上下文
  • scheduler就像是一个事情总线,负责分发收到的dispatch事情。
  • unpark则像是具体的事情处理中心,根据事情的状况和信息,对新的状况进行下一步的处理。

那构建完成后,谁会收到并处理回来的成果呢?

Exporter

$ buildctl build ... --output type=image,name=docker.io/username/image,push=true

让咱们回到用户指令的地方,没错,想要了解全貌,就得从头开始。 能够看到咱们对输出output的要求是,以image类型进行输出,并以名字name为docker.io/username/image 的镜像进行推送到远端registry。

解铃还须系铃人。 让咱们回到solver.Solve:

func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req frontend.SolveRequest, exp ExporterRequest, ent []entitlements.Entitlement) (*client.SolveResponse, error) {
   j, err := s.solver.NewJob(id)
   if err != nil {
      return nil, err
   }
   defer j.Discard()
   set, err := entitlements.WhiteList(ent, supportedEntitlements(s.entitlements))
   if err != nil {
      return nil, err
   }
   j.SetValue(keyEntitlements, set)
   j.SessionID = sessionID
   var res *frontend.Result
   if s.gatewayForwarder != nil && req.Definition == nil && req.Frontend == "" {
      ...
   } else {
      res, err = s.Bridge(j).Solve(ctx, req, sessionID)
      ...
   }
   ...
   eg, ctx2 := errgroup.WithContext(ctx)
   res.EachRef(func(ref solver.ResultProxy) error {
      eg.Go(func() error {
         _, err := ref.Result(ctx2)
         return err
      })
      return nil
   })
   ...
   var exporterResponse map[string]string
   if e := exp.Exporter; e != nil {
      ...
      if err := inBuilderContext(ctx, j, e.Name(), "", func(ctx context.Context, _ session.Group) error {
         exporterResponse, err = e.Export(ctx, inp, j.SessionID)
         return err
      }); err != nil {
         return nil, err
      }
   }
   ...
   return &client.SolveResponse{
      ExporterResponse: exporterResponse,
   }, nil
}

通过s.Bridge(j).Solve解析,获取一切的ref解析成果res.EachRef - ref.Result,终究导出回来值exporterResponse, err = e.Export(...)

那这儿的Export指的又是什么呢?让咱们将这些点都连接起来,来讲讲Export的故事:

深入理解Moby Buildkit系列 #33 - 完美收关Exporter

  • 当用户的构建指令传递给指令行buildctl时,buildAction首先接收到信息,通过ParseOutput入口,将字符串信息解析成对应的数据结构,终究得到Type为image的ExportEntry并回来,这样指令行的解析作业就完成了。
  • 前端client将解析指令传递给后端也便是buildkitd时,最先接收到相关信息的是control,并通过Solve办法,将信息传递给workerController
  • 如上一篇所述,workerController提前注册好可用worker的初始化函数,并初始化了worker实例,不过worker对应的主要有executor,并没有exporter,这是因为exporter更普遍,被安置在了base worker里,所以在Solve export的时分,实践供给服务的是:
func (w *Worker) Exporter(name string, sm *session.Manager) (exporter.Exporter, error) {
   switch name {
   case client.ExporterImage:
      return imageexporter.New(imageexporter.Opt{
         Images:         w.ImageStore,
         SessionManager: sm,
         ImageWriter:    w.imageWriter,
         RegistryHosts:  w.RegistryHosts,
         LeaseManager:   w.LeaseManager,
      })
   ...
}
  • 在这儿,咱们得到了exporter的实例,也便是imageExporterInstance。
  • 终究,solver在调用exporter的Export办法时,实践供给服务的是imageExporterInstance实例
func (e *imageExporterInstance) Export(ctx context.Context, src exporter.Source, sessionID string) (map[string]string, error) {
   ...
   if e.targetName != "" {
      targetNames := strings.Split(e.targetName, ",")
      for _, targetName := range targetNames {
         if e.opt.Images != nil {
            ...
            for _, sfx := range sfx {
               img.Name = targetName + sfx
               if _, err := e.opt.Images.Update(ctx, img); err != nil {
                  ...
               }
            }
            tagDone(nil)
            ...
}
         if e.push {
            ...
            if err := push.Push(ctx, e.opt.SessionManager, sessionID, mprovider, e.opt.ImageWriter.ContentStore(), desc.Digest, targetName, e.insecure, e.opt.RegistryHosts, e.pushByDigest, annotations); err != nil {
               return nil, err
            }
         }
      }
      resp["image.name"] = e.targetName
   }
   ...
   return resp, nil
}

根据用例传入的参数,targetName不为空,则需要存储和更新image,e.push状况也为真,会调用util里的push包来上传推送镜像。

系列完

不知不觉已经看完了buildctl build的全构建流程。 要想彻底理解,还需要更多的比如,以及一些对一些细节的补充说明,如metadata store, cache manager,等等。 其间要害的部分有SourceOp的snapshotter,ExecOp的runc容器运行时。 不过GitAction在这儿挖好了坑,也必定会把这些坑填起来。 秉着Talk is cheap, show me the code!原则,手动撸一个容器全生命周期工具,也不是彻底没有可能。

一起期待吧!