...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/policy.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade

     1  /*
     2  Copyright 2017 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 upgrade
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/pkg/errors"
    24  
    25  	"k8s.io/apimachinery/pkg/util/version"
    26  
    27  	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
    28  )
    29  
    30  const (
    31  	// MaximumAllowedMinorVersionUpgradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go
    32  	MaximumAllowedMinorVersionUpgradeSkew = 1
    33  
    34  	// MaximumAllowedMinorVersionDowngradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go
    35  	MaximumAllowedMinorVersionDowngradeSkew = 1
    36  
    37  	// MaximumAllowedMinorVersionKubeletSkew describes how many minor versions the control plane version and the kubelet can skew in a kubeadm cluster
    38  	MaximumAllowedMinorVersionKubeletSkew = 3
    39  )
    40  
    41  // VersionSkewPolicyErrors describes version skew errors that might be seen during the validation process in EnforceVersionPolicies
    42  type VersionSkewPolicyErrors struct {
    43  	Mandatory []error
    44  	Skippable []error
    45  }
    46  
    47  // EnforceVersionPolicies enforces that the proposed new version is compatible with all the different version skew policies
    48  func EnforceVersionPolicies(versionGetter VersionGetter, newK8sVersionStr string, newK8sVersion *version.Version, allowExperimentalUpgrades, allowRCUpgrades bool) *VersionSkewPolicyErrors {
    49  
    50  	skewErrors := &VersionSkewPolicyErrors{
    51  		Mandatory: []error{},
    52  		Skippable: []error{},
    53  	}
    54  
    55  	clusterVersionStr, clusterVersion, err := versionGetter.ClusterVersion()
    56  	if err != nil {
    57  		// This case can't be forced: kubeadm has to be able to lookup cluster version for upgrades to work
    58  		skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Wrap(err, "Unable to fetch cluster version"))
    59  		return skewErrors
    60  	}
    61  	fmt.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr)
    62  
    63  	kubeadmVersionStr, kubeadmVersion, err := versionGetter.KubeadmVersion()
    64  	if err != nil {
    65  		// This case can't be forced: kubeadm has to be able to lookup its version for upgrades to work
    66  		skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Wrap(err, "Unable to fetch kubeadm version"))
    67  		return skewErrors
    68  	}
    69  	fmt.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr)
    70  
    71  	kubeletVersions, err := versionGetter.KubeletVersions()
    72  	if err != nil {
    73  		// This is a non-critical error; continue although kubeadm couldn't look this up
    74  		skewErrors.Skippable = append(skewErrors.Skippable, errors.Wrap(err, "Unable to fetch kubelet version"))
    75  	}
    76  
    77  	// Make sure the new version is a supported version (higher than the minimum one supported)
    78  	if !newK8sVersion.AtLeast(constants.MinimumControlPlaneVersion) {
    79  		// This must not happen, kubeadm always supports a minimum version; and we can't go below that
    80  		skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Errorf("Specified version to upgrade to %q is equal to or lower than the minimum supported version %q. Please specify a higher version to upgrade to", newK8sVersionStr, clusterVersionStr))
    81  	}
    82  
    83  	// kubeadm doesn't support upgrades between two minor versions; e.g. a v1.7 -> v1.9 upgrade is not supported right away
    84  	if newK8sVersion.Minor() > clusterVersion.Minor()+MaximumAllowedMinorVersionUpgradeSkew {
    85  		tooLargeUpgradeSkewErr := errors.Errorf("Specified version to upgrade to %q is too high; kubeadm can upgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionUpgradeSkew)
    86  		// If the version that we're about to upgrade to is a released version, we should fully enforce this policy
    87  		// If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag
    88  		if len(newK8sVersion.PreRelease()) == 0 {
    89  			skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeUpgradeSkewErr)
    90  		} else {
    91  			skewErrors.Skippable = append(skewErrors.Skippable, tooLargeUpgradeSkewErr)
    92  		}
    93  	}
    94  
    95  	// kubeadm doesn't support downgrades between two minor versions; e.g. a v1.9 -> v1.7 downgrade is not supported right away
    96  	if newK8sVersion.Minor() < clusterVersion.Minor()-MaximumAllowedMinorVersionDowngradeSkew {
    97  		tooLargeDowngradeSkewErr := errors.Errorf("Specified version to downgrade to %q is too low; kubeadm can downgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionDowngradeSkew)
    98  		// If the version that we're about to downgrade to is a released version, we should fully enforce this policy
    99  		// If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag
   100  		if len(newK8sVersion.PreRelease()) == 0 {
   101  			skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeDowngradeSkewErr)
   102  		} else {
   103  			skewErrors.Skippable = append(skewErrors.Skippable, tooLargeDowngradeSkewErr)
   104  		}
   105  	}
   106  
   107  	// If the kubeadm version is lower than what we want to upgrade to; error
   108  	if kubeadmVersion.LessThan(newK8sVersion) {
   109  		if newK8sVersion.Minor() > kubeadmVersion.Minor() {
   110  			tooLargeKubeadmSkew := errors.Errorf("Specified version to upgrade to %q is at least one minor release higher than the kubeadm minor release (%d > %d). Such an upgrade is not supported", newK8sVersionStr, newK8sVersion.Minor(), kubeadmVersion.Minor())
   111  			// This is unsupported; kubeadm has no idea how it should handle a newer minor release than itself
   112  			// If the version is a CI/dev/experimental version though, lower the severity of this check, but then require the -f flag
   113  			if len(newK8sVersion.PreRelease()) == 0 {
   114  				skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeKubeadmSkew)
   115  			} else {
   116  				skewErrors.Skippable = append(skewErrors.Skippable, tooLargeKubeadmSkew)
   117  			}
   118  		} else {
   119  			// Upgrading to a higher patch version than kubeadm is ok if the user specifies --force. Not recommended, but possible.
   120  			skewErrors.Skippable = append(skewErrors.Skippable, errors.Errorf("Specified version to upgrade to %q is higher than the kubeadm version %q. Upgrade kubeadm first using the tool you used to install kubeadm", newK8sVersionStr, kubeadmVersionStr))
   121  		}
   122  	}
   123  
   124  	if kubeadmVersion.Major() > newK8sVersion.Major() ||
   125  		kubeadmVersion.Minor() > newK8sVersion.Minor() {
   126  		skewErrors.Skippable = append(skewErrors.Skippable, errors.Errorf("Kubeadm version %s can only be used to upgrade to Kubernetes version %d.%d", kubeadmVersionStr, kubeadmVersion.Major(), kubeadmVersion.Minor()))
   127  	}
   128  
   129  	// Detect if the version is unstable and the user didn't allow that
   130  	if err = detectUnstableVersionError(newK8sVersion, newK8sVersionStr, allowExperimentalUpgrades, allowRCUpgrades); err != nil {
   131  		skewErrors.Skippable = append(skewErrors.Skippable, err)
   132  	}
   133  
   134  	// Detect if there are too old kubelets in the cluster
   135  	// Check for nil here since this is the only case where kubeletVersions can be nil; if KubeletVersions() returned an error
   136  	// However, it's okay to skip that check
   137  	if kubeletVersions != nil {
   138  		if err = detectTooOldKubelets(newK8sVersion, kubeletVersions); err != nil {
   139  			skewErrors.Skippable = append(skewErrors.Skippable, err)
   140  		}
   141  	}
   142  
   143  	// If we did not see any errors, return nil
   144  	if len(skewErrors.Skippable) == 0 && len(skewErrors.Mandatory) == 0 {
   145  		return nil
   146  	}
   147  
   148  	// Uh oh, we encountered one or more errors, return them
   149  	return skewErrors
   150  }
   151  
   152  // detectUnstableVersionError is a helper function for detecting if the unstable version (if specified) is allowed to be used
   153  func detectUnstableVersionError(newK8sVersion *version.Version, newK8sVersionStr string, allowExperimentalUpgrades, allowRCUpgrades bool) error {
   154  	// Short-circuit quickly if this is not an unstable version
   155  	if len(newK8sVersion.PreRelease()) == 0 {
   156  		return nil
   157  	}
   158  	// If the user has specified that unstable versions are fine, then no error should be returned
   159  	if allowExperimentalUpgrades {
   160  		return nil
   161  	}
   162  	// If this is a release candidate and we allow such ones, everything's fine
   163  	if strings.HasPrefix(newK8sVersion.PreRelease(), "rc") && allowRCUpgrades {
   164  		return nil
   165  	}
   166  
   167  	return errors.Errorf("Specified version to upgrade to %q is an unstable version and such upgrades weren't allowed via setting the --allow-*-upgrades flags", newK8sVersionStr)
   168  }
   169  
   170  // detectTooOldKubelets errors out if the kubelet versions are so old that an unsupported skew would happen if the cluster was upgraded
   171  func detectTooOldKubelets(newK8sVersion *version.Version, kubeletVersions map[string][]string) error {
   172  	var tooOldKubeletVersions []string
   173  	for versionStr := range kubeletVersions {
   174  
   175  		kubeletVersion, err := version.ParseSemantic(versionStr)
   176  		if err != nil {
   177  			return errors.Errorf("couldn't parse kubelet version %s", versionStr)
   178  		}
   179  
   180  		if newK8sVersion.Minor() > kubeletVersion.Minor()+MaximumAllowedMinorVersionKubeletSkew {
   181  			tooOldKubeletVersions = append(tooOldKubeletVersions, versionStr)
   182  		}
   183  	}
   184  	if len(tooOldKubeletVersions) == 0 {
   185  		return nil
   186  	}
   187  
   188  	return errors.Errorf("There are kubelets in this cluster that are too old that have these versions %v", tooOldKubeletVersions)
   189  }
   190  

View as plain text