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 }