Jelajahi Sumber

Resolve and store manifest when creating container

This addresses the previous issue with the containerd store where, after a container is created, we can't deterministically resolve which image variant was used to run it (since we also don't store what platform the image was fetched for).

This is required for things like `docker commit`, and computing the containers layer size later, since we need to resolve the specific image variant.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
Laura Brehm 2 tahun lalu
induk
melakukan
a34060cdb4

+ 2 - 0
container/container.go

@@ -39,6 +39,7 @@ import (
 	agentexec "github.com/moby/swarmkit/v2/agent/exec"
 	"github.com/moby/sys/signal"
 	"github.com/moby/sys/symlink"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -72,6 +73,7 @@ type Container struct {
 	Args            []string
 	Config          *containertypes.Config
 	ImageID         image.ID `json:"Image"`
+	ImageManifest   *ocispec.Descriptor
 	NetworkSettings *network.Settings
 	LogPath         string
 	Name            string

+ 38 - 0
daemon/containerd/image.go

@@ -122,6 +122,44 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
 	return img, nil
 }
 
+func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*ocispec.Descriptor, error) {
+	cs := i.client.ContentStore()
+
+	desc, err := i.resolveDescriptor(ctx, refOrID)
+	if err != nil {
+		return nil, err
+	}
+
+	if containerdimages.IsManifestType(desc.MediaType) {
+		return &desc, nil
+	}
+
+	if containerdimages.IsIndexType(desc.MediaType) {
+		platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
+		if options.Platform != nil {
+			platform = cplatforms.Only(*options.Platform)
+		}
+
+		childManifests, err := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)(ctx, desc)
+		if err != nil {
+			if cerrdefs.IsNotFound(err) {
+				return nil, errdefs.NotFound(err)
+			}
+			return nil, errdefs.System(err)
+		}
+
+		// len(childManifests) == 1 since we requested 1 and if none
+		// were found LimitManifests would have thrown an error
+		if !containerdimages.IsManifestType(childManifests[0].MediaType) {
+			return nil, errdefs.NotFound(fmt.Errorf("manifest has incorrect mediatype: %s", childManifests[0].MediaType))
+		}
+
+		return &childManifests[0], nil
+	}
+
+	return nil, errdefs.NotFound(errors.New("failed to find manifest"))
+}
+
 // size returns the total size of the image's packed resources.
 func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform cplatforms.MatchComparer) (int64, error) {
 	var size int64

+ 7 - 9
daemon/containerd/image_commit.go

@@ -16,7 +16,6 @@ import (
 	cerrdefs "github.com/containerd/containerd/errdefs"
 	"github.com/containerd/containerd/images"
 	"github.com/containerd/containerd/leases"
-	"github.com/containerd/containerd/platforms"
 	"github.com/containerd/containerd/rootfs"
 	"github.com/containerd/containerd/snapshots"
 	"github.com/docker/docker/api/types/backend"
@@ -39,20 +38,19 @@ with adaptations to match the Moby data model and services.
 // CommitImage creates a new image from a commit config.
 func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig) (image.ID, error) {
 	container := i.containers.Get(cc.ContainerID)
+	cs := i.client.ContentStore()
 
-	desc, err := i.resolveDescriptor(ctx, container.Config.Image)
+	imageManifestBytes, err := content.ReadBlob(ctx, cs, *container.ImageManifest)
 	if err != nil {
 		return "", err
 	}
 
-	cs := i.client.ContentStore()
-
-	ocimanifest, err := images.Manifest(ctx, cs, desc, platforms.DefaultStrict())
-	if err != nil {
+	var manifest ocispec.Manifest
+	if err := json.Unmarshal(imageManifestBytes, &manifest); err != nil {
 		return "", err
 	}
 
-	imageConfigBytes, err := content.ReadBlob(ctx, cs, ocimanifest.Config)
+	imageConfigBytes, err := content.ReadBlob(ctx, cs, manifest.Config)
 	if err != nil {
 		return "", err
 	}
@@ -88,7 +86,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
 		return "", fmt.Errorf("failed to apply diff: %w", err)
 	}
 
-	layers := append(ocimanifest.Layers, diffLayerDesc)
+	layers := append(manifest.Layers, diffLayerDesc)
 	commitManifestDesc, configDigest, err := writeContentsForImage(ctx, i.snapshotter, cs, imageConfig, layers)
 	if err != nil {
 		return "", err
@@ -96,7 +94,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
 
 	// image create
 	img := images.Image{
-		Name:      configDigest.String(),
+		Name:      danglingImageName(configDigest.Digest()),
 		Target:    commitManifestDesc,
 		CreatedAt: time.Now(),
 	}

+ 17 - 5
daemon/create.go

@@ -115,11 +115,12 @@ func (daemon *Daemon) containerCreate(ctx context.Context, opts createOpts) (con
 // Create creates a new container from the given configuration with a given name.
 func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *container.Container, retErr error) {
 	var (
-		ctr   *container.Container
-		img   *image.Image
-		imgID image.ID
-		err   error
-		os    = runtime.GOOS
+		ctr         *container.Container
+		img         *image.Image
+		imgManifest *v1.Descriptor
+		imgID       image.ID
+		err         error
+		os          = runtime.GOOS
 	)
 
 	if opts.params.Config.Image != "" {
@@ -127,6 +128,16 @@ func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *contai
 		if err != nil {
 			return nil, err
 		}
+		// when using the containerd store, we need to get the actual
+		// image manifest so we can store it and later deterministically
+		// resolve the specific image the container is running
+		if daemon.UsesSnapshotter() {
+			imgManifest, err = daemon.imageService.GetImageManifest(ctx, opts.params.Config.Image, imagetypes.GetImageOpts{Platform: opts.params.Platform})
+			if err != nil {
+				logrus.WithError(err).Error("failed to find image manifest")
+				return nil, err
+			}
+		}
 		os = img.OperatingSystem()
 		imgID = img.ID()
 	} else if isWindows {
@@ -169,6 +180,7 @@ func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *contai
 	}
 
 	ctr.HostConfig.StorageOpt = opts.params.HostConfig.StorageOpt
+	ctr.ImageManifest = imgManifest
 
 	if daemon.UsesSnapshotter() {
 		if err := daemon.imageService.PrepareSnapshot(ctx, ctr.ID, opts.params.Config.Image, opts.params.Platform); err != nil {

+ 1 - 0
daemon/image_service.go

@@ -46,6 +46,7 @@ type ImageService interface {
 	// Containerd related methods
 
 	PrepareSnapshot(ctx context.Context, id string, image string, platform *v1.Platform) error
+	GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*v1.Descriptor, error)
 
 	// Layers
 

+ 4 - 0
daemon/images/image.go

@@ -192,6 +192,10 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
 	return img, nil
 }
 
+func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*v1.Descriptor, error) {
+	panic("not implemented")
+}
+
 func (i *ImageService) getImage(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (retImg *image.Image, retErr error) {
 	defer func() {
 		if retErr != nil || retImg == nil || options.Platform == nil {