Cloud Native 7 min read

Kubelet Source Code Deep Dive: Image Management and Garbage Collection

This article dissects Kubelet's ImageManager and ImageGCManager, detailing their interfaces, implementation structs, pull‑policy handling, parallel image pulling, GC policy thresholds, and the step‑by‑step execution flow that governs image lifecycle on a node.

Infra Learning Club
Infra Learning Club
Infra Learning Club
Kubelet Source Code Deep Dive: Image Management and Garbage Collection

ImageManager Interface

The ImageManager interface defines a single method that guarantees an image reference exists before a pod starts.

type ImageManager interface {
    // EnsureImageExists ensures the image referenced by imgRef exists.
    EnsureImageExists(ctx context.Context, objRef *v1.ObjectReference, pod *v1.Pod, imgRef string, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig, podRuntimeHandler string, pullPolicy v1.PullPolicy) (string, string, error)
}

ImageManager Implementation

The concrete imageManager struct holds the components needed for image handling.

type imageManager struct {
    recorder                 record.EventRecorder
    imageService             kubecontainer.ImageService
    backOff                  *flowcontrol.Backoff
    puller                   imagePuller
    podPullingTimeRecorder   ImagePodPullingTimeRecorder
}

Image Puller Configuration

The imagePuller can operate in serial or parallel mode, controlled by the Kubelet flag serialize-image-pulls. The default value is false. For Docker runtimes older than version 1.9, the flag must be set to true to avoid concurrency issues.

EnsureImageExists Execution Flow

Perform a pre‑check of the image using imagePullPrecheck.

If the image is not present according to the pull policy, invoke the puller to fetch the image.

imageRef, message, err = m.imagePullPrecheck(ctx, objRef, logPrefix, pullPolicy, &spec, imgRef)

The pre‑check switches on the pull policy:

func (m *imageManager) imagePullPrecheck(ctx context.Context, objRef *v1.ObjectReference, logPrefix string, pullPolicy v1.PullPolicy, spec *kubecontainer.ImageSpec, imgRef string) (imageRef string, msg string, err error) {
    switch pullPolicy {
    case v1.PullAlways:
        return "", msg, nil
    case v1.PullIfNotPresent:
        imageRef, err = m.imageService.GetImageRef(ctx, *spec)
        if err != nil {
            msg = fmt.Sprintf("Failed to inspect image %q: %v", imageRef, err)
            m.logIt(objRef, v1.EventTypeWarning, events.FailedToInspectImage, logPrefix, msg, klog.Warning)
            return "", msg, ErrImageInspect
        }
        return imageRef, msg, nil
    case v1.PullNever:
        imageRef, err = m.imageService.GetImageRef(ctx, *spec)
        if err != nil {
            msg = fmt.Sprintf("Failed to inspect image %q: %v", imageRef, err)
            m.logIt(objRef, v1.EventTypeWarning, events.FailedToInspectImage, logPrefix, msg, klog.Warning)
            return "", msg, ErrImageInspect
        }
        if imageRef == "" {
            msg = fmt.Sprintf("Container image %q is not present with pull policy of Never", imgRef)
            m.logIt(objRef, v1.EventTypeWarning, events.ErrImageNeverPullPolicy, logPrefix, msg, klog.Warning)
            return "", msg, ErrImageNeverPull
        }
        return imageRef, msg, nil
    }
    return
}

If the image must be pulled, the manager calls the puller:

m.puller.pullImage(ctx, spec, pullSecrets, pullChan, podSandboxConfig)

Parallel Image Puller

The parallel implementation limits concurrency with a token channel.

func (pip *parallelImagePuller) pullImage(ctx context.Context, spec kubecontainer.ImageSpec, pullSecrets []v1.Secret, pullChan chan<- pullResult, podSandboxConfig *runtimeapi.PodSandboxConfig) {
    go func() {
        if pip.tokens != nil {
            // Acquire a token to limit concurrency.
            pip.tokens <- struct{}{}
            defer func() { <-pip.tokens }()
        }
        startTime := time.Now()
        // Invoke the CRI image pull API.
        imageRef, err := pip.imageService.PullImage(ctx, spec, pullSecrets, podSandboxConfig)
        var size uint64
        if err == nil && imageRef != "" {
            // Retrieve image size; ignore errors.
            size, _ = pip.imageService.GetImageSize(ctx, spec)
        }
        // Send result back on the channel.
        pullChan <- pullResult{imageRef: imageRef, imageSize: size, err: err, pullDuration: time.Since(startTime)}
    }()
}

ImageGCManager Interface and Policy

type ImageGCManager interface {
    GarbageCollect(ctx context.Context, beganGC time.Time) error
    Start()
    GetImageList() ([]container.Image, error)
    DeleteUnusedImages(ctx context.Context) error
}
type ImageGCPolicy struct {
    // Trigger GC when usage exceeds this percent.
    HighThresholdPercent int
    // Do not trigger GC when usage is below this percent.
    LowThresholdPercent int
    // Minimum age before an image is considered for GC.
    MinAge time.Duration
    // Delete regardless of disk usage after this age.
    MaxAge time.Duration
}

Garbage Collection Execution Flow

Query all images and sort them by lastUsed timestamp in descending order.

Delete images whose lastUsed exceeds MaxAge.

Obtain filesystem image usage statistics via ImageFsStats.

Calculate the usage percentage; if it meets or exceeds HighThresholdPercent, free space to satisfy the policy.

// Step 1: list images in eviction order
images, err := im.imagesInEvictionOrder(ctx, freeTime)

// Step 2: free old images based on MaxAge
images, err = im.freeOldImages(ctx, images, freeTime, beganGC)

// Step 3: retrieve image filesystem stats
fsStats, _, err := im.statsProvider.ImageFsStats(ctx)

// Step 4: compute usage percent and trigger GC if needed
usagePercent := 100 - int(available*100/capacity)
if usagePercent >= im.policy.HighThresholdPercent {
    freed, err := im.freeSpace(ctx, amountToFree, freeTime, images)
}

The GC process balances image age, disk‑usage thresholds, and explicit policy limits to reclaim space on a node.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

KubernetesGoGarbageCollectionkubeletContainerRuntimeImageGCImageManager
Infra Learning Club
Written by

Infra Learning Club

Infra Learning Club shares study notes, cutting-edge technology, and career discussions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.