diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 29e02f8fb37..dd0a72bf8fa 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -813,6 +813,7 @@ func getEtcdVersionResponse(client *http.Client, url string, target interface{}) type ImagePullCheck struct { runtime utilruntime.ContainerRuntime imageList []string + sandboxImage string imagePullPolicy v1.PullPolicy } @@ -826,6 +827,15 @@ func (ipc ImagePullCheck) Check() (warnings, errorList []error) { policy := ipc.imagePullPolicy klog.V(1).Infof("using image pull policy: %s", policy) for _, image := range ipc.imageList { + if image == ipc.sandboxImage { + criSandboxImage, err := ipc.runtime.SandboxImage() + if err != nil { + klog.V(4).Infof("failed to detect the sandbox image for local container runtime, %v", err) + } else if criSandboxImage != ipc.sandboxImage { + klog.Warningf("detected that the sandbox image %q of the container runtime is inconsistent with that used by kubeadm. It is recommended that using %q as the CRI sandbox image.", + criSandboxImage, ipc.sandboxImage) + } + } switch policy { case v1.PullNever: klog.V(1).Infof("skipping pull of image: %s", image) @@ -1036,7 +1046,12 @@ func RunPullImagesCheck(execer utilsexec.Interface, cfg *kubeadmapi.InitConfigur } checks := []Checker{ - ImagePullCheck{runtime: containerRuntime, imageList: images.GetControlPlaneImages(&cfg.ClusterConfiguration), imagePullPolicy: cfg.NodeRegistration.ImagePullPolicy}, + ImagePullCheck{ + runtime: containerRuntime, + imageList: images.GetControlPlaneImages(&cfg.ClusterConfiguration), + sandboxImage: images.GetPauseImage(&cfg.ClusterConfiguration), + imagePullPolicy: cfg.NodeRegistration.ImagePullPolicy, + }, } return RunChecks(checks, os.Stderr, ignorePreflightErrors) } diff --git a/cmd/kubeadm/app/util/runtime/runtime.go b/cmd/kubeadm/app/util/runtime/runtime.go index 5610b4bed87..96cff450110 100644 --- a/cmd/kubeadm/app/util/runtime/runtime.go +++ b/cmd/kubeadm/app/util/runtime/runtime.go @@ -44,6 +44,7 @@ type ContainerRuntime interface { RemoveContainers(containers []string) error PullImage(image string) error ImageExists(image string) (bool, error) + SandboxImage() (string, error) } // CRIRuntime is a struct that interfaces with the CRI @@ -173,3 +174,19 @@ func detectCRISocketImpl(isSocket func(string) bool, knownCRISockets []string) ( func DetectCRISocket() (string, error) { return detectCRISocketImpl(isExistingSocket, defaultKnownCRISockets) } + +// SandboxImage returns the sandbox image used by the container runtime +func (runtime *CRIRuntime) SandboxImage() (string, error) { + args := []string{"-D=false", "info", "-o", "go-template", "--template", "{{.config.sandboxImage}}"} + out, err := runtime.crictl(args...).CombinedOutput() + if err != nil { + return "", errors.Wrapf(err, "output: %s, error", string(out)) + } + + sandboxImage := strings.TrimSpace(string(out)) + if len(sandboxImage) > 0 { + return sandboxImage, nil + } + + return "", errors.Errorf("the detected sandbox image is empty") +} diff --git a/cmd/kubeadm/app/util/runtime/runtime_test.go b/cmd/kubeadm/app/util/runtime/runtime_test.go index 46930fcd6dd..fc2fcb48ce3 100644 --- a/cmd/kubeadm/app/util/runtime/runtime_test.go +++ b/cmd/kubeadm/app/util/runtime/runtime_test.go @@ -161,6 +161,56 @@ func TestListKubeContainers(t *testing.T) { } } +func TestSandboxImage(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeAction{ + func() ([]byte, []byte, error) { return []byte("registry.k8s.io/pause:3.9"), nil, nil }, + func() ([]byte, []byte, error) { return []byte("registry.k8s.io/pause:3.9\n"), nil, nil }, + func() ([]byte, []byte, error) { return nil, nil, nil }, + func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} }, + }, + } + + execer := &fakeexec.FakeExec{ + CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)), + LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil }, + } + + cases := []struct { + name string + expected string + isError bool + }{ + {"valid: read sandbox image normally", "registry.k8s.io/pause:3.9", false}, + {"valid: read sandbox image with leading/trailing white spaces", "registry.k8s.io/pause:3.9", false}, + {"invalid: read empty sandbox image", "", true}, + {"invalid: failed to read sandbox image", "", true}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + runtime, err := NewContainerRuntime(execer, "unix:///some/socket.sock") + if err != nil { + t.Fatalf("unexpected NewContainerRuntime error: %v", err) + } + + sandboxImage, err := runtime.SandboxImage() + if tc.isError { + if err == nil { + t.Errorf("unexpected SandboxImage success") + } + return + } else if err != nil { + t.Errorf("unexpected SandboxImage error: %v", err) + } + + if sandboxImage != tc.expected { + t.Errorf("expected sandbox image %v, but got %v", tc.expected, sandboxImage) + } + }) + } +} + func TestRemoveContainers(t *testing.T) { fakeOK := func() ([]byte, []byte, error) { return nil, nil, nil } fakeErr := func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} }