diff --git a/pkg/kubelet/container/image_puller_test.go b/pkg/kubelet/container/image_puller_test.go index 0bde69b22ff..baabc0012c4 100644 --- a/pkg/kubelet/container/image_puller_test.go +++ b/pkg/kubelet/container/image_puller_test.go @@ -108,7 +108,7 @@ func TestPuller(t *testing.T) { fakeRecorder := &record.FakeRecorder{} puller := NewImagePuller(fakeRecorder, fakeRuntime, backOff) - fakeRuntime.ImageList = []Image{{"present_image", nil, 0}} + fakeRuntime.ImageList = []Image{{"present_image", nil, nil, 1}} fakeRuntime.Err = c.pullerErr fakeRuntime.InspectErr = c.inspectErr diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 2f53e4faeb0..a7a3a729da8 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -319,6 +319,8 @@ type Image struct { ID string // Other names by which this image is known. RepoTags []string + // Digests by which this image is known. + RepoDigests []string // The size of the image in bytes. Size int64 } diff --git a/pkg/kubelet/container/serialized_image_puller_test.go b/pkg/kubelet/container/serialized_image_puller_test.go index f4ea8f8e19e..4fea2b27482 100644 --- a/pkg/kubelet/container/serialized_image_puller_test.go +++ b/pkg/kubelet/container/serialized_image_puller_test.go @@ -108,7 +108,7 @@ func TestSerializedPuller(t *testing.T) { fakeRecorder := &record.FakeRecorder{} puller := NewSerializedImagePuller(fakeRecorder, fakeRuntime, backOff) - fakeRuntime.ImageList = []Image{{"present_image", nil, 0}} + fakeRuntime.ImageList = []Image{{"present_image", nil, nil, 0}} fakeRuntime.Err = c.pullerErr fakeRuntime.InspectErr = c.inspectErr diff --git a/pkg/kubelet/dockertools/convert.go b/pkg/kubelet/dockertools/convert.go index 05e280cf1c0..08d5257f047 100644 --- a/pkg/kubelet/dockertools/convert.go +++ b/pkg/kubelet/dockertools/convert.go @@ -76,8 +76,9 @@ func toRuntimeImage(image *dockertypes.Image) (*kubecontainer.Image, error) { } return &kubecontainer.Image{ - ID: image.ID, - RepoTags: image.RepoTags, - Size: image.VirtualSize, + ID: image.ID, + RepoTags: image.RepoTags, + RepoDigests: image.RepoDigests, + Size: image.VirtualSize, }, nil } diff --git a/pkg/kubelet/dockertools/convert_test.go b/pkg/kubelet/dockertools/convert_test.go index 174c9223400..a67025bef6b 100644 --- a/pkg/kubelet/dockertools/convert_test.go +++ b/pkg/kubelet/dockertools/convert_test.go @@ -72,12 +72,14 @@ func TestToRuntimeImage(t *testing.T) { original := &dockertypes.Image{ ID: "aeeea", RepoTags: []string{"abc", "def"}, + RepoDigests: []string{"123", "456"}, VirtualSize: 1234, } expected := &kubecontainer.Image{ - ID: "aeeea", - RepoTags: []string{"abc", "def"}, - Size: 1234, + ID: "aeeea", + RepoTags: []string{"abc", "def"}, + RepoDigests: []string{"123", "456"}, + Size: 1234, } actual, err := toRuntimeImage(original) diff --git a/pkg/kubelet/image_manager.go b/pkg/kubelet/image_manager.go index 77d385a3e36..892dcfe2798 100644 --- a/pkg/kubelet/image_manager.go +++ b/pkg/kubelet/image_manager.go @@ -164,6 +164,7 @@ func (im *realImageManager) detectImages(detectTime time.Time) error { imagesInUse := sets.NewString() for _, pod := range pods { for _, container := range pod.Containers { + glog.V(5).Infof("Pod %s/%s, container %s uses image %s", pod.Namespace, pod.Name, container.Name, container.Image) imagesInUse.Insert(container.Image) } } @@ -174,10 +175,12 @@ func (im *realImageManager) detectImages(detectTime time.Time) error { im.imageRecordsLock.Lock() defer im.imageRecordsLock.Unlock() for _, image := range images { + glog.V(5).Infof("Adding image ID %s to currentImages", image.ID) currentImages.Insert(image.ID) // New image, set it as detected now. if _, ok := im.imageRecords[image.ID]; !ok { + glog.V(5).Infof("Image ID %s is new", image.ID) im.imageRecords[image.ID] = &imageRecord{ firstDetected: detectTime, } @@ -185,15 +188,18 @@ func (im *realImageManager) detectImages(detectTime time.Time) error { // Set last used time to now if the image is being used. if isImageUsed(image, imagesInUse) { + glog.V(5).Infof("Setting Image ID %s lastUsed to %v", image.ID, now) im.imageRecords[image.ID].lastUsed = now } + glog.V(5).Infof("Image ID %s has size %d", image.ID, image.Size) im.imageRecords[image.ID].size = image.Size } // Remove old images from our records. for image := range im.imageRecords { if !currentImages.Has(image) { + glog.V(5).Infof("Image ID %s is no longer present; removing from imageRecords", image) delete(im.imageRecords, image) } } @@ -266,8 +272,10 @@ func (im *realImageManager) freeSpace(bytesToFree int64, freeTime time.Time) (in var lastErr error spaceFreed := int64(0) for _, image := range images { + glog.V(5).Infof("Evaluating image ID %s for possible garbage collection", image.id) // Images that are currently in used were given a newer lastUsed. if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) { + glog.V(5).Infof("Image ID %s has lastUsed=%v which is >= freeTime=%v, not eligible for garbage collection", image.id, image.lastUsed, freeTime) break } @@ -275,6 +283,7 @@ func (im *realImageManager) freeSpace(bytesToFree int64, freeTime time.Time) (in // In such a case, the image may have just been pulled down, and will be used by a container right away. if freeTime.Sub(image.firstDetected) < im.policy.MinAge { + glog.V(5).Infof("Image ID %s has age %v which is less than the policy's minAge of %v, not eligible for garbage collection", image.id, freeTime.Sub(image.firstDetected), im.policy.MinAge) continue } @@ -315,11 +324,11 @@ func (ev byLastUsedAndDetected) Less(i, j int) bool { } func isImageUsed(image container.Image, imagesInUse sets.String) bool { - // Check the image ID and all the RepoTags. + // Check the image ID and all the RepoTags and RepoDigests. if _, ok := imagesInUse[image.ID]; ok { return true } - for _, tag := range image.RepoTags { + for _, tag := range append(image.RepoTags, image.RepoDigests...) { if _, ok := imagesInUse[tag]; ok { return true } diff --git a/pkg/kubelet/image_manager_test.go b/pkg/kubelet/image_manager_test.go index 2f2c885737f..57de1a6e63d 100644 --- a/pkg/kubelet/image_manager_test.go +++ b/pkg/kubelet/image_manager_test.go @@ -337,6 +337,34 @@ func TestFreeSpaceImagesAlsoDoesLookupByRepoTags(t *testing.T) { assert.Len(fakeRuntime.ImageList, 1) } +func TestFreeSpaceImagesAlsoDoesLookupByRepoDigests(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + { + ID: "5678", + RepoDigests: []string{"potato", "salad"}, + Size: 2048, + }, + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + { + ID: container.ContainerID{Type: "test", ID: "c5678"}, + Image: "salad", + }, + }, + }, + } + + spaceFreed, err := manager.freeSpace(1024, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(1024, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} + func TestGarbageCollectBelowLowThreshold(t *testing.T) { policy := ImageGCPolicy{ HighThresholdPercent: 90, diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index fb822ee9a37..e93cb838ea5 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -3009,7 +3009,7 @@ func (kl *Kubelet) setNodeStatusImages(node *api.Node) { } else { for _, image := range containerImages { imagesOnNode = append(imagesOnNode, api.ContainerImage{ - Names: image.RepoTags, + Names: append(image.RepoTags, image.RepoDigests...), SizeBytes: image.Size, }) }