From 521c84aa89d8ce05a037f8ab2dba07cd6ec27077 Mon Sep 17 00:00:00 2001 From: Alexander Kanevskiy Date: Thu, 21 Sep 2017 21:00:48 +0300 Subject: [PATCH] Detect major version mismatches between kubeadm and kubelet. Kubeadm supports only one minor release back, thus for 1.9 it requires minimum kubelet from 1.8. Fixes: kubernetes/kubeadm#430 --- cmd/kubeadm/app/cmd/join.go | 6 +- cmd/kubeadm/app/constants/constants.go | 3 + cmd/kubeadm/app/preflight/BUILD | 10 +++- cmd/kubeadm/app/preflight/checks.go | 17 ++++++ cmd/kubeadm/app/preflight/utils.go | 43 ++++++++++++++ cmd/kubeadm/app/preflight/utils_test.go | 78 +++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 cmd/kubeadm/app/preflight/utils.go create mode 100644 cmd/kubeadm/app/preflight/utils_test.go diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 63d11569b67..a7354f0c120 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -20,9 +20,7 @@ import ( "fmt" "io" "io/ioutil" - "os/exec" "path/filepath" - "strings" "github.com/renstrom/dedent" "github.com/spf13/cobra" @@ -220,11 +218,11 @@ func (j *Join) Run(out io.Writer) error { kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName) // Depending on the kubelet version, we might perform the TLS bootstrap or not - kubeletVersionBytes, err := exec.Command("sh", "-c", "kubelet --version").Output() + kubeletVersion, err := preflight.GetKubeletVersion() // In case the command executed successfully and returned v1.7-something, we'll perform TLS Bootstrapping // Otherwise, just assume v1.8 // TODO: In the beginning of the v1.9 cycle, we can remove the logic as we then don't support v1.7 anymore - if err == nil && strings.HasPrefix(string(kubeletVersionBytes), "Kubernetes v1.7") { + if err == nil && kubeletVersion.Major() == 1 && kubeletVersion.Minor() == 7 { hostname := nodeutil.GetHostname(j.cfg.NodeName) if err := kubeadmnode.PerformTLSBootstrap(cfg, hostname); err != nil { return err diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index fcb0d33f7a2..483c334a804 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -211,6 +211,9 @@ var ( // UseEnableBootstrapTokenAuthFlagVersion defines the first version where the API server supports the --enable-bootstrap-token-auth flag instead of the old and deprecated flag. // TODO: Remove this when the v1.9 cycle starts and we bump the minimum supported version to v1.8.0 UseEnableBootstrapTokenAuthFlagVersion = version.MustParseSemantic("v1.8.0-beta.0") + + // MinimumKubeletVersion specifies the minimum version of kubelet which kubeadm supports + MinimumKubeletVersion = version.MustParseSemantic("v1.8.0-beta.0") ) // GetStaticPodDirectory returns the location on the disk where the Static Pod should be present diff --git a/cmd/kubeadm/app/preflight/BUILD b/cmd/kubeadm/app/preflight/BUILD index b2ae8681ae0..773a97941b2 100644 --- a/cmd/kubeadm/app/preflight/BUILD +++ b/cmd/kubeadm/app/preflight/BUILD @@ -8,7 +8,10 @@ load( go_library( name = "go_default_library", - srcs = ["checks.go"], + srcs = [ + "checks.go", + "utils.go", + ], deps = [ "//cmd/kube-apiserver/app/options:go_default_library", "//cmd/kube-controller-manager/app/options:go_default_library", @@ -29,7 +32,10 @@ go_library( go_test( name = "go_default_test", - srcs = ["checks_test.go"], + srcs = [ + "checks_test.go", + "utils_test.go", + ], library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index f3effe853bb..02ae6e12d73 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -449,6 +449,21 @@ func (kubever KubernetesVersionCheck) Check() (warnings, errors []error) { return nil, nil } +// KubeletVersionCheck validates installed kubelet version +type KubeletVersionCheck struct{} + +// Check validates kubelet version. It should be not less than minimal supported version +func (kubever KubeletVersionCheck) Check() (warnings, errors []error) { + kubeletVersion, err := GetKubeletVersion() + if err != nil { + return nil, []error{fmt.Errorf("couldn't get kubelet version: %v", err)} + } + 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{} +} + // SwapCheck warns if swap is enabled type SwapCheck struct{} @@ -625,6 +640,7 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error { SystemVerificationCheck{}, IsRootCheck{}, HostnameCheck{nodeName: cfg.NodeName}, + KubeletVersionCheck{}, ServiceCheck{Service: "kubelet", CheckIfActive: false}, ServiceCheck{Service: "docker", CheckIfActive: true}, FirewalldCheck{ports: []int{int(cfg.API.BindPort), 10250}}, @@ -690,6 +706,7 @@ func RunJoinNodeChecks(cfg *kubeadmapi.NodeConfiguration) error { SystemVerificationCheck{}, IsRootCheck{}, HostnameCheck{cfg.NodeName}, + KubeletVersionCheck{}, ServiceCheck{Service: "kubelet", CheckIfActive: false}, ServiceCheck{Service: "docker", CheckIfActive: true}, PortOpenCheck{port: 10250}, diff --git a/cmd/kubeadm/app/preflight/utils.go b/cmd/kubeadm/app/preflight/utils.go new file mode 100644 index 00000000000..d3ed03292a2 --- /dev/null +++ b/cmd/kubeadm/app/preflight/utils.go @@ -0,0 +1,43 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package preflight + +import ( + "fmt" + "os/exec" + "regexp" + "strings" + + "k8s.io/kubernetes/pkg/util/version" +) + +// GetKubeletVersion is helper function that returns version of kubelet available in $PATH +func GetKubeletVersion() (*version.Version, error) { + kubeletVersionRegex := regexp.MustCompile(`^\s*Kubernetes v((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)([-0-9a-zA-Z_\.+]*)?)\s*$`) + + out, err := exec.Command("kubelet", "--version").Output() + if err != nil { + return nil, err + } + + cleanOutput := strings.TrimSpace(string(out)) + subs := kubeletVersionRegex.FindAllStringSubmatch(cleanOutput, -1) + if len(subs) != 1 || len(subs[0]) < 2 { + return nil, fmt.Errorf("Unable to parse output from Kubelet: %q", cleanOutput) + } + return version.ParseSemantic(subs[0][1]) +} diff --git a/cmd/kubeadm/app/preflight/utils_test.go b/cmd/kubeadm/app/preflight/utils_test.go new file mode 100644 index 00000000000..da9a371c532 --- /dev/null +++ b/cmd/kubeadm/app/preflight/utils_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package preflight + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestGetKubeletVersion(t *testing.T) { + type T struct { + output string + expected string + valid bool + } + + cases := []T{ + {"v1.7.0", "1.7.0", true}, + {"v1.8.0-alpha.2.1231+afabd012389d53a", "1.8.0-alpha.2.1231+afabd012389d53a", true}, + {"something-invalid", "", false}, + } + + dir, err := ioutil.TempDir("", "test-kubelet-version") + 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) + + // First test case, kubelet not present, should be getting error + ver, err := GetKubeletVersion() + if err == nil { + t.Errorf("failed GetKubeletVersion: expected failure when kubelet not in PATH. Result: %v", ver) + } + + kubeletFn := filepath.Join(dir, "kubelet") + for _, tc := range cases { + + content := []byte(fmt.Sprintf("#!/bin/sh\necho 'Kubernetes %s'", tc.output)) + if err := ioutil.WriteFile(kubeletFn, content, 0755); err != nil { + t.Errorf("Error creating test stub file %s: %v", kubeletFn, err) + } + + ver, err := GetKubeletVersion() + switch { + case err != nil && tc.valid: + t.Errorf("GetKubeletVersion: unexpected error for %q. Error: %v", tc.output, err) + case err == nil && !tc.valid: + t.Errorf("GetKubeletVersion: error expected for key %q, but result is %q", tc.output, ver) + case ver != nil && ver.String() != tc.expected: + t.Errorf("GetKubeletVersion: unexpected version result for key %q. Expected: %q Actual: %q", tc.output, tc.expected, ver) + } + + } + +}