深入理解Moby Buildkit系列 #29 - SourceOp CacheKey

391 阅读2分钟

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

在SourceOp的CacheMap里,需要获取s.instance,而这里又依赖于s.sm.Resolve,可以看到sm实际上就是source.Manager,依赖于Resolve方法。 没错又一个Resolve,袁小白自言自语到,看来取名字真的很关键,大家都有resolve的话,这理解起来就太容易混淆了。 再看这个Resolve:

func (sm *Manager) Resolve(ctx context.Context, id Identifier, sessM *session.Manager, vtx solver.Vertex) (SourceInstance, error) {
   sm.mu.Lock()
   src, ok := sm.sources[id.ID()]
   sm.mu.Unlock()

   if !ok {
      return nil, errors.Errorf("no handler for %s", id.ID())
   }

   return src.Resolve(ctx, id, sessM, vtx)
}

居然是用数组存起来的: Source of Truth - CacheMap.jpg 对照着operation创建的地方,袁小白很快发现SourceOp是在worker中被创建的,那这个source.Manager是不是也在那里预先装载好的呢?

果然,就是在这里:

// NewWorker instantiates a local worker
func NewWorker(ctx context.Context, opt WorkerOpt) (*Worker, error) {
   ...
   sm, err := source.NewManager()
   if err != nil {
      return nil, err
   }

   is, err := containerimage.NewSource(containerimage.SourceOpt{
      Snapshotter:   opt.Snapshotter,
      ContentStore:  opt.ContentStore,
      Applier:       opt.Applier,
      ImageStore:    opt.ImageStore,
      CacheAccessor: cm,
      RegistryHosts: opt.RegistryHosts,
      LeaseManager:  opt.LeaseManager,
   })
   if err != nil {
      return nil, err
   }

   sm.Register(is)
   ...
}
  • 首先用source.NewManager创建了source manager实例
  • 紧接着注册了新的source - is,也就是container.NewSource实例

为了验证自己的想法,袁小白决定去检查一下这个实例是否有Resolve方法。 结果不出所料,就在这:

func (is *Source) Resolve(ctx context.Context, id source.Identifier, sm *session.Manager, vtx solver.Vertex) (source.SourceInstance, error) {
   imageIdentifier, ok := id.(*source.ImageIdentifier)
   if !ok {
      return nil, errors.Errorf("invalid image identifier %v", id)
   }

   platform := platforms.DefaultSpec()
   if imageIdentifier.Platform != nil {
      platform = *imageIdentifier.Platform
   }

   pullerUtil := &pull.Puller{
      ContentStore: is.ContentStore,
      Platform:     platform,
      Src:          imageIdentifier.Reference,
   }
   p := &puller{
      CacheAccessor:  is.CacheAccessor,
      LeaseManager:   is.LeaseManager,
      Puller:         pullerUtil,
      id:             imageIdentifier,
      RegistryHosts:  is.RegistryHosts,
      ImageStore:     is.ImageStore,
      Mode:           imageIdentifier.ResolveMode,
      Ref:            imageIdentifier.Reference.String(),
      SessionManager: sm,
      vtx:            vtx,
   }
   return p, nil
}

居然返回了个Puller实例,可转过头一想,也对,如果我是一个SourceOp,对应的就是Dockerfile的From指令,如果我要获取这个父容器的信息,自然需要一个对象帮我获取,那这里就会有用来提供存储服务的对象 - ContentStore,和用来提升效率的缓存策略 - CacheAccessor。

Instance是找到了,就是这个Puller,再看这个Instance的CacheKey:

func (p *puller) CacheKey(ctx context.Context, g session.Group, index int) (cacheKey string, imgDigest string, cacheOpts solver.CacheOpts, cacheDone bool, err error) {
   ...
   _, err = p.g.Do(ctx, "", func(ctx context.Context) (_ interface{}, err error) {
      ...
      p.manifest, err = p.PullManifests(ctx)
      ...
      k, err := mainManifestKey(ctx, desc, p.Platform)
      ...
      p.manifestKey = k.String()
      dt, err := content.ReadBlob(ctx, p.ContentStore, p.manifest.ConfigDesc)
      ...
   })
   ...
   if index == 0 || p.configKey == "" {
      return p.manifestKey, p.manifest.MainManifestDesc.Digest.String(), cacheOpts, cacheDone, nil
   }
   return p.configKey, p.manifest.MainManifestDesc.Digest.String(), cacheOpts, cacheDone, nil
}

原来这就是下载真正发生的地方:

  • 获取镜像所有Manifests
  • 从ContentStore中读取对应的ConfigDesc
  • 返回对应的manifest key

(这里又出现了新的知识点,那就是OCI中关于Image组织形式,我们这里先挖个坑,后续会继续我们的源码解析。 为了方便理解,我们先简单的理解为Image的描述和配置信息。)

再回到edge.go的unpark里: Edges inputs - edge pipes.jpg 我们的pipe3这时候可以回调OnSendCompletion了。

我们准备好了镜像的基础信息后,又要拿这些信息干什么用呢?

下一篇:深入理解Moby Buildkit系列 #30 - SourceOp Exec