diff --git a/pkg/kubelet/images/image_manager.go b/pkg/kubelet/images/image_manager.go index 8ec8fb67a52..8910f64097f 100644 --- a/pkg/kubelet/images/image_manager.go +++ b/pkg/kubelet/images/image_manager.go @@ -29,6 +29,7 @@ import ( "k8s.io/klog/v2" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + crierrors "k8s.io/cri-api/pkg/errors" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/events" ) @@ -158,14 +159,8 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, pod *v1.Pod, conta m.logIt(ref, v1.EventTypeWarning, events.FailedToPullImage, logPrefix, fmt.Sprintf("Failed to pull image %q: %v", container.Image, imagePullResult.err), klog.Warning) m.backOff.Next(backOffKey, m.backOff.Clock.Now()) - // Error assertions via errors.Is is not supported by gRPC (remote runtime) errors right now. - // See https://github.com/grpc/grpc-go/issues/3616 - if imagePullResult.err.Error() == ErrRegistryUnavailable.Error() { - msg := fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image) - return "", msg, imagePullResult.err - } - - return "", imagePullResult.err.Error(), ErrImagePull + msg, err := evalCRIPullErr(container, imagePullResult.err) + return "", msg, err } m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID) m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting)", @@ -174,6 +169,23 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, pod *v1.Pod, conta return imagePullResult.imageRef, "", nil } +func evalCRIPullErr(container *v1.Container, err error) (errMsg string, errRes error) { + // Error assertions via errors.Is is not supported by gRPC (remote runtime) errors right now. + // See https://github.com/grpc/grpc-go/issues/3616 + if err.Error() == crierrors.ErrRegistryUnavailable.Error() { + errMsg = fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image) + return errMsg, crierrors.ErrRegistryUnavailable + } + + if err.Error() == crierrors.ErrSignatureValidationFailed.Error() { + errMsg = fmt.Sprintf("image pull failed for %s because the signature validation failed.", container.Image) + return errMsg, crierrors.ErrSignatureValidationFailed + } + + // Fallback for no specific error + return err.Error(), ErrImagePull +} + // applyDefaultImageTag parses a docker image string, if it doesn't contain any tag or digest, // a default tag will be applied. func applyDefaultImageTag(image string) (string, error) { diff --git a/pkg/kubelet/images/image_manager_test.go b/pkg/kubelet/images/image_manager_test.go index 03d83db8e90..0e04c39e907 100644 --- a/pkg/kubelet/images/image_manager_test.go +++ b/pkg/kubelet/images/image_manager_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/flowcontrol" + crierrors "k8s.io/cri-api/pkg/errors" . "k8s.io/kubernetes/pkg/kubelet/container" ctest "k8s.io/kubernetes/pkg/kubelet/container/testing" testingclock "k8s.io/utils/clock/testing" @@ -413,3 +414,46 @@ func TestMaxParallelImagePullsLimit(t *testing.T) { wg.Wait() fakeRuntime.AssertCallCounts("PullImage", 7) } + +func TestEvalCRIPullErr(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + input error + assert func(string, error) + }{ + { + name: "fallback error", + input: errors.New("test"), + assert: func(msg string, err error) { + assert.ErrorIs(t, err, ErrImagePull) + assert.Contains(t, msg, "test") + }, + }, + { + name: "registry is unavailable", + input: crierrors.ErrRegistryUnavailable, + assert: func(msg string, err error) { + assert.ErrorIs(t, err, crierrors.ErrRegistryUnavailable) + assert.Contains(t, msg, "registry is unavailable") + }, + }, + { + name: "signature is invalid", + input: crierrors.ErrSignatureValidationFailed, + assert: func(msg string, err error) { + assert.ErrorIs(t, err, crierrors.ErrSignatureValidationFailed) + assert.Contains(t, msg, "signature validation failed") + }, + }, + } { + testInput := tc.input + testAssert := tc.assert + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + msg, err := evalCRIPullErr(&v1.Container{}, testInput) + testAssert(msg, err) + }) + } +} diff --git a/pkg/kubelet/images/types.go b/pkg/kubelet/images/types.go index 3b0397faad4..52342b28ec1 100644 --- a/pkg/kubelet/images/types.go +++ b/pkg/kubelet/images/types.go @@ -37,9 +37,6 @@ var ( // ErrImageNeverPull - Required Image is absent on host and PullPolicy is NeverPullImage ErrImageNeverPull = errors.New("ErrImageNeverPull") - // ErrRegistryUnavailable - Get http error when pulling image from registry - ErrRegistryUnavailable = errors.New("RegistryUnavailable") - // ErrInvalidImageName - Unable to parse the image name. ErrInvalidImageName = errors.New("InvalidImageName") ) diff --git a/staging/src/k8s.io/cri-api/pkg/errors/errors.go b/staging/src/k8s.io/cri-api/pkg/errors/errors.go index 41d7b92466d..a4538669122 100644 --- a/staging/src/k8s.io/cri-api/pkg/errors/errors.go +++ b/staging/src/k8s.io/cri-api/pkg/errors/errors.go @@ -17,10 +17,20 @@ limitations under the License. package errors import ( + "errors" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) +var ( + // ErrRegistryUnavailable - Get http error on the PullImage RPC call. + ErrRegistryUnavailable = errors.New("RegistryUnavailable") + + // ErrSignatureValidationFailed - Unable to validate the image signature on the PullImage RPC call. + ErrSignatureValidationFailed = errors.New("SignatureValidationFailed") +) + // IsNotFound returns a boolean indicating whether the error // is grpc not found error. // See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md