@@ -22,6 +22,7 @@ import (
22
22
"fmt"
23
23
"math"
24
24
"sort"
25
+ "strings"
25
26
"sync"
26
27
"time"
27
28
@@ -32,8 +33,10 @@ import (
32
33
"k8s.io/apimachinery/pkg/util/errors"
33
34
"k8s.io/apimachinery/pkg/util/sets"
34
35
"k8s.io/apimachinery/pkg/util/wait"
36
+ utilfeature "k8s.io/apiserver/pkg/util/feature"
35
37
"k8s.io/client-go/tools/record"
36
38
statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
39
+ "k8s.io/kubernetes/pkg/features"
37
40
"k8s.io/kubernetes/pkg/kubelet/container"
38
41
"k8s.io/kubernetes/pkg/kubelet/events"
39
42
"k8s.io/kubernetes/pkg/kubelet/metrics"
@@ -43,6 +46,10 @@ import (
43
46
// instrumentationScope is OpenTelemetry instrumentation scope name
44
47
const instrumentationScope = "k8s.io/kubernetes/pkg/kubelet/images"
45
48
49
+ // When RuntimeClassInImageCriAPI feature gate is enabled, imageRecord is
50
+ // indexed as imageId-RuntimeHandler
51
+ const imageIndexTupleFormat = "%s,%s"
52
+
46
53
// StatsProvider is an interface for fetching stats used during image garbage
47
54
// collection.
48
55
type StatsProvider interface {
@@ -90,7 +97,12 @@ type realImageGCManager struct {
90
97
// Container runtime
91
98
runtime container.Runtime
92
99
93
- // Records of images and their use.
100
+ // Records of images and their use. Indexed by ImageId.
101
+ // If RuntimeClassInImageCriAPI feature gate is enabled, imageRecords
102
+ // are identified by a tuple of (imageId,runtimeHandler) that is passed
103
+ // from ListImages() call. If no runtimehandler is specified in response
104
+ // to ListImages() by the container runtime, only imageID will be used as
105
+ // the index of this map.
94
106
imageRecords map [string ]* imageRecord
95
107
imageRecordsLock sync.Mutex
96
108
@@ -149,6 +161,8 @@ func (i *imageCache) get() []container.Image {
149
161
150
162
// Information about the images we track.
151
163
type imageRecord struct {
164
+ // runtime handler used to pull this image
165
+ runtimeHandlerUsedToPullImage string
152
166
// Time when this image was first detected.
153
167
firstDetected time.Time
154
168
@@ -223,6 +237,7 @@ func (im *realImageGCManager) GetImageList() ([]container.Image, error) {
223
237
}
224
238
225
239
func (im * realImageGCManager ) detectImages (ctx context.Context , detectTime time.Time ) (sets.String , error ) {
240
+ isRuntimeClassInImageCriAPIEnabled := utilfeature .DefaultFeatureGate .Enabled (features .RuntimeClassInImageCriAPI )
226
241
imagesInUse := sets .NewString ()
227
242
228
243
images , err := im .runtime .ListImages (ctx )
@@ -237,8 +252,14 @@ func (im *realImageGCManager) detectImages(ctx context.Context, detectTime time.
237
252
// Make a set of images in use by containers.
238
253
for _ , pod := range pods {
239
254
for _ , container := range pod .Containers {
240
- klog .V (5 ).InfoS ("Container uses image" , "pod" , klog .KRef (pod .Namespace , pod .Name ), "containerName" , container .Name , "containerImage" , container .Image , "imageID" , container .ImageID )
241
- imagesInUse .Insert (container .ImageID )
255
+ if ! isRuntimeClassInImageCriAPIEnabled {
256
+ klog .V (5 ).InfoS ("Container uses image" , "pod" , klog .KRef (pod .Namespace , pod .Name ), "containerName" , container .Name , "containerImage" , container .Image , "imageID" , container .ImageID )
257
+ imagesInUse .Insert (container .ImageID )
258
+ } else {
259
+ imageKey := getImageTuple (container .ImageID , container .ImageRuntimeHandler )
260
+ klog .V (5 ).InfoS ("Container uses image" , "pod" , klog .KRef (pod .Namespace , pod .Name ), "containerName" , container .Name , "containerImage" , container .Image , "imageID" , container .ImageID , "imageKey" , imageKey )
261
+ imagesInUse .Insert (imageKey )
262
+ }
242
263
}
243
264
}
244
265
@@ -248,28 +269,36 @@ func (im *realImageGCManager) detectImages(ctx context.Context, detectTime time.
248
269
im .imageRecordsLock .Lock ()
249
270
defer im .imageRecordsLock .Unlock ()
250
271
for _ , image := range images {
251
- klog .V (5 ).InfoS ("Adding image ID to currentImages" , "imageID" , image .ID )
252
- currentImages .Insert (image .ID )
272
+ imageKey := image .ID
273
+ if ! isRuntimeClassInImageCriAPIEnabled {
274
+ klog .V (5 ).InfoS ("Adding image ID to currentImages" , "imageID" , imageKey )
275
+ } else {
276
+ imageKey = getImageTuple (image .ID , image .Spec .RuntimeHandler )
277
+ klog .V (5 ).InfoS ("Adding image ID with runtime class to currentImages" , "imageKey" , imageKey , "runtimeHandler" , image .Spec .RuntimeHandler )
278
+ }
279
+
280
+ currentImages .Insert (imageKey )
253
281
254
282
// New image, set it as detected now.
255
- if _ , ok := im .imageRecords [image .ID ]; ! ok {
256
- klog .V (5 ).InfoS ("Image ID is new" , "imageID" , image .ID )
257
- im .imageRecords [image .ID ] = & imageRecord {
258
- firstDetected : detectTime ,
283
+ if _ , ok := im .imageRecords [imageKey ]; ! ok {
284
+ klog .V (5 ).InfoS ("Image ID is new" , "imageID" , imageKey , "runtimeHandler" , image .Spec .RuntimeHandler )
285
+ im .imageRecords [imageKey ] = & imageRecord {
286
+ firstDetected : detectTime ,
287
+ runtimeHandlerUsedToPullImage : image .Spec .RuntimeHandler ,
259
288
}
260
289
}
261
290
262
291
// Set last used time to now if the image is being used.
263
- if isImageUsed (image . ID , imagesInUse ) {
264
- klog .V (5 ).InfoS ("Setting Image ID lastUsed" , "imageID" , image . ID , "lastUsed" , now )
265
- im .imageRecords [image . ID ].lastUsed = now
292
+ if isImageUsed (imageKey , imagesInUse ) {
293
+ klog .V (5 ).InfoS ("Setting Image ID lastUsed" , "imageID" , imageKey , "lastUsed" , now )
294
+ im .imageRecords [imageKey ].lastUsed = now
266
295
}
267
296
268
- klog .V (5 ).InfoS ("Image ID has size" , "imageID" , image . ID , "size" , image .Size )
269
- im .imageRecords [image . ID ].size = image .Size
297
+ klog .V (5 ).InfoS ("Image ID has size" , "imageID" , imageKey , "size" , image .Size )
298
+ im .imageRecords [imageKey ].size = image .Size
270
299
271
- klog .V (5 ).InfoS ("Image ID is pinned" , "imageID" , image . ID , "pinned" , image .Pinned )
272
- im .imageRecords [image . ID ].pinned = image .Pinned
300
+ klog .V (5 ).InfoS ("Image ID is pinned" , "imageID" , imageKey , "pinned" , image .Pinned )
301
+ im .imageRecords [imageKey ].pinned = image .Pinned
273
302
}
274
303
275
304
// Remove old images from our records.
@@ -391,7 +420,7 @@ func (im *realImageGCManager) freeSpace(ctx context.Context, bytesToFree int64,
391
420
var deletionErrors []error
392
421
spaceFreed := int64 (0 )
393
422
for _ , image := range images {
394
- klog .V (5 ).InfoS ("Evaluating image ID for possible garbage collection based on disk usage" , "imageID" , image .id )
423
+ klog .V (5 ).InfoS ("Evaluating image ID for possible garbage collection based on disk usage" , "imageID" , image .id , "runtimeHandler" , image . imageRecord . runtimeHandlerUsedToPullImage )
395
424
// Images that are currently in used were given a newer lastUsed.
396
425
if image .lastUsed .Equal (freeTime ) || image .lastUsed .After (freeTime ) {
397
426
klog .V (5 ).InfoS ("Image ID was used too recently, not eligible for garbage collection" , "imageID" , image .id , "lastUsed" , image .lastUsed , "freeTime" , freeTime )
@@ -423,19 +452,28 @@ func (im *realImageGCManager) freeSpace(ctx context.Context, bytesToFree int64,
423
452
}
424
453
425
454
func (im * realImageGCManager ) freeImage (ctx context.Context , image evictionInfo ) error {
455
+ isRuntimeClassInImageCriAPIEnabled := utilfeature .DefaultFeatureGate .Enabled (features .RuntimeClassInImageCriAPI )
426
456
// Remove image. Continue despite errors.
427
- klog .InfoS ("Removing image to free bytes" , "imageID" , image .id , "size" , image .size )
428
- err := im .runtime .RemoveImage (ctx , container.ImageSpec {Image : image .id })
457
+ var err error
458
+ klog .InfoS ("Removing image to free bytes" , "imageID" , image .id , "size" , image .size , "runtimeHandler" , image .runtimeHandlerUsedToPullImage )
459
+ err = im .runtime .RemoveImage (ctx , container.ImageSpec {Image : image .id , RuntimeHandler : image .runtimeHandlerUsedToPullImage })
429
460
if err != nil {
430
461
return err
431
462
}
432
- delete (im .imageRecords , image .id )
463
+
464
+ imageKey := image .id
465
+ if isRuntimeClassInImageCriAPIEnabled {
466
+ imageKey = getImageTuple (image .id , image .runtimeHandlerUsedToPullImage )
467
+ }
468
+ delete (im .imageRecords , imageKey )
469
+
433
470
metrics .ImageGarbageCollectedTotal .Inc ()
434
471
return err
435
472
}
436
473
437
474
// Queries all of the image records and arranges them in a slice of evictionInfo, sorted based on last time used, ignoring images pinned by the runtime.
438
475
func (im * realImageGCManager ) imagesInEvictionOrder (ctx context.Context , freeTime time.Time ) ([]evictionInfo , error ) {
476
+ isRuntimeClassInImageCriAPIEnabled := utilfeature .DefaultFeatureGate .Enabled (features .RuntimeClassInImageCriAPI )
439
477
imagesInUse , err := im .detectImages (ctx , freeTime )
440
478
if err != nil {
441
479
return nil , err
@@ -457,15 +495,46 @@ func (im *realImageGCManager) imagesInEvictionOrder(ctx context.Context, freeTim
457
495
continue
458
496
459
497
}
460
- images = append (images , evictionInfo {
461
- id : image ,
462
- imageRecord : * record ,
463
- })
498
+ if ! isRuntimeClassInImageCriAPIEnabled {
499
+ images = append (images , evictionInfo {
500
+ id : image ,
501
+ imageRecord : * record ,
502
+ })
503
+ } else {
504
+ imageID := getImageIDFromTuple (image )
505
+ // Ensure imageID is valid or else continue
506
+ if imageID == "" {
507
+ im .recorder .Eventf (im .nodeRef , v1 .EventTypeWarning , "ImageID is not valid, skipping, ImageID: %v" , imageID )
508
+ continue
509
+ }
510
+ images = append (images , evictionInfo {
511
+ id : imageID ,
512
+ imageRecord : * record ,
513
+ })
514
+ }
464
515
}
465
516
sort .Sort (byLastUsedAndDetected (images ))
466
517
return images , nil
467
518
}
468
519
520
+ // If RuntimeClassInImageCriAPI feature gate is enabled, imageRecords
521
+ // are identified by a tuple of (imageId,runtimeHandler) that is passed
522
+ // from ListImages() call. If no runtimehandler is specified in response
523
+ // to ListImages() by the container runtime, only imageID will be will
524
+ // be returned.
525
+ func getImageTuple (imageID , runtimeHandler string ) string {
526
+ if runtimeHandler == "" {
527
+ return imageID
528
+ }
529
+ return fmt .Sprintf (imageIndexTupleFormat , imageID , runtimeHandler )
530
+ }
531
+
532
+ // get imageID from the imageTuple
533
+ func getImageIDFromTuple (image string ) string {
534
+ imageTuples := strings .Split (image , "," )
535
+ return imageTuples [0 ]
536
+ }
537
+
469
538
type evictionInfo struct {
470
539
id string
471
540
imageRecord
0 commit comments