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