diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 43d180ec6d3..279cd055c04 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -371,6 +371,8 @@ type Image struct { Size int64 // ImageSpec for the image which include annotations. Spec ImageSpec + // Pin for preventing garbage collection + Pinned bool } // EnvVar represents the environment variable. diff --git a/pkg/kubelet/images/image_gc_manager.go b/pkg/kubelet/images/image_gc_manager.go index ac1651d2518..0debd422fe2 100644 --- a/pkg/kubelet/images/image_gc_manager.go +++ b/pkg/kubelet/images/image_gc_manager.go @@ -338,6 +338,16 @@ func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) ( im.imageRecordsLock.Lock() defer im.imageRecordsLock.Unlock() + // Make the ListImages into a map to grab an image by ID + allImages, err := im.runtime.ListImages() + if err != nil { + return 0, err + } + imagesMap := make(map[string]container.Image, len(allImages)) + for _, img := range allImages { + imagesMap[img.ID] = img + } + // Get all images in eviction order. images := make([]evictionInfo, 0, len(im.imageRecords)) for image, record := range im.imageRecords { @@ -345,6 +355,12 @@ func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) ( klog.V(5).InfoS("Image ID is being used", "imageID", image) continue } + // Check if image is pinned, prevent garbage collection + if imagesMap[image].Pinned { + klog.V(5).InfoS("Image is pinned, skipping garbage collection", "imageID", image) + continue + + } images = append(images, evictionInfo{ id: image, imageRecord: *record, diff --git a/pkg/kubelet/images/image_gc_manager_test.go b/pkg/kubelet/images/image_gc_manager_test.go index e7c2ef24f47..299d004dd69 100644 --- a/pkg/kubelet/images/image_gc_manager_test.go +++ b/pkg/kubelet/images/image_gc_manager_test.go @@ -206,6 +206,89 @@ func TestDeleteUnusedImagesExemptSandboxImage(t *testing.T) { require.NoError(t, err) } +func TestDeletePinnedImage(t *testing.T) { + manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + { + ID: sandboxImage, + Size: 1024, + Pinned: true, + }, + { + ID: sandboxImage, + Size: 1024, + }, + } + + err := manager.DeleteUnusedImages() + assert := assert.New(t) + assert.Len(fakeRuntime.ImageList, 2) + require.NoError(t, err) +} + +func TestDoNotDeletePinnedImage(t *testing.T) { + manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + { + ID: "1", + Size: 1024, + Pinned: true, + }, + { + ID: "2", + Size: 1024, + }, + } + + spaceFreed, err := manager.freeSpace(4096, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(1024, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} + +func TestDeleteUnPinnedImage(t *testing.T) { + manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + { + ID: "1", + Size: 1024, + Pinned: false, + }, + { + ID: "2", + Size: 1024, + }, + } + + spaceFreed, err := manager.freeSpace(2048, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(2048, spaceFreed) + assert.Len(fakeRuntime.ImageList, 0) +} + +func TestAllPinnedImages(t *testing.T) { + manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + { + ID: "1", + Size: 1024, + Pinned: true, + }, + { + ID: "2", + Size: 1024, + Pinned: true, + }, + } + + spaceFreed, err := manager.freeSpace(2048, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(0, spaceFreed) + assert.Len(fakeRuntime.ImageList, 2) +} func TestDetectImagesContainerStopped(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() diff --git a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go index 68cc754d089..a50bca35cd6 100644 --- a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go +++ b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go @@ -6048,7 +6048,7 @@ type Image struct { // ImageSpec for image which includes annotations Spec *ImageSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // Recommendation on whether this image should be exempt from garbage collection. - // It must only be treated as a recommendation--the client can still request the image be deleted, + // It must only be treated as a recommendation -- the client can still request that the image be deleted, // and the runtime must oblige. Pinned bool `protobuf:"varint,8,opt,name=pinned,proto3" json:"pinned,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` diff --git a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto index 6411df04507..8f96c97ebc6 100644 --- a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto +++ b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto @@ -1248,7 +1248,7 @@ message Image { // ImageSpec for image which includes annotations ImageSpec spec = 7; // Recommendation on whether this image should be exempt from garbage collection. - // It must only be treated as a recommendation--the client can still request the image be deleted, + // It must only be treated as a recommendation -- the client can still request that the image be deleted, // and the runtime must oblige. bool pinned = 8; } diff --git a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.pb.go b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.pb.go index c60c8d7470a..66bfd8e552b 100644 --- a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.pb.go +++ b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.pb.go @@ -6057,7 +6057,7 @@ type Image struct { // ImageSpec for image which includes annotations Spec *ImageSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // Recommendation on whether this image should be exempt from garbage collection. - // It must only be treated as a recommendation--the client can still request the image be deleted, + // It must only be treated as a recommendation -- the client can still request that the image be deleted, // and the runtime must oblige. Pinned bool `protobuf:"varint,8,opt,name=pinned,proto3" json:"pinned,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` diff --git a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.proto b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.proto index 33c8574049e..ba3230d183b 100644 --- a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.proto +++ b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2/api.proto @@ -1256,7 +1256,7 @@ message Image { // ImageSpec for image which includes annotations ImageSpec spec = 7; // Recommendation on whether this image should be exempt from garbage collection. - // It must only be treated as a recommendation--the client can still request the image be deleted, + // It must only be treated as a recommendation -- the client can still request that the image be deleted, // and the runtime must oblige. bool pinned = 8; }