diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 6afd5b31b54..1894c6fcf1b 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -498,7 +498,9 @@ func (kubever KubernetesVersionCheck) Check() (warnings, errors []error) { } // KubeletVersionCheck validates installed kubelet version -type KubeletVersionCheck struct{} +type KubeletVersionCheck struct { + KubernetesVersion string +} // Check validates kubelet version. It should be not less than minimal supported version func (kubever KubeletVersionCheck) Check() (warnings, errors []error) { @@ -509,7 +511,17 @@ func (kubever KubeletVersionCheck) Check() (warnings, errors []error) { if kubeletVersion.LessThan(kubeadmconstants.MinimumKubeletVersion) { return nil, []error{fmt.Errorf("Kubelet version %q is lower than kubadm can support. Please upgrade kubelet", kubeletVersion)} } - return nil, []error{} + + if kubever.KubernetesVersion != "" { + k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion) + if err != nil { + return nil, []error{fmt.Errorf("couldn't parse kubernetes version %q: %v", kubever.KubernetesVersion, err)} + } + if kubeletVersion.Major() > k8sVersion.Major() || kubeletVersion.Minor() > k8sVersion.Minor() { + return nil, []error{fmt.Errorf("the kubelet version is higher than the control plane version. This is not a supported version skew and may lead to a malfunctional cluster. Kubelet version: %q Control plane version: %q", kubeletVersion, k8sVersion)} + } + } + return nil, nil } // SwapCheck warns if swap is enabled @@ -688,7 +700,7 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error { SystemVerificationCheck{}, IsPrivilegedUserCheck{}, HostnameCheck{nodeName: cfg.NodeName}, - KubeletVersionCheck{}, + KubeletVersionCheck{KubernetesVersion: cfg.KubernetesVersion}, ServiceCheck{Service: "kubelet", CheckIfActive: false}, ServiceCheck{Service: "docker", CheckIfActive: true}, FirewalldCheck{ports: []int{int(cfg.API.BindPort), 10250}}, diff --git a/cmd/kubeadm/app/preflight/checks_test.go b/cmd/kubeadm/app/preflight/checks_test.go index caec19de82a..52cdc138b4e 100644 --- a/cmd/kubeadm/app/preflight/checks_test.go +++ b/cmd/kubeadm/app/preflight/checks_test.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "io/ioutil" + "path/filepath" "strings" "testing" @@ -556,3 +557,59 @@ func restoreEnv(e map[string]string) { os.Setenv(k, v) } } + +func TestKubeletVersionCheck(t *testing.T) { + type T struct { + kubeletVersion string + k8sVersion string + expectErrors bool + expectWarnings bool + } + + cases := []T{ + {"v1.10.2", "", false, false}, // check minimally supported version when there is no information about control plane + {"v1.7.3", "v1.7.8", true, false}, // too old kubelet (older than kubeadmconstants.MinimumKubeletVersion), should fail. + {"v1.9.0", "v1.9.5", false, false}, // kubelet within same major.minor as control plane + {"v1.9.5", "v1.9.1", false, false}, // kubelet is newer, but still within same major.minor as control plane + {"v1.9.0", "v1.10.1", false, false}, // kubelet is lower than control plane, but newer than minimally supported + {"v1.10.0-alpha.1", "v1.9.1", true, false}, // kubelet is newer (development build) than control plane, should fail. + {"v1.10.0", "v1.9.5", true, false}, // kubelet is newer (release) than control plane, should fail. + } + + dir, err := ioutil.TempDir("", "test-kubelet-version-check") + if err != nil { + t.Errorf("Failed to create directory for testing GetKubeletVersion: %v", err) + } + defer os.RemoveAll(dir) + + // We don't want to call real kubelet or something else in $PATH + oldPATH := os.Getenv("PATH") + defer os.Setenv("PATH", oldPATH) + + os.Setenv("PATH", dir) + + kubeletFn := filepath.Join(dir, "kubelet") + for _, tc := range cases { + + content := []byte(fmt.Sprintf("#!/bin/sh\necho 'Kubernetes %s'", tc.kubeletVersion)) + if err := ioutil.WriteFile(kubeletFn, content, 0755); err != nil { + t.Errorf("Error creating test stub file %s: %v", kubeletFn, err) + } + + check := KubeletVersionCheck{KubernetesVersion: tc.k8sVersion} + warnings, errors := check.Check() + + switch { + case warnings != nil && !tc.expectWarnings: + t.Errorf("KubeletVersionCheck: unexpected warnings for kubelet version %q and kubernetes version %q. Warnings: %v", tc.kubeletVersion, tc.k8sVersion, warnings) + case warnings == nil && tc.expectWarnings: + t.Errorf("KubeletVersionCheck: expected warnings for kubelet version %q and kubernetes version %q but got nothing", tc.kubeletVersion, tc.k8sVersion) + case errors != nil && !tc.expectErrors: + t.Errorf("KubeletVersionCheck: unexpected errors for kubelet version %q and kubernetes version %q. errors: %v", tc.kubeletVersion, tc.k8sVersion, errors) + case errors == nil && tc.expectErrors: + t.Errorf("KubeletVersionCheck: expected errors for kubelet version %q and kubernetes version %q but got nothing", tc.kubeletVersion, tc.k8sVersion) + } + + } + +}