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 }