Skip to content

Conversation

@burgerdev
Copy link
Member

Before this PR, the imagepuller was only caching full images. If two different images shared layers, these would be downloaded two times.

With this change, we're switching the underlying layer storage engine (from podman to umoci), such that we have finer control over the storage layout. This allows storing the layers in independent, content-addressed directories, which can then be reused when assembling images.


Note to developers:

  • I removed the digest checks in the layer pull paths, because they are already implemented in go-containerregistry, as demonstrated by the evil registry test.
  • Using compress/gzip instead of pgzip causes a pull time regression north of 30%.
  • Contexts are not propagated, which causes the pull to continue even if the agent gave up. This is unchanged from before, but we should tackle this soon.
  • Before this change, manifests were cached implicitly, because the images were tagged with the manifest digest. Now, we are pulling the manifest each time, even if we are pulling the same image twice. This is not critical, as manifests are on the order of kB, but we should eventually cache them, too.
  • 2/3 benchmark runs show no regression

@burgerdev burgerdev requested a review from charludo October 30, 2025 14:37
@burgerdev burgerdev requested a review from katexochen as a code owner October 30, 2025 14:37
@burgerdev burgerdev added the feature Shiny new feature for our users label Oct 30, 2025
@charludo charludo self-assigned this Oct 30, 2025
@burgerdev
Copy link
Member Author

umoci pulls in libraries with MPL license, need to check whether we can allow this :/

@burgerdev burgerdev marked this pull request as draft October 30, 2025 16:06
Copy link
Contributor

@charludo charludo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, that's quite the overhaul! 🎉

Had already started looking at this prior to the license question / draft status. Re: MPL: seems we'd need to open-source the imagepuller itself?

Comment on lines +86 to +96
var layers []string
remoteLayers, err := remoteImg.Layers()
if err != nil {
return nil, fmt.Errorf("creating image: %w", err)
}
log.Info("Created image", "id", newImg.ID)

if err := s.Store.RemoveNames(newImg.ID, newImg.Names); err != nil {
return nil, fmt.Errorf("removing pre-existing image names: %w", err)
return nil, fmt.Errorf("listing layers: %w", err)
}
if err := s.Store.AddNames(newImg.ID, []string{r.ImageUrl}); err != nil {
return nil, fmt.Errorf("adding image url as image name: %w", err)
for i, l := range remoteLayers {
digest, err := l.Digest()
if err != nil {
return nil, fmt.Errorf("getting digest of layer %d: %w", i, err)
}
layers = append(layers, digest.String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears to me that this might reasonably be moved into the storeAndVerifyLayers func, i.e. store layers, then return the list of layers. That would also make it easier to add some unittests to the existing ones for this function which are checking that all wanted layers are being returned, regardless of whether they were pulled just now or in a previous image's pull.

}

func (s *ImagePullerService) storeAndVerifyLayers(log *slog.Logger, remoteImg gcr.Image) (string, error) {
func (s *ImagePullerService) storeAndVerifyLayers(log *slog.Logger, remoteImg gcr.Image) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is not really fitting anymore, since we don't do manual digest checks any longer.

Maybe this should even be moved into the new store package, as store.Store(gcr.Image) or store.StoreDeduplicated(...)?

if err != nil {
return "", fmt.Errorf("reading layer %d: %w", idx, err)
}
// TODO(burgerdev): pass a context here?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something here, but it seems like we should quite easily be able to pass in the context from PullImage here?

// While we could be using remoteLayer.Uncompressed(), the gcr implementation currently uses gzip
// from the stdlib, which causes a significant performance hit compared to pgzip. This is why we
// decompress here.
func getDecompressedReader(remoteLayer gcr.Layer) (io.ReadCloser, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really appreciate some unittests for this package. Seems to me it's otherwise easy to get something wrong here in future iterations 😅

@katexochen
Copy link
Member

umoci pulls in libraries with MPL license, need to check whether we can allow this :/

We can use the library and add an exception for it.

@katexochen katexochen added this to the v1.15.0 milestone Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Shiny new feature for our users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants