k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/images/image_manager.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package images
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/client-go/tools/record"
    28  	"k8s.io/client-go/util/flowcontrol"
    29  	"k8s.io/klog/v2"
    30  
    31  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    32  	crierrors "k8s.io/cri-api/pkg/errors"
    33  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    34  	"k8s.io/kubernetes/pkg/kubelet/events"
    35  	"k8s.io/kubernetes/pkg/kubelet/metrics"
    36  	"k8s.io/kubernetes/pkg/util/parsers"
    37  )
    38  
    39  type ImagePodPullingTimeRecorder interface {
    40  	RecordImageStartedPulling(podUID types.UID)
    41  	RecordImageFinishedPulling(podUID types.UID)
    42  }
    43  
    44  // imageManager provides the functionalities for image pulling.
    45  type imageManager struct {
    46  	recorder     record.EventRecorder
    47  	imageService kubecontainer.ImageService
    48  	backOff      *flowcontrol.Backoff
    49  	// It will check the presence of the image, and report the 'image pulling', image pulled' events correspondingly.
    50  	puller imagePuller
    51  
    52  	podPullingTimeRecorder ImagePodPullingTimeRecorder
    53  }
    54  
    55  var _ ImageManager = &imageManager{}
    56  
    57  // NewImageManager instantiates a new ImageManager object.
    58  func NewImageManager(recorder record.EventRecorder, imageService kubecontainer.ImageService, imageBackOff *flowcontrol.Backoff, serialized bool, maxParallelImagePulls *int32, qps float32, burst int, podPullingTimeRecorder ImagePodPullingTimeRecorder) ImageManager {
    59  	imageService = throttleImagePulling(imageService, qps, burst)
    60  
    61  	var puller imagePuller
    62  	if serialized {
    63  		puller = newSerialImagePuller(imageService)
    64  	} else {
    65  		puller = newParallelImagePuller(imageService, maxParallelImagePulls)
    66  	}
    67  	return &imageManager{
    68  		recorder:               recorder,
    69  		imageService:           imageService,
    70  		backOff:                imageBackOff,
    71  		puller:                 puller,
    72  		podPullingTimeRecorder: podPullingTimeRecorder,
    73  	}
    74  }
    75  
    76  // shouldPullImage returns whether we should pull an image according to
    77  // the presence and pull policy of the image.
    78  func shouldPullImage(container *v1.Container, imagePresent bool) bool {
    79  	if container.ImagePullPolicy == v1.PullNever {
    80  		return false
    81  	}
    82  
    83  	if container.ImagePullPolicy == v1.PullAlways ||
    84  		(container.ImagePullPolicy == v1.PullIfNotPresent && (!imagePresent)) {
    85  		return true
    86  	}
    87  
    88  	return false
    89  }
    90  
    91  // records an event using ref, event msg.  log to glog using prefix, msg, logFn
    92  func (m *imageManager) logIt(ref *v1.ObjectReference, eventtype, event, prefix, msg string, logFn func(args ...interface{})) {
    93  	if ref != nil {
    94  		m.recorder.Event(ref, eventtype, event, msg)
    95  	} else {
    96  		logFn(fmt.Sprint(prefix, " ", msg))
    97  	}
    98  }
    99  
   100  // EnsureImageExists pulls the image for the specified pod and container, and returns
   101  // (imageRef, error message, error).
   102  func (m *imageManager) EnsureImageExists(ctx context.Context, pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig, podRuntimeHandler string) (string, string, error) {
   103  	logPrefix := fmt.Sprintf("%s/%s/%s", pod.Namespace, pod.Name, container.Image)
   104  	ref, err := kubecontainer.GenerateContainerRef(pod, container)
   105  	if err != nil {
   106  		klog.ErrorS(err, "Couldn't make a ref to pod", "pod", klog.KObj(pod), "containerName", container.Name)
   107  	}
   108  
   109  	// If the image contains no tag or digest, a default tag should be applied.
   110  	image, err := applyDefaultImageTag(container.Image)
   111  	if err != nil {
   112  		msg := fmt.Sprintf("Failed to apply default image tag %q: %v", container.Image, err)
   113  		m.logIt(ref, v1.EventTypeWarning, events.FailedToInspectImage, logPrefix, msg, klog.Warning)
   114  		return "", msg, ErrInvalidImageName
   115  	}
   116  
   117  	var podAnnotations []kubecontainer.Annotation
   118  	for k, v := range pod.GetAnnotations() {
   119  		podAnnotations = append(podAnnotations, kubecontainer.Annotation{
   120  			Name:  k,
   121  			Value: v,
   122  		})
   123  	}
   124  
   125  	spec := kubecontainer.ImageSpec{
   126  		Image:          image,
   127  		Annotations:    podAnnotations,
   128  		RuntimeHandler: podRuntimeHandler,
   129  	}
   130  
   131  	imageRef, err := m.imageService.GetImageRef(ctx, spec)
   132  	if err != nil {
   133  		msg := fmt.Sprintf("Failed to inspect image %q: %v", container.Image, err)
   134  		m.logIt(ref, v1.EventTypeWarning, events.FailedToInspectImage, logPrefix, msg, klog.Warning)
   135  		return "", msg, ErrImageInspect
   136  	}
   137  
   138  	present := imageRef != ""
   139  	if !shouldPullImage(container, present) {
   140  		if present {
   141  			msg := fmt.Sprintf("Container image %q already present on machine", container.Image)
   142  			m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, msg, klog.Info)
   143  			return imageRef, "", nil
   144  		}
   145  		msg := fmt.Sprintf("Container image %q is not present with pull policy of Never", container.Image)
   146  		m.logIt(ref, v1.EventTypeWarning, events.ErrImageNeverPullPolicy, logPrefix, msg, klog.Warning)
   147  		return "", msg, ErrImageNeverPull
   148  	}
   149  
   150  	backOffKey := fmt.Sprintf("%s_%s", pod.UID, container.Image)
   151  	if m.backOff.IsInBackOffSinceUpdate(backOffKey, m.backOff.Clock.Now()) {
   152  		msg := fmt.Sprintf("Back-off pulling image %q", container.Image)
   153  		m.logIt(ref, v1.EventTypeNormal, events.BackOffPullImage, logPrefix, msg, klog.Info)
   154  		return "", msg, ErrImagePullBackOff
   155  	}
   156  	m.podPullingTimeRecorder.RecordImageStartedPulling(pod.UID)
   157  	m.logIt(ref, v1.EventTypeNormal, events.PullingImage, logPrefix, fmt.Sprintf("Pulling image %q", container.Image), klog.Info)
   158  	startTime := time.Now()
   159  	pullChan := make(chan pullResult)
   160  	m.puller.pullImage(ctx, spec, pullSecrets, pullChan, podSandboxConfig)
   161  	imagePullResult := <-pullChan
   162  	if imagePullResult.err != nil {
   163  		m.logIt(ref, v1.EventTypeWarning, events.FailedToPullImage, logPrefix, fmt.Sprintf("Failed to pull image %q: %v", container.Image, imagePullResult.err), klog.Warning)
   164  		m.backOff.Next(backOffKey, m.backOff.Clock.Now())
   165  
   166  		msg, err := evalCRIPullErr(container, imagePullResult.err)
   167  		return "", msg, err
   168  	}
   169  	m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID)
   170  	imagePullDuration := time.Since(startTime).Truncate(time.Millisecond)
   171  	m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting). Image size: %v bytes.",
   172  		container.Image, imagePullResult.pullDuration.Truncate(time.Millisecond), imagePullDuration, imagePullResult.imageSize), klog.Info)
   173  	metrics.ImagePullDuration.WithLabelValues(metrics.GetImageSizeBucket(imagePullResult.imageSize)).Observe(imagePullDuration.Seconds())
   174  	m.backOff.GC()
   175  	return imagePullResult.imageRef, "", nil
   176  }
   177  
   178  func evalCRIPullErr(container *v1.Container, err error) (errMsg string, errRes error) {
   179  	// Error assertions via errors.Is is not supported by gRPC (remote runtime) errors right now.
   180  	// See https://github.com/grpc/grpc-go/issues/3616
   181  	if strings.HasPrefix(err.Error(), crierrors.ErrRegistryUnavailable.Error()) {
   182  		errMsg = fmt.Sprintf(
   183  			"image pull failed for %s because the registry is unavailable%s",
   184  			container.Image,
   185  			// Trim the error name from the message to convert errors like:
   186  			// "RegistryUnavailable: a more detailed explanation" to:
   187  			// "...because the registry is unavailable: a more detailed explanation"
   188  			strings.TrimPrefix(err.Error(), crierrors.ErrRegistryUnavailable.Error()),
   189  		)
   190  		return errMsg, crierrors.ErrRegistryUnavailable
   191  	}
   192  
   193  	if strings.HasPrefix(err.Error(), crierrors.ErrSignatureValidationFailed.Error()) {
   194  		errMsg = fmt.Sprintf(
   195  			"image pull failed for %s because the signature validation failed%s",
   196  			container.Image,
   197  			// Trim the error name from the message to convert errors like:
   198  			// "SignatureValidationFailed: a more detailed explanation" to:
   199  			// "...because the signature validation failed: a more detailed explanation"
   200  			strings.TrimPrefix(err.Error(), crierrors.ErrSignatureValidationFailed.Error()),
   201  		)
   202  		return errMsg, crierrors.ErrSignatureValidationFailed
   203  	}
   204  
   205  	// Fallback for no specific error
   206  	return err.Error(), ErrImagePull
   207  }
   208  
   209  // applyDefaultImageTag parses a docker image string, if it doesn't contain any tag or digest,
   210  // a default tag will be applied.
   211  func applyDefaultImageTag(image string) (string, error) {
   212  	_, tag, digest, err := parsers.ParseImageName(image)
   213  	if err != nil {
   214  		return "", err
   215  	}
   216  	// we just concatenate the image name with the default tag here instead
   217  	if len(digest) == 0 && len(tag) > 0 && !strings.HasSuffix(image, ":"+tag) {
   218  		// we just concatenate the image name with the default tag here instead
   219  		// of using dockerref.WithTag(named, ...) because that would cause the
   220  		// image to be fully qualified as docker.io/$name if it's a short name
   221  		// (e.g. just busybox). We don't want that to happen to keep the CRI
   222  		// agnostic wrt image names and default hostnames.
   223  		image = image + ":" + tag
   224  	}
   225  	return image, nil
   226  }