...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/compute.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  	versionutil "k8s.io/apimachinery/pkg/util/version"
    24  	clientset "k8s.io/client-go/kubernetes"
    25  	"k8s.io/klog/v2"
    26  
    27  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    28  	"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
    29  	"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
    30  )
    31  
    32  // Upgrade defines an upgrade possibility to upgrade from a current version to a new one
    33  type Upgrade struct {
    34  	Description string
    35  	Before      ClusterState
    36  	After       ClusterState
    37  }
    38  
    39  // CanUpgradeKubelets returns whether an upgrade of any kubelet in the cluster is possible
    40  func (u *Upgrade) CanUpgradeKubelets() bool {
    41  	// If there are multiple different versions now, an upgrade is possible (even if only for a subset of the nodes)
    42  	if len(u.Before.KubeletVersions) > 1 {
    43  		return true
    44  	}
    45  	// Don't report something available for upgrade if we don't know the current state
    46  	if len(u.Before.KubeletVersions) == 0 {
    47  		return false
    48  	}
    49  
    50  	// if the same version number existed both before and after, we don't have to upgrade it
    51  	_, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion]
    52  	return !sameVersionFound
    53  }
    54  
    55  // ClusterState describes the state of certain versions for a cluster during an upgrade
    56  type ClusterState struct {
    57  	// KubeVersion describes the version of latest Kubernetes API Server in the cluster.
    58  	KubeVersion string
    59  	// DNSVersion describes the version of the DNS add-on.
    60  	DNSVersion string
    61  	// KubeadmVersion describes the version of the kubeadm CLI
    62  	KubeadmVersion string
    63  	// EtcdVersion represents the version of etcd used in the cluster
    64  	EtcdVersion string
    65  
    66  	// The following maps describe the versions of the different components in the cluster.
    67  	// The key is the version string and the value is a list of nodes that have that version.
    68  	KubeAPIServerVersions         map[string][]string
    69  	KubeControllerManagerVersions map[string][]string
    70  	KubeSchedulerVersions         map[string][]string
    71  	EtcdVersions                  map[string][]string
    72  	KubeletVersions               map[string][]string
    73  }
    74  
    75  // GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
    76  // kinds of upgrades can be performed
    77  func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed bool, client clientset.Interface, printer output.Printer) ([]Upgrade, error) {
    78  	printer.Printf("[upgrade] Fetching available versions to upgrade to\n")
    79  
    80  	// Collect the upgrades kubeadm can do in this list
    81  	var upgrades []Upgrade
    82  
    83  	// Get the kube-apiserver versions in the cluster
    84  	kubeAPIServerVersions, err := versionGetterImpl.ComponentVersions(kubeadmconstants.KubeAPIServer)
    85  	if err != nil {
    86  		return upgrades, err
    87  	}
    88  	if len(kubeAPIServerVersions) > 1 {
    89  		verMsg := []string{}
    90  		for version, nodes := range kubeAPIServerVersions {
    91  			verMsg = append(verMsg, fmt.Sprintf("%s on nodes %v", version, nodes))
    92  		}
    93  		klog.Warningf("Different API server versions in the cluster were discovered: %v. Please upgrade your control plane"+
    94  			" nodes to the same version of Kubernetes", strings.Join(verMsg, ", "))
    95  	}
    96  
    97  	// Get the lastest cluster version
    98  	clusterVersion, err := getLatestClusterVersion(kubeAPIServerVersions)
    99  	if err != nil {
   100  		return upgrades, err
   101  	}
   102  	clusterVersionStr := clusterVersion.String()
   103  
   104  	printer.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr)
   105  
   106  	// Get current kubeadm CLI version
   107  	kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion()
   108  	if err != nil {
   109  		return upgrades, err
   110  	}
   111  	printer.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr)
   112  
   113  	// Get and output the current latest stable version
   114  	stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version")
   115  	if err != nil {
   116  		klog.Warningf("[upgrade/versions] WARNING: %v\n", err)
   117  		klog.Warningf("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version")
   118  		stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion
   119  	} else {
   120  		printer.Printf("[upgrade/versions] Target version: %s\n", stableVersionStr)
   121  	}
   122  
   123  	// Get the kubelet versions in the cluster
   124  	kubeletVersions, err := versionGetterImpl.KubeletVersions()
   125  	if err != nil {
   126  		return upgrades, err
   127  	}
   128  
   129  	// Get the kube-controller-manager versions in the cluster
   130  	kubeControllerManagerVersions, err := versionGetterImpl.ComponentVersions(kubeadmconstants.KubeControllerManager)
   131  	if err != nil {
   132  		return upgrades, err
   133  	}
   134  
   135  	// Get the kube-scheduler versions in the cluster
   136  	kubeSchedulerVersions, err := versionGetterImpl.ComponentVersions(kubeadmconstants.KubeScheduler)
   137  	if err != nil {
   138  		return upgrades, err
   139  	}
   140  
   141  	// Get the etcd versions in the cluster
   142  	etcdVersions, err := versionGetterImpl.ComponentVersions(kubeadmconstants.Etcd)
   143  	if err != nil {
   144  		return upgrades, err
   145  	}
   146  	isExternalEtcd := len(etcdVersions) == 0
   147  
   148  	dnsVersion, err := dns.DeployedDNSAddon(client)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	// Construct a descriptor for the current state of the world
   154  	beforeState := ClusterState{
   155  		KubeVersion:                   clusterVersionStr,
   156  		DNSVersion:                    dnsVersion,
   157  		KubeadmVersion:                kubeadmVersionStr,
   158  		KubeAPIServerVersions:         kubeAPIServerVersions,
   159  		KubeControllerManagerVersions: kubeControllerManagerVersions,
   160  		KubeSchedulerVersions:         kubeSchedulerVersions,
   161  		KubeletVersions:               kubeletVersions,
   162  		EtcdVersions:                  etcdVersions,
   163  	}
   164  
   165  	// Do a "dumb guess" that a new minor upgrade is available just because the latest stable version is higher than the cluster version
   166  	// This guess will be corrected once we know if there is a patch version available
   167  	canDoMinorUpgrade := clusterVersion.LessThan(stableVersion)
   168  
   169  	// A patch version doesn't exist if the cluster version is higher than or equal to the current stable version
   170  	// in the case that a user is trying to upgrade from, let's say, v1.8.0-beta.2 to v1.8.0-rc.1 (given we support such upgrades experimentally)
   171  	// a stable-1.8 branch doesn't exist yet. Hence this check.
   172  	if patchVersionBranchExists(clusterVersion, stableVersion) {
   173  		currentBranch := getBranchFromVersion(clusterVersionStr)
   174  		versionLabel := fmt.Sprintf("stable-%s", currentBranch)
   175  		description := fmt.Sprintf("version in the v%s series", currentBranch)
   176  
   177  		// Get and output the latest patch version for the cluster branch
   178  		patchVersionStr, patchVersion, err := versionGetterImpl.VersionFromCILabel(versionLabel, description)
   179  		if err != nil {
   180  			klog.Warningf("[upgrade/versions] WARNING: %v\n", err)
   181  		} else {
   182  			printer.Printf("[upgrade/versions] Latest %s: %s\n", description, patchVersionStr)
   183  
   184  			// Check if a minor version upgrade is possible when a patch release exists
   185  			// It's only possible if the latest patch version is higher than the current patch version
   186  			// If that's the case, they must be on different branches => a newer minor version can be upgraded to
   187  			canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion)
   188  
   189  			// If the cluster version is lower than the newest patch version, we should inform about the possible upgrade
   190  			if patchUpgradePossible(clusterVersion, patchVersion) {
   191  
   192  				// The kubeadm version has to be upgraded to the latest patch version
   193  				newKubeadmVer := patchVersionStr
   194  				if kubeadmVersion.AtLeast(patchVersion) {
   195  					// In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion
   196  					newKubeadmVer = kubeadmVersionStr
   197  				}
   198  
   199  				upgrades = append(upgrades, Upgrade{
   200  					Description: description,
   201  					Before:      beforeState,
   202  					After: ClusterState{
   203  						KubeVersion:    patchVersionStr,
   204  						DNSVersion:     kubeadmconstants.CoreDNSVersion,
   205  						KubeadmVersion: newKubeadmVer,
   206  						EtcdVersion:    getSuggestedEtcdVersion(isExternalEtcd, patchVersionStr),
   207  					},
   208  				})
   209  			}
   210  		}
   211  	}
   212  
   213  	if canDoMinorUpgrade {
   214  		upgrades = append(upgrades, Upgrade{
   215  			Description: "stable version",
   216  			Before:      beforeState,
   217  			After: ClusterState{
   218  				KubeVersion:    stableVersionStr,
   219  				DNSVersion:     kubeadmconstants.CoreDNSVersion,
   220  				KubeadmVersion: stableVersionStr,
   221  				EtcdVersion:    getSuggestedEtcdVersion(isExternalEtcd, stableVersionStr),
   222  			},
   223  		})
   224  	}
   225  
   226  	if experimentalUpgradesAllowed || rcUpgradesAllowed {
   227  		// dl.k8s.io/release/latest.txt is ALWAYS an alpha.X version
   228  		// dl.k8s.io/release/latest-1.X.txt is first v1.X.0-alpha.0 -> v1.X.0-alpha.Y, then v1.X.0-beta.0 to v1.X.0-beta.Z, then v1.X.0-rc.1 to v1.X.0-rc.W.
   229  		// After the v1.X.0 release, latest-1.X.txt is always a beta.0 version. Let's say the latest stable version on the v1.7 branch is v1.7.3, then the
   230  		// latest-1.7 version is v1.7.4-beta.0
   231  
   232  		// Worth noticing is that when the release-1.X branch is cut; there are two versions tagged: v1.X.0-beta.0 AND v1.(X+1).alpha.0
   233  		// The v1.(X+1).alpha.0 is pretty much useless and should just be ignored, as more betas may be released that have more features than the initial v1.(X+1).alpha.0
   234  
   235  		// So what we do below is getting the latest overall version, always an v1.X.0-alpha.Y version. Then we get latest-1.(X-1) version. This version may be anything
   236  		// between v1.(X-1).0-beta.0 and v1.(X-1).Z-beta.0. At some point in time, latest-1.(X-1) will point to v1.(X-1).0-rc.1. Then we should show it.
   237  
   238  		// The flow looks like this (with time on the X axis):
   239  		// v1.8.0-alpha.1 -> v1.8.0-alpha.2 -> v1.8.0-alpha.3 | release-1.8 branch | v1.8.0-beta.0 -> v1.8.0-beta.1 -> v1.8.0-beta.2 -> v1.8.0-rc.1 -> v1.8.0 -> v1.8.1
   240  		//                                                                           v1.9.0-alpha.0                                             -> v1.9.0-alpha.1 -> v1.9.0-alpha.2
   241  
   242  		// Get and output the current latest unstable version
   243  		latestVersionStr, latestVersion, err := versionGetterImpl.VersionFromCILabel("latest", "experimental version")
   244  		if err != nil {
   245  			return upgrades, err
   246  		}
   247  		_, _ = printer.Printf("[upgrade/versions] Latest %s: %s\n", "experimental version", latestVersionStr)
   248  
   249  		minorUnstable := latestVersion.Components()[1]
   250  		// Get and output the current latest unstable version
   251  		previousBranch := fmt.Sprintf("latest-1.%d", minorUnstable-1)
   252  		previousBranchLatestVersionStr, previousBranchLatestVersion, err := versionGetterImpl.VersionFromCILabel(previousBranch, "previous version")
   253  		if err != nil {
   254  			return upgrades, err
   255  		}
   256  		_, _ = printer.Printf("[upgrade/versions] Latest %s: %s\n", "previous version", previousBranchLatestVersionStr)
   257  
   258  		// If that previous latest version is an RC, RCs are allowed and the cluster version is lower than the RC version, show the upgrade
   259  		if rcUpgradesAllowed && rcUpgradePossible(clusterVersion, previousBranchLatestVersion) {
   260  			upgrades = append(upgrades, Upgrade{
   261  				Description: "release candidate version",
   262  				Before:      beforeState,
   263  				After: ClusterState{
   264  					KubeVersion:    previousBranchLatestVersionStr,
   265  					DNSVersion:     kubeadmconstants.CoreDNSVersion,
   266  					KubeadmVersion: previousBranchLatestVersionStr,
   267  					EtcdVersion:    getSuggestedEtcdVersion(isExternalEtcd, previousBranchLatestVersionStr),
   268  				},
   269  			})
   270  		}
   271  
   272  		// Show the possibility if experimental upgrades are allowed
   273  		if experimentalUpgradesAllowed && clusterVersion.LessThan(latestVersion) {
   274  
   275  			// Default to assume that the experimental version to show is the unstable one
   276  			unstableKubeVersion := latestVersionStr
   277  
   278  			// Ẃe should not display alpha.0. The previous branch's beta/rc versions are more relevant due how the kube branching process works.
   279  			if latestVersion.PreRelease() == "alpha.0" {
   280  				unstableKubeVersion = previousBranchLatestVersionStr
   281  			}
   282  
   283  			upgrades = append(upgrades, Upgrade{
   284  				Description: "experimental version",
   285  				Before:      beforeState,
   286  				After: ClusterState{
   287  					KubeVersion:    unstableKubeVersion,
   288  					DNSVersion:     kubeadmconstants.CoreDNSVersion,
   289  					KubeadmVersion: unstableKubeVersion,
   290  					EtcdVersion:    getSuggestedEtcdVersion(isExternalEtcd, unstableKubeVersion),
   291  				},
   292  			})
   293  		}
   294  	}
   295  
   296  	// Add a newline in the end of this output to leave some space to the next output section
   297  	printer.Println()
   298  
   299  	return upgrades, nil
   300  }
   301  
   302  func getBranchFromVersion(version string) string {
   303  	v := versionutil.MustParseGeneric(version)
   304  	return fmt.Sprintf("%d.%d", v.Major(), v.Minor())
   305  }
   306  
   307  func patchVersionBranchExists(clusterVersion, stableVersion *versionutil.Version) bool {
   308  	return stableVersion.AtLeast(clusterVersion)
   309  }
   310  
   311  func patchUpgradePossible(clusterVersion, patchVersion *versionutil.Version) bool {
   312  	return clusterVersion.LessThan(patchVersion)
   313  }
   314  
   315  func rcUpgradePossible(clusterVersion, previousBranchLatestVersion *versionutil.Version) bool {
   316  	return strings.HasPrefix(previousBranchLatestVersion.PreRelease(), "rc") && clusterVersion.LessThan(previousBranchLatestVersion)
   317  }
   318  
   319  func minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion *versionutil.Version) bool {
   320  	return patchVersion.LessThan(stableVersion)
   321  }
   322  
   323  func getSuggestedEtcdVersion(isExternalEtcd bool, kubernetesVersion string) string {
   324  	if isExternalEtcd {
   325  		return ""
   326  	}
   327  	etcdVersion, warning, err := kubeadmconstants.EtcdSupportedVersion(kubeadmconstants.SupportedEtcdVersion, kubernetesVersion)
   328  	if err != nil {
   329  		klog.Warningf("[upgrade/versions] could not retrieve an etcd version for the target Kubernetes version: %v", err)
   330  		return "N/A"
   331  	}
   332  	if warning != nil {
   333  		klog.V(1).Infof("[upgrade/versions] WARNING: %v", warning)
   334  	}
   335  	return etcdVersion.String()
   336  }
   337  
   338  func getLatestClusterVersion(kubeAPIServerVersions map[string][]string) (*versionutil.Version, error) {
   339  	var latestVersion *versionutil.Version
   340  	for versionStr, nodes := range kubeAPIServerVersions {
   341  		ver, err := versionutil.ParseSemantic(versionStr)
   342  		if err != nil {
   343  			return nil, fmt.Errorf("couldn't parse kube-apiserver version %s from nodes %v", versionStr, nodes)
   344  		}
   345  		if latestVersion == nil || ver.AtLeast(latestVersion) {
   346  			latestVersion = ver
   347  		}
   348  	}
   349  
   350  	return latestVersion, nil
   351  }
   352  

View as plain text