mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 22:01:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			319 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			319 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| 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 upgrade
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	versionutil "k8s.io/apimachinery/pkg/util/version"
 | |
| 	clientset "k8s.io/client-go/kubernetes"
 | |
| 	"k8s.io/klog/v2"
 | |
| 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
 | |
| 	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
 | |
| 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
 | |
| )
 | |
| 
 | |
| // Upgrade defines an upgrade possibility to upgrade from a current version to a new one
 | |
| type Upgrade struct {
 | |
| 	Description string
 | |
| 	Before      ClusterState
 | |
| 	After       ClusterState
 | |
| }
 | |
| 
 | |
| // CanUpgradeKubelets returns whether an upgrade of any kubelet in the cluster is possible
 | |
| func (u *Upgrade) CanUpgradeKubelets() bool {
 | |
| 	// If there are multiple different versions now, an upgrade is possible (even if only for a subset of the nodes)
 | |
| 	if len(u.Before.KubeletVersions) > 1 {
 | |
| 		return true
 | |
| 	}
 | |
| 	// Don't report something available for upgrade if we don't know the current state
 | |
| 	if len(u.Before.KubeletVersions) == 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// if the same version number existed both before and after, we don't have to upgrade it
 | |
| 	_, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion]
 | |
| 	return !sameVersionFound
 | |
| }
 | |
| 
 | |
| // CanUpgradeEtcd returns whether an upgrade of etcd is possible
 | |
| func (u *Upgrade) CanUpgradeEtcd() bool {
 | |
| 	return u.Before.EtcdVersion != u.After.EtcdVersion
 | |
| }
 | |
| 
 | |
| // ClusterState describes the state of certain versions for a cluster
 | |
| type ClusterState struct {
 | |
| 	// KubeVersion describes the version of the Kubernetes API Server, Controller Manager, Scheduler and Proxy.
 | |
| 	KubeVersion string
 | |
| 	// DNSType describes the type of DNS add-on used in the cluster.
 | |
| 	DNSType kubeadmapi.DNSAddOnType
 | |
| 	// DNSVersion describes the version of the DNS add-on.
 | |
| 	DNSVersion string
 | |
| 	// KubeadmVersion describes the version of the kubeadm CLI
 | |
| 	KubeadmVersion string
 | |
| 	// KubeletVersions is a map with a version number linked to the amount of kubelets running that version in the cluster
 | |
| 	KubeletVersions map[string]uint16
 | |
| 	// EtcdVersion represents the version of etcd used in the cluster
 | |
| 	EtcdVersion string
 | |
| }
 | |
| 
 | |
| // GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
 | |
| // kinds of upgrades can be performed
 | |
| func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed, externalEtcd bool, dnsType kubeadmapi.DNSAddOnType, client clientset.Interface, manifestsDir string) ([]Upgrade, error) {
 | |
| 	fmt.Println("[upgrade] Fetching available versions to upgrade to")
 | |
| 
 | |
| 	// Collect the upgrades kubeadm can do in this list
 | |
| 	upgrades := []Upgrade{}
 | |
| 
 | |
| 	// Get the cluster version
 | |
| 	clusterVersionStr, clusterVersion, err := versionGetterImpl.ClusterVersion()
 | |
| 	if err != nil {
 | |
| 		return upgrades, err
 | |
| 	}
 | |
| 	fmt.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr)
 | |
| 
 | |
| 	// Get current kubeadm CLI version
 | |
| 	kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion()
 | |
| 	if err != nil {
 | |
| 		return upgrades, err
 | |
| 	}
 | |
| 	fmt.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr)
 | |
| 
 | |
| 	// Get and output the current latest stable version
 | |
| 	stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version")
 | |
| 	if err != nil {
 | |
| 		fmt.Printf("[upgrade/versions] WARNING: %v\n", err)
 | |
| 		fmt.Println("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version")
 | |
| 		stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion
 | |
| 	} else {
 | |
| 		fmt.Printf("[upgrade/versions] Latest %s: %s\n", "stable version", stableVersionStr)
 | |
| 	}
 | |
| 
 | |
| 	// Get the kubelet versions in the cluster
 | |
| 	kubeletVersions, err := versionGetterImpl.KubeletVersions()
 | |
| 	if err != nil {
 | |
| 		return upgrades, err
 | |
| 	}
 | |
| 
 | |
| 	// Get current stacked etcd version on the local node
 | |
| 	var etcdVersion string
 | |
