Browse Source

c8d/prune: Handle containers started from image id

If an image is only by id instead of its name, don't prune it
completely. but only untag it and create a dangling image for it.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 2 years ago
parent
commit
e638351ef9
1 changed files with 48 additions and 19 deletions
  1. 48 19
      daemon/containerd/image_prune.go

+ 48 - 19
daemon/containerd/image_prune.go

@@ -2,6 +2,7 @@ package containerd
 
 import (
 	"context"
+	"strings"
 
 	cerrdefs "github.com/containerd/containerd/errdefs"
 	containerdimages "github.com/containerd/containerd/images"
@@ -70,38 +71,50 @@ func (i *ImageService) pruneUnused(ctx context.Context, filterFunc imageFilterFu
 		return nil, err
 	}
 
+	// How many images make reference to a particular target digest.
+	digestRefCount := map[digest.Digest]int{}
+	// Images considered for pruning.
 	imagesToPrune := map[string]containerdimages.Image{}
 	for _, img := range allImages {
-		if !danglingOnly || isDanglingImage(img) {
-			imagesToPrune[img.Name] = img
-		}
-	}
+		digestRefCount[img.Target.Digest] += 1
 
-	// Apply filters
-	for name, img := range imagesToPrune {
-		filteredOut := !filterFunc(img)
-		log.G(ctx).WithFields(logrus.Fields{
-			"image":       name,
-			"filteredOut": filteredOut,
-		}).Debug("filtering image")
+		if !danglingOnly || isDanglingImage(img) {
+			canBePruned := filterFunc(img)
+			log.G(ctx).WithFields(logrus.Fields{
+				"image":       img.Name,
+				"canBePruned": canBePruned,
+			}).Debug("considering image for pruning")
+
+			if canBePruned {
+				imagesToPrune[img.Name] = img
+			}
 
-		if filteredOut {
-			delete(imagesToPrune, name)
 		}
 	}
 
-	containers := i.containers.List()
+	// Image specified by digests that are used by containers.
+	usedDigests := map[digest.Digest]struct{}{}
 
-	var errs error
 	// Exclude images used by existing containers
-	for _, ctr := range containers {
+	for _, ctr := range i.containers.List() {
 		// If the original image was deleted, make sure we don't delete the dangling image
 		delete(imagesToPrune, danglingImageName(ctr.ImageID.Digest()))
 
 		// Config.Image is the image reference passed by user.
-		// For example: container created by `docker run alpine` will have Image="alpine"
-		// Warning: This doesn't handle truncated ids:
-		//          `docker run 124c7d2` will have Image="124c7d270790"
+		// Config.ImageID is the resolved content digest based on the user's Config.Image.
+		// For example: container created by:
+		//           `docker run alpine` will have Config.Image="alpine"
+		//           `docker run 82d1e9d` will have Config.Image="82d1e9d"
+		// but both will have ImageID="sha256:82d1e9d7ed48a7523bdebc18cf6290bdb97b82302a8a9c27d4fe885949ea94d1"
+		imageDgst := ctr.ImageID.Digest()
+
+		// If user didn't specify an explicit image, mark the digest as used.
+		normalizedImageID := "sha256:" + strings.TrimPrefix(ctr.Config.Image, "sha256:")
+		if strings.HasPrefix(imageDgst.String(), normalizedImageID) {
+			usedDigests[imageDgst] = struct{}{}
+			continue
+		}
+
 		ref, err := reference.ParseNormalizedNamed(ctr.Config.Image)
 		log.G(ctx).WithFields(logrus.Fields{
 			"ctr":          ctr.ID,
@@ -110,12 +123,28 @@ func (i *ImageService) pruneUnused(ctx context.Context, filterFunc imageFilterFu
 		}).Debug("filtering container's image")
 
 		if err == nil {
+			// If user provided a specific image name, exclude that image.
 			name := reference.TagNameOnly(ref)
 			delete(imagesToPrune, name.String())
 		}
 	}
 
+	// Create dangling images for images that will be deleted but are still in use.
+	for _, img := range imagesToPrune {
+		dgst := img.Target.Digest
+
+		digestRefCount[dgst] -= 1
+		if digestRefCount[dgst] == 0 {
+			if _, isUsed := usedDigests[dgst]; isUsed {
+				if err := i.ensureDanglingImage(ctx, img); err != nil {
+					return &report, errors.Wrapf(err, "failed to create ensure dangling image for %s", img.Name)
+				}
+			}
+		}
+	}
+
 	possiblyDeletedConfigs := map[digest.Digest]struct{}{}
+	var errs error
 
 	// Workaround for https://github.com/moby/buildkit/issues/3797
 	defer func() {