Skip to content

Commit a8b7e19

Browse files
authored
Merge pull request kubernetes#121456 from kiashok/addRuntimeClassInCriFeatureGate
KEP 4216: Add changes for alpha version under RuntimeClassInImageCriApi feature gate
2 parents 6abff74 + 252e1d2 commit a8b7e19

14 files changed

+536
-51
lines changed

pkg/features/kube_features.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,13 @@ const (
718718
// certificate as expiration approaches.
719719
RotateKubeletServerCertificate featuregate.Feature = "RotateKubeletServerCertificate"
720720

721+
// owner: @kiashok
722+
// kep: https://kep.k8s.io/4216
723+
// alpha: v1.29
724+
//
725+
// Adds support to pull images based on the runtime class specified.
726+
RuntimeClassInImageCriAPI featuregate.Feature = "RuntimeClassInImageCriApi"
727+
721728
// owner: @danielvegamyhre
722729
// kep: https://kep.k8s.io/2413
723730
// beta: v1.27
@@ -1149,6 +1156,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
11491156

11501157
RotateKubeletServerCertificate: {Default: true, PreRelease: featuregate.Beta},
11511158

1159+
RuntimeClassInImageCriAPI: {Default: false, PreRelease: featuregate.Alpha},
1160+
11521161
ElasticIndexedJob: {Default: true, PreRelease: featuregate.Beta},
11531162

11541163
SchedulerQueueingHints: {Default: true, PreRelease: featuregate.Beta},

pkg/kubelet/container/helpers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ func ConvertPodStatusToRunningPod(runtimeName string, podStatus *PodStatus) Pod
273273
Name: containerStatus.Name,
274274
Image: containerStatus.Image,
275275
ImageID: containerStatus.ImageID,
276+
ImageRuntimeHandler: containerStatus.ImageRuntimeHandler,
276277
Hash: containerStatus.Hash,
277278
HashWithoutResources: containerStatus.HashWithoutResources,
278279
State: containerStatus.State,

pkg/kubelet/container/runtime.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ type Version interface {
5252
type ImageSpec struct {
5353
// ID of the image.
5454
Image string
55+
// Runtime handler used to pull this image
56+
RuntimeHandler string
5557
// The annotations for the image.
5658
// This should be passed to CRI during image pulls and returned when images are listed.
5759
Annotations []Annotation
@@ -282,6 +284,8 @@ type Container struct {
282284
Image string
283285
// The id of the image used by the container.
284286
ImageID string
287+
// Runtime handler used to pull the image if any.
288+
ImageRuntimeHandler string
285289
// Hash of the container, used for comparison. Optional for containers
286290
// not managed by kubelet.
287291
Hash uint64
@@ -347,6 +351,8 @@ type Status struct {
347351
Image string
348352
// ID of the image.
349353
ImageID string
354+
// Runtime handler used to pull the image if any.
355+
ImageRuntimeHandler string
350356
// Hash of the container, used for comparison.
351357
Hash uint64
352358
// Hash of the container over fields with Resources field zero'd out.

pkg/kubelet/images/image_gc_manager.go

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"math"
2424
"sort"
25+
"strings"
2526
"sync"
2627
"time"
2728

@@ -32,8 +33,10 @@ import (
3233
"k8s.io/apimachinery/pkg/util/errors"
3334
"k8s.io/apimachinery/pkg/util/sets"
3435
"k8s.io/apimachinery/pkg/util/wait"
36+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3537
"k8s.io/client-go/tools/record"
3638
statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
39+
"k8s.io/kubernetes/pkg/features"
3740
"k8s.io/kubernetes/pkg/kubelet/container"
3841
"k8s.io/kubernetes/pkg/kubelet/events"
3942
"k8s.io/kubernetes/pkg/kubelet/metrics"
@@ -43,6 +46,10 @@ import (
4346
// instrumentationScope is OpenTelemetry instrumentation scope name
4447
const instrumentationScope = "k8s.io/kubernetes/pkg/kubelet/images"
4548

49+
// When RuntimeClassInImageCriAPI feature gate is enabled, imageRecord is
50+
// indexed as imageId-RuntimeHandler
51+
const imageIndexTupleFormat = "%s,%s"
52+
4653
// StatsProvider is an interface for fetching stats used during image garbage
4754
// collection.
4855
type StatsProvider interface {
@@ -90,7 +97,12 @@ type realImageGCManager struct {
9097
// Container runtime
9198
runtime container.Runtime
9299

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.
94106
imageRecords map[string]*imageRecord
95107
imageRecordsLock sync.Mutex
96108

@@ -149,6 +161,8 @@ func (i *imageCache) get() []container.Image {
149161

150162
// Information about the images we track.
151163
type imageRecord struct {
164+
// runtime handler used to pull this image
165+
runtimeHandlerUsedToPullImage string
152166
// Time when this image was first detected.
153167
firstDetected time.Time
154168

@@ -223,6 +237,7 @@ func (im *realImageGCManager) GetImageList() ([]container.Image, error) {
223237
}
224238

225239
func (im *realImageGCManager) detectImages(ctx context.Context, detectTime time.Time) (sets.String, error) {
240+
isRuntimeClassInImageCriAPIEnabled := utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClassInImageCriAPI)
226241
imagesInUse := sets.NewString()
227242

228243
images, err := im.runtime.ListImages(ctx)
@@ -237,8 +252,14 @@ func (im *realImageGCManager) detectImages(ctx context.Context, detectTime time.
237252
// Make a set of images in use by containers.
238253
for _, pod := range pods {
239254
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+
}
242263
}
243264
}
244265

@@ -248,28 +269,36 @@ func (im *realImageGCManager) detectImages(ctx context.Context, detectTime time.
248269
im.imageRecordsLock.Lock()
249270
defer im.imageRecordsLock.Unlock()
250271
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)
253281

254282
// 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,
259288
}
260289
}
261290

262291
// 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
266295
}
267296

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
270299

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
273302
}
274303

275304
// Remove old images from our records.
@@ -391,7 +420,7 @@ func (im *realImageGCManager) freeSpace(ctx context.Context, bytesToFree int64,
391420
var deletionErrors []error
392421
spaceFreed := int64(0)
393422
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)
395424
// Images that are currently in used were given a newer lastUsed.
396425
if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) {
397426
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,
423452
}
424453

425454
func (im *realImageGCManager) freeImage(ctx context.Context, image evictionInfo) error {
455+
isRuntimeClassInImageCriAPIEnabled := utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClassInImageCriAPI)
426456
// 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})
429460
if err != nil {
430461
return err
431462
}
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+
433470
metrics.ImageGarbageCollectedTotal.Inc()
434471
return err
435472
}
436473

437474
// 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.
438475
func (im *realImageGCManager) imagesInEvictionOrder(ctx context.Context, freeTime time.Time) ([]evictionInfo, error) {
476+
isRuntimeClassInImageCriAPIEnabled := utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClassInImageCriAPI)
439477
imagesInUse, err := im.detectImages(ctx, freeTime)
440478
if err != nil {
441479
return nil, err
@@ -457,15 +495,46 @@ func (im *realImageGCManager) imagesInEvictionOrder(ctx context.Context, freeTim
457495
continue
458496

459497
}
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+
}
464515
}
465516
sort.Sort(byLastUsedAndDetected(images))
466517
return images, nil
467518
}
468519

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+
469538
type evictionInfo struct {
470539
id string
471540
imageRecord

0 commit comments

Comments
 (0)