| 	if !externalEtcd {
 | |
| 		etcdVersion, err = GetEtcdImageTagFromStaticPod(manifestsDir)
 | |
| 		if err != nil {
 | |
| 			return upgrades, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	currentDNSType, dnsVersion, err := dns.DeployedDNSAddon(client)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Construct a descriptor for the current state of the world
 | |
| 	beforeState := ClusterState{
 | |
| 		KubeVersion:     clusterVersionStr,
 | |
| 		DNSType:         currentDNSType,
 | |
| 		DNSVersion:      dnsVersion,
 | |
| 		KubeadmVersion:  kubeadmVersionStr,
 | |
| 		KubeletVersions: kubeletVersions,
 | |
| 		EtcdVersion:     etcdVersion,
 | |
| 	}
 | |
| 
 | |
| 	// Do a "dumb guess" that a new minor upgrade is available just because the latest stable version is higher than the cluster version
 | |
| 	// This guess will be corrected once we know if there is a patch version available
 | |
| 	canDoMinorUpgrade := clusterVersion.LessThan(stableVersion)
 | |
| 
 | |
| 	// A patch version doesn't exist if the cluster version is higher than or equal to the current stable version
 | |
| 	// 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)
 | |
| 	// a stable-1.8 branch doesn't exist yet. Hence this check.
 | |
| 	if patchVersionBranchExists(clusterVersion, stableVersion) {
 | |
| 		currentBranch := getBranchFromVersion(clusterVersionStr)
 | |
| 		versionLabel := fmt.Sprintf("stable-%s", currentBranch)
 | |
| 		description := fmt.Sprintf("version in the v%s series", currentBranch)
 | |
| 
 | |
| 		// Get and output the latest patch version for the cluster branch
 | |
| 		patchVersionStr, patchVersion, err := versionGetterImpl.VersionFromCILabel(versionLabel, description)
 | |
| 		if err != nil {
 | |
| 			fmt.Printf("[upgrade/versions] WARNING: %v\n", err)
 | |
| 		} else {
 | |
| 			fmt.Printf("[upgrade/versions] Latest %s: %s\n", description, patchVersionStr)
 | |
| 
 | |
| 			// Check if a minor version upgrade is possible when a patch release exists
 | |
| 			// It's only possible if the latest patch version is higher than the current patch version
 | |
| 			// If that's the case, they must be on different branches => a newer minor version can be upgraded to
 | |
| 			canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion)
 | |
| 
 | |
| 			// If the cluster version is lower than the newest patch version, we should inform about the possible upgrade
 | |
| 			if patchUpgradePossible(clusterVersion, patchVersion) {
 | |
| 
 | |
| 				// The kubeadm version has to be upgraded to the latest patch version
 | |
| 				newKubeadmVer := patchVersionStr
 | |
| 				if kubeadmVersion.AtLeast(patchVersion) {
 | |
| 					// In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion
 | |
| 					newKubeadmVer = kubeadmVersionStr
 | |
| 				}
 | |
| 
 | |
| 				upgrades = append(upgrades, Upgrade{
 | |
| 					Description: description,
 | |
| 					Before:      beforeState,
 | |
| 					After: ClusterState{
 | |
| 						KubeVersion:    patchVersionStr,
 | |
| 						DNSType:        dnsType,
 | |
| 						DNSVersion:     kubeadmconstants.GetDNSVersion(dnsType),
 | |
| 						KubeadmVersion: newKubeadmVer,
 | |
| 						EtcdVersion:    getSuggestedEtcdVersion(externalEtcd, patchVersionStr),
 | |
| 						// KubeletVersions is unset here as it is not used anywhere in .After
 | |
| 					},
 | |
| 				})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if canDoMinorUpgrade {
 | |
| 		upgrades = append(upgrades, Upgrade{
 | |
| 			Description: "stable version",
 | |
| 			Before:      beforeState,
 | |
| 			After: ClusterState{
 | |
| 				KubeVersion:    stableVersionStr,
 | |
| 				DNSType:        dnsType,
 | |
| 				DNSVersion:     kubeadmconstants.GetDNSVersion(dnsType),
 | |
| 				KubeadmVersion: stableVersionStr,
 | |
| 				EtcdVersion:    getSuggestedEtcdVersion(externalEtcd, stableVersionStr),
 | |
| 				// KubeletVersions is unset here as it is not used anywhere in .After
 | |
| 			},
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	if experimentalUpgradesAllowed || rcUpgradesAllowed {
 | |
| 		// dl.k8s.io/release/latest.txt is ALWAYS an alpha.X version
 | |
| 		// 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.
 | |
| 		// 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
 | |
| 		// latest-1.7 version is v1.7.4-beta.0
 | |
| 
 | |
| 		// 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
 | |
| 		// 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
 | |
| 
 | |
| 		// 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
 | |
| 		// 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.
 | |
| 
 | |
| 		// The flow looks like this (with time on the X axis):
 | |
| 		// 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
 | |
| 		//                                                                           v1.9.0-alpha.0                                             -> v1.9.0-alpha.1 -> v1.9.0-alpha.2
 | |
| 
 | |
| 		// Get and output the current latest unstable version
 | |
| 		latestVersionStr, latestVersion, err := versionGetterImpl.VersionFromCILabel("latest", "experimental version")
 | |
| 		if err != nil {
 | |
| 			return upgrades, err
 | |
| 		}
 | |
| 		fmt.Printf("[upgrade/versions] Latest %s: %s\n", "experimental version", latestVersionStr)
 | |
| 
 | |
| 		minorUnstable := latestVersion.Components()[1]
 | |
| 		// Get and output the current latest unstable version
 | |
| 		previousBranch := fmt.Sprintf("latest-1.%d", minorUnstable-1)
 | |
| 		previousBranchLatestVersionStr, previousBranchLatestVersion, err := versionGetterImpl.VersionFromCILabel(previousBranch, "")
 | |
| 		if err != nil {
 | |
| 			return upgrades, err
 | |
| 		}
 | |
| 		fmt.Printf("[upgrade/versions] Latest %s: %s\n", "", previousBranchLatestVersionStr)
 | |
| 
 | |
| 		// If that previous latest version is an RC, RCs are allowed and the cluster version is lower than the RC version, show the upgrade
 | |
| 		if rcUpgradesAllowed && rcUpgradePossible(clusterVersion, previousBranchLatestVersion) {
 | |
| 			upgrades = append(upgrades, Upgrade{
 | |
| 				Description: "release candidate version",
 | |
| 				Before:      beforeState,
 | |
| 				After: ClusterState{
 | |
| 					KubeVersion:    previousBranchLatestVersionStr,
 | |
| 					DNSType:        dnsType,
 | |
| 					DNSVersion:     kubeadmconstants.GetDNSVersion(dnsType),
 | |
| 					KubeadmVersion: previousBranchLatestVersionStr,
 | |
| 					EtcdVersion:    getSuggestedEtcdVersion(externalEtcd, previousBranchLatestVersionStr),
 | |
| 					// KubeletVersions is unset here as it is not used anywhere in .After
 | |
| 				},
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 		// Show the possibility if experimental upgrades are allowed
 | |
| 		if experimentalUpgradesAllowed && clusterVersion.LessThan(latestVersion) {
 | |
| 
 | |
| 			// Default to assume that the experimental version to show is the unstable one
 | |
| 			unstableKubeVersion := latestVersionStr
 | |
| 			unstableKubeDNSVersion := kubeadmconstants.GetDNSVersion(dnsType)
 | |
| 
 | |
| 			// Ẃe should not display alpha.0. The previous branch's beta/rc versions are more relevant due how the kube branching process works.
 | |
| 			if latestVersion.PreRelease() == "alpha.0" {
 | |
| 				unstableKubeVersion = previousBranchLatestVersionStr
 | |
| 				unstableKubeDNSVersion = kubeadmconstants.GetDNSVersion(dnsType)
 | |
| 			}
 | |
| 
 | |
| 			upgrades = append(upgrades, Upgrade{
 | |
| 				Description: "experimental version",
 | |
| 				Before:      beforeState,
 | |
| 				After: ClusterState{
 | |
| 					KubeVersion:    unstableKubeVersion,
 | |
| 					DNSType:        dnsType,
 | |
| 					DNSVersion:     unstableKubeDNSVersion,
 | |
| 					KubeadmVersion: unstableKubeVersion,
 | |
| 					EtcdVersion:    getSuggestedEtcdVersion(externalEtcd, unstableKubeVersion),
 | |
| 					// KubeletVersions is unset here as it is not used anywhere in .After
 | |
| 				},
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Add a newline in the end of this output to leave some space to the next output section
 | |
| 	fmt.Println("")
 | |
| 
 | |
| 	return upgrades, nil
 | |
| }
 | |
| 
 | |
| func getBranchFromVersion(version string) string {
 | |
| 	v := versionutil.MustParseGeneric(version)
 | |
| 	return fmt.Sprintf("%d.%d", v.Major(), v.Minor())
 | |
| }
 | |
| 
 | |
| func patchVersionBranchExists(clusterVersion, stableVersion *versionutil.Version) bool {
 | |
| 	return stableVersion.AtLeast(clusterVersion)
 | |
| }
 | |
| 
 | |
| func patchUpgradePossible(clusterVersion, patchVersion *versionutil.Version) bool {
 | |
| 	return clusterVersion.LessThan(patchVersion)
 | |
| }
 | |
| 
 | |
| func rcUpgradePossible(clusterVersion, previousBranchLatestVersion *versionutil.Version) bool {
 | |
| 	return strings.HasPrefix(previousBranchLatestVersion.PreRelease(), "rc") && clusterVersion.LessThan(previousBranchLatestVersion)
 | |
| }
 | |
| 
 | |
| func minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion *versionutil.Version) bool {
 | |
| 	return patchVersion.LessThan(stableVersion)
 | |
| }
 | |
| 
 | |
| func getSuggestedEtcdVersion(externalEtcd bool, kubernetesVersion string) string {
 | |
| 	if externalEtcd {
 | |
| 		return ""
 | |
| 	}
 | |
| 	etcdVersion, warning, err := kubeadmconstants.EtcdSupportedVersion(kubeadmconstants.SupportedEtcdVersion, kubernetesVersion)
 | |
| 	if err != nil {
 | |
| 		klog.Warningf("[upgrade/versions] could not retrieve an etcd version for the target Kubernetes version: %v", err)
 | |
| 		return "N/A"
 | |
| 	}
 | |
| 	if warning != nil {
 | |
| 		klog.Warningf("[upgrade/versions] %v", warning)
 | |
| 	}
 | |
| 	return etcdVersion.String()
 | |
| }
 |